"}
+
/proc/strtohex(str)
if(!istext(str)||!str)
return
diff --git a/code/_compile_options.dm b/code/_compile_options.dm
index 9a36c626ba..0e0bd4ffaa 100644
--- a/code/_compile_options.dm
+++ b/code/_compile_options.dm
@@ -11,15 +11,28 @@
#ifdef TESTING
#define DATUMVAR_DEBUGGING_MODE
-//#define GC_FAILURE_HARD_LOOKUP //makes paths that fail to GC call find_references before del'ing.
- //implies FIND_REF_NO_CHECK_TICK
+/*
+* Enables extools-powered reference tracking system, letting you see what is referencing objects that refuse to hard delete.
+*
+* * Requires TESTING to be defined to work.
+*/
+//#define REFERENCE_TRACKING
-//#define FIND_REF_NO_CHECK_TICK //Sets world.loop_checks to false and prevents find references from sleeping
+///Method of tracking references without using extools. Slower, kept to avoid over-reliance on extools.
+//#define LEGACY_REFERENCE_TRACKING
+#ifdef LEGACY_REFERENCE_TRACKING
+///Use the legacy reference on things hard deleting by default.
+//#define GC_FAILURE_HARD_LOOKUP
+#ifdef GC_FAILURE_HARD_LOOKUP
+#define FIND_REF_NO_CHECK_TICK
+#endif //ifdef GC_FAILURE_HARD_LOOKUP
+
+#endif //ifdef LEGACY_REFERENCE_TRACKING
//#define VISUALIZE_ACTIVE_TURFS //Highlights atmos active turfs in green
-#endif
+#endif //ifdef TESTING
//#define UNIT_TESTS //Enables unit tests via TEST_RUN_PARAMETER
#ifndef PRELOAD_RSC //set to:
diff --git a/code/_globalvars/game_modes.dm b/code/_globalvars/game_modes.dm
index 01a34c2ab7..960e1f8d59 100644
--- a/code/_globalvars/game_modes.dm
+++ b/code/_globalvars/game_modes.dm
@@ -9,4 +9,8 @@ GLOBAL_DATUM(start_state, /datum/station_state) // Used in round-end report
//TODO clear this one up too
-GLOBAL_DATUM(cult_narsie, /obj/singularity/narsie/large/cult)
\ No newline at end of file
+GLOBAL_DATUM(cult_narsie, /obj/singularity/narsie/large/cult)
+
+
+///We want reality_smash_tracker to exist only once and be accesable from anywhere.
+GLOBAL_DATUM_INIT(reality_smash_track, /datum/reality_smash_tracker, new)
diff --git a/code/_globalvars/lists/flavor_misc.dm b/code/_globalvars/lists/flavor_misc.dm
index a25381f266..09d4aadf50 100644
--- a/code/_globalvars/lists/flavor_misc.dm
+++ b/code/_globalvars/lists/flavor_misc.dm
@@ -40,6 +40,46 @@ GLOBAL_LIST_EMPTY(insect_fluffs_list)
GLOBAL_LIST_EMPTY(insect_markings_list)
GLOBAL_LIST_EMPTY(caps_list)
+//a way to index the right bodypart list given the type of bodypart
+GLOBAL_LIST_INIT(mutant_reference_list, list(
+ "tail_lizard" = GLOB.tails_list_lizard,
+ "waggingtail_lizard" = GLOB.animated_tails_list_lizard,
+ "tail_human" = GLOB.tails_list_human,
+ "waggingtail_human" = GLOB.animated_tails_list_human,
+ "spines" = GLOB.spines_list,
+ "waggingspines" = GLOB.animated_spines_list,
+ "snout" = GLOB.snouts_list,
+ "frills" = GLOB.frills_list,
+ "horns" = GLOB.horns_list,
+ "ears" = GLOB.ears_list,
+ "body_markings" = GLOB.body_markings_list,
+ "wings" = GLOB.wings_list,
+ "wingsopen" = GLOB.wings_open_list,
+ "deco_wings" = GLOB.deco_wings_list,
+ "legs" = GLOB.legs_list,
+ "insect_wings" = GLOB.insect_wings_list,
+ "insect_fluff" = GLOB.insect_fluffs_list,
+ "insect_markings" = GLOB.insect_markings_list,
+ "caps" = GLOB.caps_list,
+ "ipc_screen" = GLOB.ipc_screens_list,
+ "ipc_antenna" = GLOB.ipc_antennas_list,
+ "mam_tail" = GLOB.mam_tails_list,
+ "mam_waggingtail" = GLOB.mam_tails_animated_list,
+ "mam_body_markings" = GLOB.mam_body_markings_list,
+ "mam_ears" = GLOB.mam_ears_list,
+ "mam_snouts" = GLOB.mam_snouts_list,
+ "taur" = GLOB.taur_list,
+ "xenodorsal" = GLOB.xeno_dorsal_list,
+ "xenohead" = GLOB.xeno_head_list,
+ "xenotail" = GLOB.xeno_tail_list))
+
+//references wag types to regular types, wings open to wings, etc
+GLOBAL_LIST_INIT(mutant_transform_list, list("wingsopen" = "wings",
+ "waggingtail_human" = "tail_human",
+ "waggingtail_lizard" = "tail_lizard",
+ "waggingspines" = "spines",
+ "mam_waggingtail" = "mam_tail"))
+
GLOBAL_LIST_INIT(ghost_forms_with_directions_list, list("ghost")) //stores the ghost forms that support directional sprites
GLOBAL_LIST_INIT(ghost_forms_with_accessories_list, list("ghost")) //stores the ghost forms that support hair and other such things
@@ -231,4 +271,16 @@ GLOBAL_LIST_INIT(speech_verbs, list("default","says","gibbers", "states", "chitt
GLOBAL_LIST_INIT(roundstart_tongues, list("default","human tongue" = /obj/item/organ/tongue, "lizard tongue" = /obj/item/organ/tongue/lizard, "skeleton tongue" = /obj/item/organ/tongue/bone, "fly tongue" = /obj/item/organ/tongue/fly, "ipc tongue" = /obj/item/organ/tongue/robot/ipc))
+//locked parts are those that your picked species requires to have
+//unlocked parts are those that anyone can choose on customisation regardless
+//parts not in unlocked, but in all, are thus locked
+GLOBAL_LIST_INIT(all_mutant_parts, list("tail_lizard" = "Tail", "mam_tail" = "Tail", "tail_human" = "Tail", "snout" = "Snout", "frills" = "Frills", "spines" = "Spines", "body_markings" = "Body Markings", "mam_body_markings" = "Species Markings" , "mam_ears" = "Ears", "ears" = "Ears", "mam_snouts" = "Snout", "legs" = "Legs", "deco_wings" = "Decorative Wings", "insect_wings" = "Insect Wings", "insect_fluff" = "Insect Fluff", "taur" = "Tauric Body", "insect_markings" = "Insect Markings", "wings" = "Wings", "xenohead" = "Caste Head", "xenotail" = "Tail", "xenodorsal" = "Dorsal Spines", "ipc_screen" = "Screen", "ipc_antenna" = "Antenna", "meat_type" = "Meat Type", "horns" = "Horns"))
+GLOBAL_LIST_INIT(unlocked_mutant_parts, list("horns", "insect_fluff"))
+//parts in either of the above two lists that require a second option that allows them to be coloured
+GLOBAL_LIST_INIT(colored_mutant_parts, list("insect_wings" = "wings_color", "deco_wings" = "wings_color", "horns" = "horns_color"))
+//body ids that have greyscale sprites
+GLOBAL_LIST_INIT(greyscale_limb_types, list("human","moth","lizard","pod","plant","jelly","slime","golem","lum","stargazer","mush","ethereal","snail","c_golem","b_golem","mammal","xeno","ipc","insect","synthliz","avian","aquatic"))
+
+//body ids that have prosthetic sprites
+GLOBAL_LIST_INIT(prosthetic_limb_types, list("xion","bishop","cybersolutions","grayson","hephaestus","nanotrasen","talon"))
diff --git a/code/_globalvars/lists/loadout_categories.dm b/code/_globalvars/lists/loadout_categories.dm
new file mode 100644
index 0000000000..0f0ac52214
--- /dev/null
+++ b/code/_globalvars/lists/loadout_categories.dm
@@ -0,0 +1,13 @@
+GLOBAL_LIST_INIT(loadout_categories, list(
+ LOADOUT_CATEGORY_BACKPACK = list(LOADOUT_SUBCATEGORY_BACKPACK_GENERAL, LOADOUT_SUBCATEGORY_BACKPACK_TOYS),
+ LOADOUT_CATEGORY_NECK = list(LOADOUT_SUBCATEGORY_NECK_GENERAL, LOADOUT_SUBCATEGORY_NECK_TIE, LOADOUT_SUBCATEGORY_NECK_SCARVES),
+ LOADOUT_CATEGORY_MASK = LOADOUT_SUBCATEGORIES_NONE,
+ LOADOUT_CATEGORY_HANDS = LOADOUT_SUBCATEGORIES_NONE,
+ LOADOUT_CATEGORY_UNIFORM = list(LOADOUT_SUBCATEGORY_UNIFORM_GENERAL, LOADOUT_SUBCATEGORY_UNIFORM_JOBS, LOADOUT_SUBCATEGORY_UNIFORM_SUITS, LOADOUT_SUBCATEGORY_UNIFORM_SKIRTS, LOADOUT_SUBCATEGORY_UNIFORM_DRESSES, LOADOUT_SUBCATEGORY_UNIFORM_SWEATERS, LOADOUT_SUBCATEGORY_UNIFORM_PANTS, LOADOUT_SUBCATEGORY_UNIFORM_SHORTS),
+ LOADOUT_CATEGORY_SUIT = list(LOADOUT_SUBCATEGORY_SUIT_GENERAL, LOADOUT_SUBCATEGORY_SUIT_COATS, LOADOUT_SUBCATEGORY_SUIT_JACKETS, LOADOUT_SUBCATEGORY_SUIT_JOBS),
+ LOADOUT_CATEGORY_HEAD = list(LOADOUT_SUBCATEGORY_HEAD_GENERAL, LOADOUT_SUBCATEGORY_HEAD_JOBS),
+ LOADOUT_CATEGORY_SHOES = LOADOUT_SUBCATEGORIES_NONE,
+ LOADOUT_CATEGORY_GLOVES = LOADOUT_SUBCATEGORIES_NONE,
+ LOADOUT_CATEGORY_GLASSES = LOADOUT_SUBCATEGORIES_NONE,
+ LOADOUT_CATEGORY_DONATOR = LOADOUT_SUBCATEGORIES_NONE
+))
diff --git a/code/_globalvars/lists/maintenance_loot.dm b/code/_globalvars/lists/maintenance_loot.dm
index 865f405442..14a1924e76 100644
--- a/code/_globalvars/lists/maintenance_loot.dm
+++ b/code/_globalvars/lists/maintenance_loot.dm
@@ -107,13 +107,8 @@ GLOBAL_LIST_INIT(maintenance_loot, list(
/obj/item/toy/eightball = 1,
/obj/item/reagent_containers/pill/floorpill = 1,
/obj/item/reagent_containers/food/snacks/cannedpeaches/maint = 2,
- /obj/item/storage/daki = 3, //VERY IMPORTANT CIT CHANGE - adds bodypillows to maint
- /obj/item/storage/pill_bottle/penis_enlargement = 2,
- /obj/item/storage/pill_bottle/breast_enlargement = 2,
/obj/item/clothing/shoes/wheelys = 1,
/obj/item/clothing/shoes/kindleKicks = 1,
- /obj/item/autosurgeon/penis = 1,
- /obj/item/autosurgeon/testicles = 1,
/obj/item/storage/box/marshmallow = 2,
/obj/item/clothing/gloves/tackler/offbrand = 1,
/obj/item/stack/sticky_tape = 1,
diff --git a/code/_globalvars/lists/names.dm b/code/_globalvars/lists/names.dm
index e334d08040..b80fcf0bbf 100644
--- a/code/_globalvars/lists/names.dm
+++ b/code/_globalvars/lists/names.dm
@@ -17,6 +17,7 @@ GLOBAL_LIST_INIT(golem_names, world.file2list("strings/names/golem.txt"))
GLOBAL_LIST_INIT(moth_first, world.file2list("strings/names/moth_first.txt"))
GLOBAL_LIST_INIT(moth_last, world.file2list("strings/names/moth_last.txt"))
GLOBAL_LIST_INIT(plasmaman_names, world.file2list("strings/names/plasmaman.txt"))
+GLOBAL_LIST_INIT(ethereal_names, world.file2list("strings/names/ethereal.txt"))
GLOBAL_LIST_INIT(posibrain_names, world.file2list("strings/names/posibrain.txt"))
GLOBAL_LIST_INIT(nightmare_names, world.file2list("strings/names/nightmare.txt"))
GLOBAL_LIST_INIT(megacarp_first_names, world.file2list("strings/names/megacarp1.txt"))
diff --git a/code/_globalvars/lists/objects.dm b/code/_globalvars/lists/objects.dm
index 4de7c88bf7..706a5ed955 100644
--- a/code/_globalvars/lists/objects.dm
+++ b/code/_globalvars/lists/objects.dm
@@ -13,6 +13,7 @@ GLOBAL_LIST_EMPTY(deliverybeacontags) //list of all tags associated with d
GLOBAL_LIST_EMPTY(nuke_list)
GLOBAL_LIST_EMPTY(alarmdisplay) //list of all machines or programs that can display station alerts
GLOBAL_LIST_EMPTY(singularities) //list of all singularities on the station (actually technically all engines)
+GLOBAL_LIST_EMPTY(grounding_rods) //list of all grounding rods on the station
GLOBAL_LIST(chemical_reactions_list) //list of all /datum/chemical_reaction datums. Used during chemical reactions
GLOBAL_LIST(chemical_reagents_list) //list of all /datum/reagent datums indexed by reagent id. Used by chemistry stuff
@@ -41,3 +42,6 @@ GLOBAL_LIST_EMPTY(ai_status_displays)
GLOBAL_LIST_EMPTY(mob_spawners) // All mob_spawn objects
GLOBAL_LIST_EMPTY(alert_consoles) // Station alert consoles, /obj/machinery/computer/station_alert
+
+//list of everyone playing rock paper scissors
+GLOBAL_LIST_EMPTY(rockpaperscissors_players)
diff --git a/code/_globalvars/traits.dm b/code/_globalvars/traits.dm
index 8d8b8820b2..605b20a4f3 100644
--- a/code/_globalvars/traits.dm
+++ b/code/_globalvars/traits.dm
@@ -37,7 +37,9 @@ GLOBAL_LIST_INIT(traits_by_type, list(
"TRAIT_RESISTCOLD" = TRAIT_RESISTCOLD,
"TRAIT_RESISTHIGHPRESSURE" = TRAIT_RESISTHIGHPRESSURE,
"TRAIT_RESISTLOWPRESSURE" = TRAIT_RESISTLOWPRESSURE,
+ "TRAIT_BOMBIMMUNE" = TRAIT_BOMBIMMUNE,
"TRAIT_RADIMMUNE" = TRAIT_RADIMMUNE,
+ "TRAIT_GENELESS" = TRAIT_GENELESS,
"TRAIT_VIRUSIMMUNE" = TRAIT_VIRUSIMMUNE,
"TRAIT_PIERCEIMMUNE" = TRAIT_PIERCEIMMUNE,
"TRAIT_NODISMEMBER" = TRAIT_NODISMEMBER,
diff --git a/code/_onclick/hud/alert.dm b/code/_onclick/hud/alert.dm
index 8111680c41..980ec38909 100644
--- a/code/_onclick/hud/alert.dm
+++ b/code/_onclick/hud/alert.dm
@@ -184,14 +184,23 @@
/obj/screen/alert/hot
name = "Too Hot"
- desc = "You're flaming hot! Get somewhere cooler and take off any insulating clothing like a fire suit."
+ desc = "The air around you is pretty toasty! Consider putting on some insulating clothing, or moving to a cooler area."
icon_state = "hot"
/obj/screen/alert/cold
name = "Too Cold"
- desc = "You're freezing cold! Get somewhere warmer and take off any insulating clothing like a space suit."
+ desc = "The air around you is pretty cold! Consider wearing a coat, or moving to a warmer area."
icon_state = "cold"
+/obj/screen/alert/sweat
+ name = "Sweating"
+ desc = "You're sweating! Get somewhere cooler and take off any insulating clothing like a fire suit."
+ icon_state = "sweat"
+
+/obj/screen/alert/shiver
+ name = "Shivering"
+ desc = "You're shivering! Get somewhere warmer and take off any insulating clothing like a space suit."
+
/obj/screen/alert/lowpressure
name = "Low Pressure"
desc = "The air around you is hazardously thin. A space suit would protect you."
@@ -493,6 +502,16 @@ Recharging stations are available in robotics, the dormitory bathrooms, and the
desc = "Unit's power cell is running low. Recharging stations are available in robotics, the dormitory bathrooms, and the AI satellite."
icon_state = "lowcell"
+/obj/screen/alert/etherealcharge
+ name = "Low Blood Charge"
+ desc = "Your blood's electric charge is running low, find a source of charge for your blood. Use a recharging station found in robotics or the dormitory bathrooms, or eat some Ethereal-friendly food."
+ icon_state = "etherealcharge"
+
+/obj/screen/alert/ethereal_overcharge
+ name = "Blood Overcharge"
+ desc = "Your blood's electric charge is becoming dangerously high, find an outlet for your energy. Use Grab Intent on an APC to channel your energy into it."
+ icon_state = "ethereal_overcharge"
+
//Need to cover all use cases - emag, illegal upgrade module, malf AI hack, traitor cyborg
/obj/screen/alert/hacked
name = "Hacked"
diff --git a/code/_onclick/hud/screen_objects/storage.dm b/code/_onclick/hud/screen_objects/storage.dm
index ce7bc96c96..72b2d035a3 100644
--- a/code/_onclick/hud/screen_objects/storage.dm
+++ b/code/_onclick/hud/screen_objects/storage.dm
@@ -122,13 +122,13 @@
if(pixel_size == pixels)
return
pixel_size = pixels
- cut_overlays(TRUE)
+ cut_overlays()
//our icon size is 32 pixels.
transform = matrix((pixels - (VOLUMETRIC_STORAGE_BOX_BORDER_SIZE * 2)) / VOLUMETRIC_STORAGE_BOX_ICON_SIZE, 0, 0, 0, 1, 0)
left.pixel_x = -((pixels - VOLUMETRIC_STORAGE_BOX_ICON_SIZE) * 0.5) - VOLUMETRIC_STORAGE_BOX_BORDER_SIZE
right.pixel_x = ((pixels - VOLUMETRIC_STORAGE_BOX_ICON_SIZE) * 0.5) + VOLUMETRIC_STORAGE_BOX_BORDER_SIZE
- add_overlay(left, TRUE)
- add_overlay(right, TRUE)
+ add_overlay(left)
+ add_overlay(right)
/obj/screen/storage/volumetric_edge
layer = VOLUMETRIC_STORAGE_BOX_LAYER
diff --git a/code/_onclick/item_attack.dm b/code/_onclick/item_attack.dm
index 54cd4e1fd3..827d67a701 100644
--- a/code/_onclick/item_attack.dm
+++ b/code/_onclick/item_attack.dm
@@ -214,25 +214,24 @@
else
return clamp(w_class * 6, 10, 100) // Multiply the item's weight class by 6, then clamp the value between 10 and 100
-/mob/living/proc/send_item_attack_message(obj/item/I, mob/living/user, hit_area, current_force)
+/mob/living/proc/send_item_attack_message(obj/item/I, mob/living/user, hit_area, obj/item/bodypart/hit_bodypart)
var/message_verb = "attacked"
- if(I.attack_verb && I.attack_verb.len)
+ if(length(I.attack_verb))
message_verb = "[pick(I.attack_verb)]"
- if(current_force < I.force * FEEBLE_ATTACK_MSG_THRESHOLD)
- message_verb = "[pick("feebly", "limply", "saplessly")] [message_verb]"
else if(!I.force)
return
var/message_hit_area = ""
if(hit_area)
message_hit_area = " in the [hit_area]"
- var/attack_message = "[src] has been [message_verb][message_hit_area] with [I]."
+ var/attack_message = "[src] is [message_verb][message_hit_area] with [I]!"
+ var/attack_message_local = "You're [message_verb][message_hit_area] with [I]!"
if(user in viewers(src, null))
- attack_message = "[user] has [message_verb] [src][message_hit_area] with [I]!"
+ attack_message = "[user] [message_verb] [src][message_hit_area] with [I]!"
+ attack_message_local = "[user] [message_verb] you[message_hit_area] with [I]!"
+ if(user == src)
+ attack_message_local = "You [message_verb] yourself[message_hit_area] with [I]"
visible_message("[attack_message]",\
- "[attack_message]", null, COMBAT_MESSAGE_RANGE)
- if(hit_area == BODY_ZONE_HEAD)
- if(prob(2))
- playsound(src, 'sound/weapons/dink.ogg', 30, 1)
+ "[attack_message_local]", null, COMBAT_MESSAGE_RANGE)
return 1
/// How much stamina this takes to swing this is not for realism purposes hecc off.
diff --git a/code/_onclick/overmind.dm b/code/_onclick/overmind.dm
index 8ace273dd8..419524c871 100644
--- a/code/_onclick/overmind.dm
+++ b/code/_onclick/overmind.dm
@@ -32,4 +32,4 @@
/mob/camera/blob/AltClickOn(atom/A) //Remove a blob
var/turf/T = get_turf(A)
if(T)
- remove_blob(T)
\ No newline at end of file
+ remove_blob(T)
diff --git a/code/controllers/configuration/configuration.dm b/code/controllers/configuration/configuration.dm
index 1c6e96f9e4..81f01219b8 100644
--- a/code/controllers/configuration/configuration.dm
+++ b/code/controllers/configuration/configuration.dm
@@ -14,13 +14,15 @@
var/list/modes // allowed modes
var/list/gamemode_cache
var/list/votable_modes // votable modes
- // var/list/ic_filter_regex
var/list/storyteller_cache
var/list/mode_names
var/list/mode_reports
var/list/mode_false_report_weight
var/motd
+ // var/policy
+
+ // var/static/regex/ic_filter_regex
/datum/controller/configuration/proc/admin_reload()
if(IsAdminAdvancedProcCall())
@@ -50,6 +52,11 @@
break
loadmaplist(CONFIG_MAPS_FILE)
LoadMOTD()
+ // LoadPolicy()
+ // LoadChatFilter()
+
+ if (Master)
+ Master.OnConfigLoad()
/datum/controller/configuration/proc/full_wipe()
if(IsAdminAdvancedProcCall())
@@ -135,7 +142,7 @@
if(entry == "$include")
if(!value)
- log_config("LINE [linenumber]: Invalid $include directive: [value]")
+ log_config("LINE [linenumber]: Warning: Invalid $include directive: [value]")
else
LoadEntries(value, stack)
++.
@@ -143,7 +150,7 @@
var/datum/config_entry/E = _entries[entry]
if(!E)
- log_config("LINE [linenumber]: Unknown setting: '[entry]'")
+ log_config("LINE [linenumber]: Unknown setting in configuration: '[entry]'")
continue
if(lockthis)
@@ -153,9 +160,9 @@
var/datum/config_entry/new_ver = entries_by_type[E.deprecated_by]
var/new_value = E.DeprecationUpdate(value)
var/good_update = istext(new_value)
- log_config("LINE [linenumber]: [entry] is deprecated and will be removed soon. Migrate to [new_ver.name]![good_update ? " Suggested new value is: [new_value]" : ""]")
+ log_config("LINE [linenumber]: Entry [entry] is deprecated and will be removed soon. Migrate to [new_ver.name]![good_update ? " Suggested new value is: [new_value]" : ""]")
if(!warned_deprecated_configs)
- addtimer(CALLBACK(GLOBAL_PROC, /proc/message_admins, "This server is using deprecated configuration settings. Please check the logs and update accordingly."), 0)
+ DelayedMessageAdmins("This server is using deprecated configuration settings. Please check the logs and update accordingly.")
warned_deprecated_configs = TRUE
if(good_update)
value = new_value
@@ -163,7 +170,7 @@
else
warning("[new_ver.type] is deprecated but gave no proper return for DeprecationUpdate()")
- var/validated = E.ValidateAndSet(value, TRUE)
+ var/validated = E.ValidateAndSet(value)
if(!validated)
log_config("LINE [linenumber]: Failed to validate setting \"[value]\" for [entry]")
else
@@ -195,13 +202,7 @@
statclick = new/obj/effect/statclick/debug(null, "Edit", src)
stat("[name]:", statclick)
-/datum/controller/configuration/proc/Get(entry_type)
- var/datum/config_entry/E = GetEntryDatum(entry_type)
- if((E.protection & CONFIG_ENTRY_HIDDEN) && IsAdminAdvancedProcCall() && GLOB.LastAdminCalledProc == "Get" && GLOB.LastAdminCalledTargetRef == "[REF(src)]")
- log_admin_private("Config access of [entry_type] attempted by [key_name(usr)]")
- return
- return E.config_entry_value
-
+/// Your typical GET but returns a config.
/datum/controller/configuration/proc/GetEntryDatum(entry_type)
var/datum/config_entry/E = entry_type
var/entry_is_abstract = initial(E.abstract_type) == entry_type
@@ -210,8 +211,24 @@
E = entries_by_type[entry_type]
if(!E)
CRASH("Missing config entry for [entry_type]!")
+ if((E.protection & CONFIG_ENTRY_HIDDEN) && IsAdminAdvancedProcCall() && GLOB.LastAdminCalledProc == "Get" && GLOB.LastAdminCalledTargetRef == "[REF(src)]")
+ log_admin_private("Config access of [entry_type] attempted by [key_name(usr)]")
+ return
return E
+/datum/controller/configuration/proc/Get(entry_type)
+ var/datum/config_entry/E = entry_type
+ var/entry_is_abstract = initial(E.abstract_type) == entry_type
+ if(entry_is_abstract)
+ CRASH("Tried to retrieve an abstract config_entry: [entry_type]")
+ E = entries_by_type[entry_type]
+ if(!E)
+ CRASH("Missing config entry for [entry_type]!")
+ if((E.protection & CONFIG_ENTRY_HIDDEN) && IsAdminAdvancedProcCall() && GLOB.LastAdminCalledProc == "Get" && GLOB.LastAdminCalledTargetRef == "[REF(src)]")
+ log_admin_private("Config access of [entry_type] attempted by [key_name(usr)]")
+ return
+ return E.config_entry_value
+
/datum/controller/configuration/proc/Set(entry_type, new_val)
var/datum/config_entry/E = entry_type
var/entry_is_abstract = initial(E.abstract_type) == entry_type
@@ -236,7 +253,6 @@
for(var/T in gamemode_cache)
// I wish I didn't have to instance the game modes in order to look up
// their information, but it is the only way (at least that I know of).
- // for future reference: just use initial() lol
var/datum/game_mode/M = new T()
if(M.config_tag)
@@ -258,7 +274,37 @@
var/tm_info = GLOB.revdata.GetTestMergeInfo()
if(motd || tm_info)
motd = motd ? "[motd] [tm_info]" : tm_info
+/*
+Policy file should be a json file with a single object.
+Value is raw html.
+Possible keywords :
+Job titles / Assigned roles (ghost spawners for example) : Assistant , Captain , Ash Walker
+Mob types : /mob/living/simple_animal/hostile/carp
+Antagonist types : /datum/antagonist/highlander
+Species types : /datum/species/lizard
+special keywords defined in _DEFINES/admin.dm
+
+Example config:
+{
+ "Assistant" : "Don't kill everyone",
+ "/datum/antagonist/highlander" : "Kill everyone",
+ "Ash Walker" : "Kill all spacemans"
+}
+
+*/
+/*
+/datum/controller/configuration/proc/LoadPolicy()
+ policy = list()
+ var/rawpolicy = file2text("[directory]/policy.json")
+ if(rawpolicy)
+ var/parsed = safe_json_decode(rawpolicy)
+ if(!parsed)
+ log_config("JSON parsing failure for policy.json")
+ DelayedMessageAdmins("JSON parsing failure for policy.json")
+ else
+ policy = parsed
+*/
/datum/controller/configuration/proc/loadmaplist(filename)
log_config("Loading config file [filename]...")
filename = "[directory]/[filename]"
@@ -305,6 +351,8 @@
currentmap.voteweight = text2num(data)
if ("default","defaultmap")
defaultmap = currentmap
+ //if ("votable")
+ // currentmap.votable = TRUE
if ("endmap")
LAZYINITLIST(maplist)
maplist[currentmap.map_name] = currentmap
@@ -326,6 +374,7 @@
return new T
return new /datum/game_mode/extended()
+/// For dynamic.
/datum/controller/configuration/proc/pick_storyteller(storyteller_name)
for(var/T in storyteller_cache)
var/datum/dynamic_storyteller/S = T
@@ -334,6 +383,32 @@
return T
return /datum/dynamic_storyteller/classic
+/// Same with this
+/datum/controller/configuration/proc/get_runnable_storytellers()
+ var/list/datum/dynamic_storyteller/runnable_storytellers = new
+ var/list/probabilities = Get(/datum/config_entry/keyed_list/storyteller_weight)
+ var/list/repeated_mode_adjust = Get(/datum/config_entry/number_list/repeated_mode_adjust)
+ var/list/min_player_counts = Get(/datum/config_entry/keyed_list/storyteller_min_players)
+ for(var/T in storyteller_cache)
+ var/datum/dynamic_storyteller/S = T
+ var/config_tag = initial(S.config_tag)
+ var/probability = (config_tag in probabilities) ? probabilities[config_tag] : initial(S.weight)
+ var/min_players = (config_tag in min_player_counts) ? min_player_counts[config_tag] : initial(S.min_players)
+ if(probability <= 0)
+ continue
+ if(length(GLOB.player_list) < min_players)
+ continue
+ if(SSpersistence.saved_storytellers.len == repeated_mode_adjust.len)
+ var/name = initial(S.name)
+ var/recent_round = min(SSpersistence.saved_storytellers.Find(name),3)
+ var/adjustment = 0
+ while(recent_round)
+ adjustment += repeated_mode_adjust[recent_round]
+ recent_round = SSpersistence.saved_modes.Find(name,recent_round+1,0)
+ probability *= ((100-adjustment)/100)
+ runnable_storytellers[S] = probability
+ return runnable_storytellers
+
/datum/controller/configuration/proc/get_runnable_modes()
var/list/datum/game_mode/runnable_modes = new
var/list/probabilities = Get(/datum/config_entry/keyed_list/probability)
@@ -367,32 +442,6 @@
runnable_modes[M] = final_weight
return runnable_modes
-/datum/controller/configuration/proc/get_runnable_storytellers()
- var/list/datum/dynamic_storyteller/runnable_storytellers = new
- var/list/probabilities = Get(/datum/config_entry/keyed_list/storyteller_weight)
- var/list/repeated_mode_adjust = Get(/datum/config_entry/number_list/repeated_mode_adjust)
- var/list/min_player_counts = Get(/datum/config_entry/keyed_list/storyteller_min_players)
- for(var/T in storyteller_cache)
- var/datum/dynamic_storyteller/S = T
- var/config_tag = initial(S.config_tag)
- var/probability = (config_tag in probabilities) ? probabilities[config_tag] : initial(S.weight)
- var/min_players = (config_tag in min_player_counts) ? min_player_counts[config_tag] : initial(S.min_players)
- if(probability <= 0)
- continue
- if(length(GLOB.player_list) < min_players)
- continue
- if(SSpersistence.saved_storytellers.len == repeated_mode_adjust.len)
- var/name = initial(S.name)
- var/recent_round = min(SSpersistence.saved_storytellers.Find(name),3)
- var/adjustment = 0
- while(recent_round)
- adjustment += repeated_mode_adjust[recent_round]
- recent_round = SSpersistence.saved_modes.Find(name,recent_round+1,0)
- probability *= ((100-adjustment)/100)
- runnable_storytellers[S] = probability
- return runnable_storytellers
-
-
/datum/controller/configuration/proc/get_runnable_midround_modes(crew)
var/list/datum/game_mode/runnable_modes = new
var/list/probabilities = Get(/datum/config_entry/keyed_list/probability)
@@ -418,7 +467,6 @@
/*
/datum/controller/configuration/proc/LoadChatFilter()
var/list/in_character_filter = list()
-
if(!fexists("[directory]/in_character_filter.txt"))
return
log_config("Loading config file in_character_filter.txt...")
@@ -428,8 +476,8 @@
if(findtextEx(line,"#",1,2))
continue
in_character_filter += REGEX_QUOTE(line)
-
ic_filter_regex = in_character_filter.len ? regex("\\b([jointext(in_character_filter, "|")])\\b", "i") : null
-
- syncChatRegexes()
*/
+//Message admins when you can.
+/datum/controller/configuration/proc/DelayedMessageAdmins(text)
+ addtimer(CALLBACK(GLOBAL_PROC, /proc/message_admins, text), 0)
diff --git a/code/controllers/configuration/entries/game_options.dm b/code/controllers/configuration/entries/game_options.dm
index 5765f291e9..2e3cb1ea46 100644
--- a/code/controllers/configuration/entries/game_options.dm
+++ b/code/controllers/configuration/entries/game_options.dm
@@ -98,6 +98,11 @@
config_entry_value = 6
min_val = 1
+/datum/config_entry/number/ecult_scaling_coeff //how much does the amount of players get divided by to determine e_cult
+ config_entry_value = 6
+ integer = FALSE
+ min_val = 1
+
/datum/config_entry/number/security_scaling_coeff //how much does the amount of players get divided by to determine open security officer positions
config_entry_value = 8
min_val = 1
@@ -529,4 +534,4 @@
config_entry_value = 6
/datum/config_entry/number/max_shuttle_size
- config_entry_value = 250
+ config_entry_value = 500
diff --git a/code/controllers/configuration/entries/general.dm b/code/controllers/configuration/entries/general.dm
index 2d5e69f149..273ac7fd3e 100644
--- a/code/controllers/configuration/entries/general.dm
+++ b/code/controllers/configuration/entries/general.dm
@@ -368,6 +368,10 @@
/datum/config_entry/flag/allow_map_voting
+/datum/config_entry/number/client_warn_version
+ config_entry_value = null
+ min_val = 500
+
/datum/config_entry/number/client_warn_version
config_entry_value = null
min_val = 500
@@ -384,6 +388,10 @@
/datum/config_entry/string/client_error_message
config_entry_value = "Your version of byond is too old, may have issues, and is blocked from accessing this server."
+/datum/config_entry/number/client_error_build
+ config_entry_value = null
+ min_val = 0
+
/datum/config_entry/number/minute_topic_limit
config_entry_value = null
min_val = 0
diff --git a/code/controllers/configuration/entries/resources.dm b/code/controllers/configuration/entries/resources.dm
new file mode 100644
index 0000000000..c839ccc078
--- /dev/null
+++ b/code/controllers/configuration/entries/resources.dm
@@ -0,0 +1,30 @@
+/datum/config_entry/keyed_list/external_rsc_urls
+ key_mode = KEY_MODE_TEXT
+ value_mode = VALUE_MODE_FLAG
+
+/datum/config_entry/flag/asset_simple_preload
+
+/datum/config_entry/string/asset_transport
+/datum/config_entry/string/asset_transport/ValidateAndSet(str_val)
+ return (lowertext(str_val) in list("simple", "webroot")) && ..(lowertext(str_val))
+
+/datum/config_entry/string/asset_cdn_webroot
+ protection = CONFIG_ENTRY_LOCKED
+
+/datum/config_entry/string/asset_cdn_webroot/ValidateAndSet(str_var)
+ if (!str_var || trim(str_var) == "")
+ return FALSE
+ if (str_var && str_var[length(str_var)] != "/")
+ str_var += "/"
+ return ..(str_var)
+
+/datum/config_entry/string/asset_cdn_url
+ protection = CONFIG_ENTRY_LOCKED
+ default = null
+
+/datum/config_entry/string/asset_cdn_url/ValidateAndSet(str_var)
+ if (!str_var || trim(str_var) == "")
+ return FALSE
+ if (str_var && str_var[length(str_var)] != "/")
+ str_var += "/"
+ return ..(str_var)
diff --git a/code/controllers/controller.dm b/code/controllers/controller.dm
index 06547d120d..c9d5f1e565 100644
--- a/code/controllers/controller.dm
+++ b/code/controllers/controller.dm
@@ -16,4 +16,4 @@
/datum/controller/proc/Recover()
-/datum/controller/proc/stat_entry()
\ No newline at end of file
+/datum/controller/proc/stat_entry()
diff --git a/code/controllers/master.dm b/code/controllers/master.dm
index cdbea1de85..8a381558a9 100644
--- a/code/controllers/master.dm
+++ b/code/controllers/master.dm
@@ -76,7 +76,11 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
// Highlander-style: there can only be one! Kill off the old and replace it with the new.
if(!random_seed)
- random_seed = (TEST_RUN_PARAMETER in world.params) ? 29051994 : rand(1, 1e9)
+ #ifdef UNIT_TESTS
+ random_seed = 29051994
+ #else
+ random_seed = rand(1, 1e9)
+ #endif
rand_seed(random_seed)
var/list/_subsystems = list()
@@ -184,9 +188,6 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
if(delay)
sleep(delay)
- if(tgs_prime)
- world.TgsInitializationComplete()
-
if(init_sss)
init_subtypes(/datum/controller/subsystem, subsystems)
@@ -219,6 +220,9 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
world.fps = CONFIG_GET(number/fps)
var/initialized_tod = REALTIMEOFDAY
+ if(tgs_prime)
+ world.TgsInitializationComplete()
+
if(sleep_offline_after_initializations)
world.sleep_offline = TRUE
sleep(1)
@@ -643,3 +647,8 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
processing = CONFIG_GET(number/mc_tick_rate/base_mc_tick_rate)
else if (client_count > CONFIG_GET(number/mc_tick_rate/high_pop_mc_mode_amount))
processing = CONFIG_GET(number/mc_tick_rate/high_pop_mc_tick_rate)
+
+/datum/controller/master/proc/OnConfigLoad()
+ for (var/thing in subsystems)
+ var/datum/controller/subsystem/SS = thing
+ SS.OnConfigLoad()
diff --git a/code/controllers/subsystem.dm b/code/controllers/subsystem.dm
index 4ee3a01956..ce582f52f9 100644
--- a/code/controllers/subsystem.dm
+++ b/code/controllers/subsystem.dm
@@ -1,39 +1,91 @@
+/**
+ * # Subsystem base class
+ *
+ * Defines a subsystem to be managed by the [Master Controller][/datum/controller/master]
+ *
+ * Simply define a child of this subsystem, using the [SUBSYSTEM_DEF] macro, and the MC will handle registration.
+ * Changing the name is required
+**/
/datum/controller/subsystem
// Metadata; you should define these.
- name = "fire coderbus" //name of the subsystem
- var/init_order = INIT_ORDER_DEFAULT //order of initialization. Higher numbers are initialized first, lower numbers later. Use defines in __DEFINES/subsystems.dm for easy understanding of order.
- var/wait = 20 //time to wait (in deciseconds) between each call to fire(). Must be a positive integer.
- var/priority = FIRE_PRIORITY_DEFAULT //When mutiple subsystems need to run in the same tick, higher priority subsystems will run first and be given a higher share of the tick before MC_TICK_CHECK triggers a sleep
- var/flags = 0 //see MC.dm in __DEFINES Most flags must be set on world start to take full effect. (You can also restart the mc to force them to process again)
+ /// Name of the subsystem - you must change this
+ name = "fire coderbus"
- var/initialized = FALSE //set to TRUE after it has been initialized, will obviously never be set if the subsystem doesn't initialize
+ /// Order of initialization. Higher numbers are initialized first, lower numbers later. Use or create defines such as [INIT_ORDER_DEFAULT] so we can see the order in one file.
+ var/init_order = INIT_ORDER_DEFAULT
- //set to 0 to prevent fire() calls, mostly for admin use or subsystems that may be resumed later
- // use the SS_NO_FIRE flag instead for systems that never fire to keep it from even being added to the list
+ /// Time to wait (in deciseconds) between each call to fire(). Must be a positive integer.
+ var/wait = 20
+
+ /// Priority Weight: When mutiple subsystems need to run in the same tick, higher priority subsystems will be given a higher share of the tick before MC_TICK_CHECK triggers a sleep, higher priority subsystems also run before lower priority subsystems
+ var/priority = FIRE_PRIORITY_DEFAULT
+
+ /// [Subsystem Flags][SS_NO_INIT] to control binary behavior. Flags must be set at compile time or before preinit finishes to take full effect. (You can also restart the mc to force them to process again)
+ var/flags = 0
+
+ /// This var is set to TRUE after the subsystem has been initialized.
+ var/initialized = FALSE
+
+ /// Set to 0 to prevent fire() calls, mostly for admin use or subsystems that may be resumed later
+ /// use the [SS_NO_FIRE] flag instead for systems that never fire to keep it from even being added to list that is checked every tick
var/can_fire = TRUE
- // Bookkeeping variables; probably shouldn't mess with these.
- var/last_fire = 0 //last world.time we called fire()
- var/next_fire = 0 //scheduled world.time for next fire()
- var/cost = 0 //average time to execute
- var/tick_usage = 0 //average tick usage
- var/tick_overrun = 0 //average tick overrun
- var/state = SS_IDLE //tracks the current state of the ss, running, paused, etc.
- var/paused_ticks = 0 //ticks this ss is taking to run right now.
- var/paused_tick_usage //total tick_usage of all of our runs while pausing this run
- var/ticks = 1 //how many ticks does this ss take to run on avg.
- var/times_fired = 0 //number of times we have called fire()
- var/queued_time = 0 //time we entered the queue, (for timing and priority reasons)
- var/queued_priority //we keep a running total to make the math easier, if priority changes mid-fire that would break our running total, so we store it here
- //linked list stuff for the queue
- var/datum/controller/subsystem/queue_next
- var/datum/controller/subsystem/queue_prev
-
+ ///Bitmap of what game states can this subsystem fire at. See [RUNLEVELS_DEFAULT] for more details.
var/runlevels = RUNLEVELS_DEFAULT //points of the game at which the SS can fire
- var/static/list/failure_strikes //How many times we suspect a subsystem type has crashed the MC, 3 strikes and you're out!
+ /*
+ * The following variables are managed by the MC and should not be modified directly.
+ */
+
+ /// Last world.time the subsystem completed a run (as in wasn't paused by [MC_TICK_CHECK])
+ var/last_fire = 0
+
+ /// Scheduled world.time for next fire()
+ var/next_fire = 0
+
+ /// Running average of the amount of milliseconds it takes the subsystem to complete a run (including all resumes but not the time spent paused)
+ var/cost = 0
+
+ /// Running average of the amount of tick usage in percents of a tick it takes the subsystem to complete a run
+ var/tick_usage = 0
+
+ /// Running average of the amount of tick usage (in percents of a game tick) the subsystem has spent past its allocated time without pausing
+ var/tick_overrun = 0
+
+ /// Tracks the current execution state of the subsystem. Used to handle subsystems that sleep in fire so the mc doesn't run them again while they are sleeping
+ var/state = SS_IDLE
+
+ /// Tracks how many fires the subsystem has consecutively paused on in the current run
+ var/paused_ticks = 0
+
+ /// Tracks how much of a tick the subsystem has consumed in the current run
+ var/paused_tick_usage
+
+ /// Tracks how many fires the subsystem takes to complete a run on average.
+ var/ticks = 1
+
+ /// Tracks the amount of completed runs for the subsystem
+ var/times_fired = 0
+
+ /// Time the subsystem entered the queue, (for timing and priority reasons)
+ var/queued_time = 0
+
+ /// Priority at the time the subsystem entered the queue. Needed to avoid changes in priority (by admins and the like) from breaking things.
+ var/queued_priority
+
+ /// How many times we suspect a subsystem type has crashed the MC, 3 strikes and you're out!
+ var/static/list/failure_strikes
+
+ /// Next subsystem in the queue of subsystems to run this tick
+ var/datum/controller/subsystem/queue_next
+ /// Previous subsystem in the queue of subsystems to run this tick
+ var/datum/controller/subsystem/queue_prev
+
+ //Do not blindly add vars here to the bottom, put it where it goes above
+ //If your var only has two values, put it in as a flag.
+
//Do not override
///datum/controller/subsystem/New()
@@ -46,6 +98,7 @@
//This is used so the mc knows when the subsystem sleeps. do not override.
/datum/controller/subsystem/proc/ignite(resumed = 0)
+ SHOULD_NOT_OVERRIDE(TRUE)
set waitfor = 0
. = SS_SLEEPING
fire(resumed)
@@ -87,7 +140,7 @@
queue_node_flags = queue_node.flags
if (queue_node_flags & SS_TICKER)
- if (!(SS_flags & SS_TICKER))
+ if ((SS_flags & (SS_TICKER|SS_BACKGROUND)) != SS_TICKER)
continue
if (queue_node_priority < SS_priority)
break
@@ -155,6 +208,9 @@
if(SS_SLEEPING)
state = SS_PAUSING
+/// Called after the config has been loaded or reloaded.
+/datum/controller/subsystem/proc/OnConfigLoad()
+
/datum/controller/subsystem/proc/subsystem_log(msg)
return log_subsystem(name, msg)
diff --git a/code/controllers/subsystem/acid.dm b/code/controllers/subsystem/acid.dm
index e3c415960b..7c9e7634ab 100644
--- a/code/controllers/subsystem/acid.dm
+++ b/code/controllers/subsystem/acid.dm
@@ -29,7 +29,7 @@ SUBSYSTEM_DEF(acid)
if(O.acid_level && O.acid_processing())
else
- O.cut_overlay(GLOB.acid_overlay, TRUE)
+ O.update_icon()
processing -= O
if (MC_TICK_CHECK)
diff --git a/code/controllers/subsystem/assets.dm b/code/controllers/subsystem/assets.dm
index 7285298283..4f02d32ad0 100644
--- a/code/controllers/subsystem/assets.dm
+++ b/code/controllers/subsystem/assets.dm
@@ -4,6 +4,23 @@ SUBSYSTEM_DEF(assets)
flags = SS_NO_FIRE
var/list/cache = list()
var/list/preload = list()
+ var/datum/asset_transport/transport = new()
+
+/datum/controller/subsystem/assets/OnConfigLoad()
+ var/newtransporttype = /datum/asset_transport
+ switch (CONFIG_GET(string/asset_transport))
+ if ("webroot")
+ newtransporttype = /datum/asset_transport/webroot
+
+ if (newtransporttype == transport.type)
+ return
+
+ var/datum/asset_transport/newtransport = new newtransporttype ()
+ if (newtransport.validate_config())
+ transport = newtransport
+ transport.Load()
+
+
/datum/controller/subsystem/assets/Initialize(timeofday)
for(var/type in typesof(/datum/asset))
@@ -11,8 +28,6 @@ SUBSYSTEM_DEF(assets)
if (type != initial(A._abstract))
get_asset_datum(type)
- preload = cache.Copy() //don't preload assets generated during the round
+ transport.Initialize(cache)
- for(var/client/C in GLOB.clients)
- addtimer(CALLBACK(GLOBAL_PROC, .proc/getFilesSlow, C, preload, FALSE), 10)
..()
diff --git a/code/controllers/subsystem/atoms.dm b/code/controllers/subsystem/atoms.dm
index db1fced637..b9a4785f49 100644
--- a/code/controllers/subsystem/atoms.dm
+++ b/code/controllers/subsystem/atoms.dm
@@ -90,6 +90,8 @@ SUBSYSTEM_DEF(atoms)
qdeleted = TRUE
else if(!(A.flags_1 & INITIALIZED_1))
BadInitializeCalls[the_type] |= BAD_INIT_DIDNT_INIT
+ else
+ SEND_SIGNAL(A,COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZE)
return qdeleted || QDELING(A)
diff --git a/code/controllers/subsystem/chat.dm b/code/controllers/subsystem/chat.dm
index ba2794d00f..a4f8dfdc5d 100644
--- a/code/controllers/subsystem/chat.dm
+++ b/code/controllers/subsystem/chat.dm
@@ -5,91 +5,35 @@ SUBSYSTEM_DEF(chat)
priority = FIRE_PRIORITY_CHAT
init_order = INIT_ORDER_CHAT
- var/list/payload = list()
-
+ var/list/payload_by_client = list()
/datum/controller/subsystem/chat/fire()
- for(var/i in payload)
- var/client/C = i
- C << output(payload[C], "browseroutput:output")
- payload -= C
-
+ for(var/key in payload_by_client)
+ var/client/client = key
+ var/payload = payload_by_client[key]
+ payload_by_client -= key
+ if(client)
+ // Send to tgchat
+ client.tgui_panel?.window.send_message("chat/message", payload)
+ // Send to old chat
+ for(var/msg in payload)
+ SEND_TEXT(client, msg["text"])
if(MC_TICK_CHECK)
return
-
-/datum/controller/subsystem/chat/proc/queue(target, message, handle_whitespace = TRUE, trailing_newline = TRUE, confidential = TRUE)
- if(!target || !message)
- return
-
- if(!istext(message))
- stack_trace("to_chat called with invalid input type")
- return
-
- if(target == world)
- target = GLOB.clients
-
- //Some macros remain in the string even after parsing and fuck up the eventual output
- var/original_message = message
-
- //url_encode it TWICE, this way any UTF-8 characters are able to be decoded by the Javascript.
- //Do the double-encoding here to save nanoseconds
- var/twiceEncoded
-
+/datum/controller/subsystem/chat/proc/queue(target, text, flags)
if(islist(target))
- var/sanitized_message = FALSE
- for(var/I in target)
- var/client/C = CLIENT_FROM_VAR(I) //Grab us a client if possible
-
- if(!C)
- continue
-
- //Send it to the old style output window.
- SEND_TEXT(C, original_message)
-
- if(!C?.chatOutput || C.chatOutput.broken) //A player who hasn't updated his skin file.
- continue
-
- if(!sanitized_message)
- message = replacetext(message, "\improper", "")
- message = replacetext(message, "\proper", "")
- if(handle_whitespace)
- message = replacetext(message, "\n", " ")
- message = replacetext(message, "\t", "[FOURSPACES][FOURSPACES]")
- if (trailing_newline)
- message += " "
- twiceEncoded = url_encode(url_encode(message))
- sanitized_message = TRUE
-
- if(!C.chatOutput.loaded) //Client still loading, put their messages in a queue
- C.chatOutput.messageQueue += message
- continue
-
- payload[C] += twiceEncoded
-
- else
- var/client/C = CLIENT_FROM_VAR(target) //Grab us a client if possible
-
- if(!C)
- return
-
- //Send it to the old style output window.
- SEND_TEXT(C, original_message)
-
- if(!C?.chatOutput || C.chatOutput.broken) //A player who hasn't updated his skin file.
- return
-
- message = replacetext(message, "\improper", "")
- message = replacetext(message, "\proper", "")
- if(handle_whitespace)
- message = replacetext(message, "\n", " ")
- message = replacetext(message, "\t", "[FOURSPACES][FOURSPACES]")
- if (trailing_newline)
- message += " "
- twiceEncoded = url_encode(url_encode(message))
-
- if(!C.chatOutput.loaded) //Client still loading, put their messages in a queue
- C.chatOutput.messageQueue += message
- return
-
- payload[C] += twiceEncoded
+ for(var/_target in target)
+ var/client/client = CLIENT_FROM_VAR(_target)
+ if(client)
+ LAZYADD(payload_by_client[client], list(list(
+ "text" = text,
+ "flags" = flags,
+ )))
+ return
+ var/client/client = CLIENT_FROM_VAR(target)
+ if(client)
+ LAZYADD(payload_by_client[client], list(list(
+ "text" = text,
+ "flags" = flags,
+ )))
diff --git a/code/controllers/subsystem/fluid.dm b/code/controllers/subsystem/fluid.dm
new file mode 100644
index 0000000000..c4fa13d693
--- /dev/null
+++ b/code/controllers/subsystem/fluid.dm
@@ -0,0 +1,5 @@
+PROCESSING_SUBSYSTEM_DEF(fluids)
+ name = "Fluids"
+ wait = 20
+ stat_tag = "FD" //its actually Fluid Ducts
+ flags = SS_NO_INIT | SS_TICKER
diff --git a/code/controllers/subsystem/garbage.dm b/code/controllers/subsystem/garbage.dm
index 8a1c08bc35..b46e22c1fa 100644
--- a/code/controllers/subsystem/garbage.dm
+++ b/code/controllers/subsystem/garbage.dm
@@ -25,7 +25,7 @@ SUBSYSTEM_DEF(garbage)
//Queue
var/list/queues
- #ifdef TESTING
+ #ifdef LEGACY_REFERENCE_TRACKING
var/list/reference_find_on_fail = list()
var/list/reference_find_on_fail_types = list()
#endif
@@ -134,7 +134,7 @@ SUBSYSTEM_DEF(garbage)
++gcedlasttick
++totalgcs
pass_counts[level]++
- #ifdef TESTING
+ #ifdef LEGACY_REFERENCE_TRACKING
reference_find_on_fail -= refID //It's deleted we don't care anymore.
#endif
if (MC_TICK_CHECK)
@@ -145,7 +145,9 @@ SUBSYSTEM_DEF(garbage)
fail_counts[level]++
switch (level)
if (GC_QUEUE_CHECK)
- #ifdef TESTING
+ #ifdef REFERENCE_TRACKING
+ D.find_references()
+ #elif defined(LEGACY_REFERENCE_TRACKING)
if(reference_find_on_fail[refID])
D.find_references()
#ifdef GC_FAILURE_HARD_LOOKUP
@@ -156,7 +158,19 @@ SUBSYSTEM_DEF(garbage)
#endif
var/type = D.type
var/datum/qdel_item/I = items[type]
+ #ifdef TESTING
+ log_world("## TESTING: GC: -- \ref[D] | [type] was unable to be GC'd --")
+ for(var/c in GLOB.admins) //Using testing() here would fill the logs with ADMIN_VV garbage
+ var/client/admin = c
+ if(!check_rights_for(admin, R_ADMIN))
+ continue
+ to_chat(admin, "## TESTING: GC: -- [ADMIN_VV(D)] | [type] was unable to be GC'd --")
testing("GC: -- \ref[src] | [type] was unable to be GC'd --")
+ #endif
+ #ifdef REFERENCE_TRACKING
+ GLOB.deletion_failures += D //It should no longer be bothered by the GC, manual deletion only.
+ continue
+ #endif
I.failures++
if (GC_QUEUE_HARDDELETE)
HardDelete(D)
@@ -181,7 +195,7 @@ SUBSYSTEM_DEF(garbage)
var/gctime = world.time
var/refid = "\ref[D]"
-#ifdef TESTING
+#ifdef LEGACY_REFERENCE_TRACKING
if(reference_find_on_fail_types[D.type])
reference_find_on_fail["\ref[D]"] = TRUE
#endif
@@ -193,7 +207,7 @@ SUBSYSTEM_DEF(garbage)
queue[refid] = gctime
-#ifdef TESTING
+#ifdef LEGACY_REFERENCE_TRACKING
/datum/controller/subsystem/garbage/proc/add_type_to_findref(type)
if(!ispath(type))
return "NOT A VAILD PATH"
@@ -260,12 +274,6 @@ SUBSYSTEM_DEF(garbage)
/datum/qdel_item/New(mytype)
name = "[mytype]"
-#ifdef TESTING
-/proc/qdel_and_find_ref_if_fail(datum/D, force = FALSE)
- SSgarbage.reference_find_on_fail["\ref[D]"] = TRUE
- qdel(D, force)
-#endif
-
// Should be treated as a replacement for the 'del' keyword.
// Datums passed to this will be given a chance to clean up references to allow the GC to collect them.
/proc/qdel(datum/D, force=FALSE, ...)
@@ -319,16 +327,13 @@ SUBSYSTEM_DEF(garbage)
SSgarbage.Queue(D, GC_QUEUE_HARDDELETE)
if (QDEL_HINT_HARDDEL_NOW) //qdel should assume this object won't gc, and hard del it post haste.
SSgarbage.HardDelete(D)
- if (QDEL_HINT_FINDREFERENCE)//qdel will, if TESTING is enabled, display all references to this object, then queue the object for deletion.
+ #ifdef LEGACY_REFERENCE_TRACKING
+ if (QDEL_HINT_FINDREFERENCE) //qdel will, if LEGACY_REFERENCE_TRACKING is enabled, display all references to this object, then queue the object for deletion.
SSgarbage.Queue(D)
- #ifdef TESTING
- D.find_references()
- #endif
if (QDEL_HINT_IFFAIL_FINDREFERENCE)
SSgarbage.Queue(D)
- #ifdef TESTING
SSgarbage.reference_find_on_fail["\ref[D]"] = TRUE
- #endif
+ #endif
else
#ifdef TESTING
if(!I.no_hint)
@@ -339,119 +344,6 @@ SUBSYSTEM_DEF(garbage)
else if(D.gc_destroyed == GC_CURRENTLY_BEING_QDELETED)
CRASH("[D.type] destroy proc was called multiple times, likely due to a qdel loop in the Destroy logic")
-#ifdef TESTING
-
-/datum/verb/find_refs()
- set category = "Debug"
- set name = "Find References"
- set src in world
-
- find_references(FALSE)
-
-/datum/proc/find_references(skip_alert)
- running_find_references = type
- if(usr && usr.client)
- if(usr.client.running_find_references)
- testing("CANCELLED search for references to a [usr.client.running_find_references].")
- usr.client.running_find_references = null
- running_find_references = null
- //restart the garbage collector
- SSgarbage.can_fire = 1
- SSgarbage.next_fire = world.time + world.tick_lag
- return
-
- if(!skip_alert)
- if(alert("Running this will lock everything up for about 5 minutes. Would you like to begin the search?", "Find References", "Yes", "No") == "No")
- running_find_references = null
- return
-
- //this keeps the garbage collector from failing to collect objects being searched for in here
- SSgarbage.can_fire = 0
-
- if(usr && usr.client)
- usr.client.running_find_references = type
-
- testing("Beginning search for references to a [type].")
- last_find_references = world.time
-
- DoSearchVar(GLOB) //globals
- for(var/datum/thing in world) //atoms (don't beleive it's lies)
- DoSearchVar(thing, "World -> [thing]")
-
- for (var/datum/thing) //datums
- DoSearchVar(thing, "World -> [thing]")
-
- for (var/client/thing) //clients
- DoSearchVar(thing, "World -> [thing]")
-
- testing("Completed search for references to a [type].")
- if(usr && usr.client)
- usr.client.running_find_references = null
- running_find_references = null
-
- //restart the garbage collector
- SSgarbage.can_fire = 1
- SSgarbage.next_fire = world.time + world.tick_lag
-
-/datum/verb/qdel_then_find_references()
- set category = "Debug"
- set name = "qdel() then Find References"
- set src in world
-
- qdel(src, TRUE) //Force.
- if(!running_find_references)
- find_references(TRUE)
-
-/datum/verb/qdel_then_if_fail_find_references()
- set category = "Debug"
- set name = "qdel() then Find References if GC failure"
- set src in world
-
- qdel_and_find_ref_if_fail(src, TRUE)
-
-/datum/proc/DoSearchVar(X, Xname, recursive_limit = 64)
- if(usr && usr.client && !usr.client.running_find_references)
- return
- if (!recursive_limit)
- return
-
- if(istype(X, /datum))
- var/datum/D = X
- if(D.last_find_references == last_find_references)
- return
-
- D.last_find_references = last_find_references
- var/list/L = D.vars
-
- for(var/varname in L)
- if (varname == "vars")
- continue
- var/variable = L[varname]
-
- if(variable == src)
- testing("Found [src.type] \ref[src] in [D.type]'s [varname] var. [Xname]")
-
- else if(islist(variable))
- DoSearchVar(variable, "[Xname] -> list", recursive_limit-1)
-
- else if(islist(X))
- var/normal = IS_NORMAL_LIST(X)
- for(var/I in X)
- if (I == src)
- testing("Found [src.type] \ref[src] in list [Xname].")
-
- else if (I && !isnum(I) && normal && X[I] == src)
- testing("Found [src.type] \ref[src] in list [Xname]\[[I]\]")
-
- else if (islist(I))
- DoSearchVar(I, "[Xname] -> list", recursive_limit-1)
-
-#ifndef FIND_REF_NO_CHECK_TICK
- CHECK_TICK
-#endif
-
-#endif
-
#ifdef TESTING
/proc/writeDatumCount()
var/list/datums = list()
diff --git a/code/controllers/subsystem/job.dm b/code/controllers/subsystem/job.dm
index 4b2cf24f1e..4f59992ace 100644
--- a/code/controllers/subsystem/job.dm
+++ b/code/controllers/subsystem/job.dm
@@ -674,7 +674,7 @@ SUBSYSTEM_DEF(job)
return
for(var/i in the_mob.client.prefs.chosen_gear)
var/datum/gear/G = i
- G = GLOB.loadout_items[slot_to_string(initial(G.category))][initial(G.name)]
+ G = GLOB.loadout_items[initial(G.category)][initial(G.subcategory)][initial(G.name)]
if(!G)
continue
var/permitted = TRUE
@@ -682,14 +682,14 @@ SUBSYSTEM_DEF(job)
permitted = FALSE
if(G.donoritem && !G.donator_ckey_check(the_mob.client.ckey))
permitted = FALSE
- if(!equipbackpackstuff && G.category == SLOT_IN_BACKPACK)//snowflake check since plopping stuff in the backpack doesnt work for pre-job equip loadout stuffs
+ if(!equipbackpackstuff && G.slot == SLOT_IN_BACKPACK)//snowflake check since plopping stuff in the backpack doesnt work for pre-job equip loadout stuffs
permitted = FALSE
- if(equipbackpackstuff && G.category != SLOT_IN_BACKPACK)//ditto
+ if(equipbackpackstuff && G.slot != SLOT_IN_BACKPACK)//ditto
permitted = FALSE
if(!permitted)
continue
var/obj/item/I = new G.path
- if(!M.equip_to_slot_if_possible(I, G.category, disable_warning = TRUE, bypass_equip_delay_self = TRUE)) // If the job's dresscode compliant, try to put it in its slot, first
+ if(!M.equip_to_slot_if_possible(I, G.slot, disable_warning = TRUE, bypass_equip_delay_self = TRUE)) // If the job's dresscode compliant, try to put it in its slot, first
if(iscarbon(M))
var/mob/living/carbon/C = M
var/obj/item/storage/backpack/B = C.back
diff --git a/code/controllers/subsystem/mapping.dm b/code/controllers/subsystem/mapping.dm
index 97addfa131..617004646a 100644
--- a/code/controllers/subsystem/mapping.dm
+++ b/code/controllers/subsystem/mapping.dm
@@ -113,7 +113,7 @@ SUBSYSTEM_DEF(mapping)
// needs to be whitelisted for underground too so place_below ruins work
seedRuins(ice_ruins, CONFIG_GET(number/icemoon_budget), list(/area/icemoon/surface/outdoors/unexplored, /area/icemoon/underground/unexplored), ice_ruins_templates)
for (var/ice_z in ice_ruins)
- spawn_rivers(ice_z, 4, /turf/open/openspace/icemoon, /area/icemoon/surface/outdoors/unexplored/rivers)
+ spawn_rivers(ice_z, 4, /turf/open/transparent/openspace/icemoon, /area/icemoon/surface/outdoors/unexplored/rivers)
var/list/ice_ruins_underground = levels_by_trait(ZTRAIT_ICE_RUINS_UNDERGROUND)
if (ice_ruins_underground.len)
diff --git a/code/controllers/subsystem/overlays.dm b/code/controllers/subsystem/overlays.dm
index 20eb2af001..ea0f82436c 100644
--- a/code/controllers/subsystem/overlays.dm
+++ b/code/controllers/subsystem/overlays.dm
@@ -114,67 +114,47 @@ SUBSYSTEM_DEF(overlays)
#define NOT_QUEUED_ALREADY (!(flags_1 & OVERLAY_QUEUED_1))
#define QUEUE_FOR_COMPILE flags_1 |= OVERLAY_QUEUED_1; SSoverlays.queue += src;
-/atom/proc/cut_overlays(priority = FALSE)
- LAZYINITLIST(priority_overlays)
+/atom/proc/cut_overlays()
LAZYINITLIST(remove_overlays)
LAZYINITLIST(add_overlays)
remove_overlays = overlays.Copy()
add_overlays.Cut()
- if(priority)
- priority_overlays.Cut()
-
//If not already queued for work and there are overlays to remove
if(NOT_QUEUED_ALREADY && remove_overlays.len)
QUEUE_FOR_COMPILE
-/atom/proc/cut_overlay(list/overlays, priority)
+/atom/proc/cut_overlay(list/overlays)
if(!overlays)
return
overlays = build_appearance_list(overlays)
LAZYINITLIST(add_overlays) //always initialized after this point
- LAZYINITLIST(priority_overlays)
LAZYINITLIST(remove_overlays)
var/a_len = add_overlays.len
var/r_len = remove_overlays.len
- var/p_len = priority_overlays.len
remove_overlays += overlays
add_overlays -= overlays
-
- if(priority)
- var/list/cached_priority = priority_overlays
- LAZYREMOVE(cached_priority, overlays)
-
var/fa_len = add_overlays.len
var/fr_len = remove_overlays.len
- var/fp_len = priority_overlays.len
//If not already queued and there is work to be done
- if(NOT_QUEUED_ALREADY && (fa_len != a_len || fr_len != r_len || fp_len != p_len))
+ if(NOT_QUEUED_ALREADY && (fa_len != a_len || fr_len != r_len))
QUEUE_FOR_COMPILE
-/atom/proc/add_overlay(list/overlays, priority = FALSE)
+/atom/proc/add_overlay(list/overlays)
if(!overlays)
return
overlays = build_appearance_list(overlays)
LAZYINITLIST(add_overlays) //always initialized after this point
- LAZYINITLIST(priority_overlays)
var/a_len = add_overlays.len
- var/p_len = priority_overlays.len
- if(priority)
- priority_overlays += overlays //or in the image. Can we use [image] = image?
- var/fp_len = priority_overlays.len
- if(NOT_QUEUED_ALREADY && fp_len != p_len)
- QUEUE_FOR_COMPILE
- else
- add_overlays += overlays
- var/fa_len = add_overlays.len
- if(NOT_QUEUED_ALREADY && fa_len != a_len)
- QUEUE_FOR_COMPILE
+ add_overlays += overlays
+ var/fa_len = add_overlays.len
+ if(NOT_QUEUED_ALREADY && fa_len != a_len)
+ QUEUE_FOR_COMPILE
/atom/proc/copy_overlays(atom/other, cut_old) //copys our_overlays from another atom
if(!other)
diff --git a/code/controllers/subsystem/persistence.dm b/code/controllers/subsystem/persistence.dm
index b4a162c982..e1b6e6f6e2 100644
--- a/code/controllers/subsystem/persistence.dm
+++ b/code/controllers/subsystem/persistence.dm
@@ -48,25 +48,6 @@ SUBSYSTEM_DEF(persistence)
/datum/controller/subsystem/persistence/proc/LoadSatchels()
var/placed_satchel = 0
var/path
- if(fexists("data/npc_saves/SecretSatchels.sav")) //legacy conversion. Will only ever run once.
- var/savefile/secret_satchels = new /savefile("data/npc_saves/SecretSatchels.sav")
- for(var/map in secret_satchels)
- var/json_file = file("data/npc_saves/SecretSatchels[map].json")
- var/list/legacy_secret_satchels = splittext(secret_satchels[map],"#")
- var/list/satchels = list()
- for(var/i=1,i<=legacy_secret_satchels.len,i++)
- var/satchel_string = legacy_secret_satchels[i]
- var/list/chosen_satchel = splittext(satchel_string,"|")
- if(chosen_satchel.len == 3)
- var/list/data = list()
- data["x"] = text2num(chosen_satchel[1])
- data["y"] = text2num(chosen_satchel[2])
- data["saved_obj"] = chosen_satchel[3]
- satchels += list(data)
- var/list/file_data = list()
- file_data["data"] = satchels
- WRITE_FILE(json_file, json_encode(file_data))
- fdel("data/npc_saves/SecretSatchels.sav")
var/json_file = file("data/npc_saves/SecretSatchels[SSmapping.config.map_name].json")
var/list/json = list()
@@ -561,8 +542,8 @@ SUBSYSTEM_DEF(persistence)
ending_human.client.prefs.scars_list["[ending_human.client.prefs.scars_index]"] = ""
else
for(var/k in ending_human.all_wounds)
- var/datum/wound/W = k
- W.remove_wound() // so we can get the scars for open wounds
+ var/datum/wound/iter_wound = k
+ iter_wound.remove_wound() // so we can get the scars for open wounds
if(!ending_human.client)
return
ending_human.client.prefs.scars_list["[ending_human.client.prefs.scars_index]"] = ending_human.format_scars()
diff --git a/code/controllers/subsystem/ping.dm b/code/controllers/subsystem/ping.dm
deleted file mode 100644
index c5c9bb3933..0000000000
--- a/code/controllers/subsystem/ping.dm
+++ /dev/null
@@ -1,33 +0,0 @@
-SUBSYSTEM_DEF(ping)
- name = "Ping"
- priority = FIRE_PRIORITY_PING
- wait = 3 SECONDS
- flags = SS_NO_INIT
- runlevels = RUNLEVEL_LOBBY | RUNLEVEL_SETUP | RUNLEVEL_GAME | RUNLEVEL_POSTGAME
-
- var/list/currentrun = list()
-
-/datum/controller/subsystem/ping/stat_entry()
- ..("P:[GLOB.clients.len]")
-
-
-/datum/controller/subsystem/ping/fire(resumed = 0)
- if (!resumed)
- src.currentrun = GLOB.clients.Copy()
-
- //cache for sanic speed (lists are references anyways)
- var/list/currentrun = src.currentrun
-
- while (currentrun.len)
- var/client/C = currentrun[currentrun.len]
- currentrun.len--
-
- if (!C || !C.chatOutput || !C.chatOutput.loaded)
- if (MC_TICK_CHECK)
- return
- continue
-
- // softPang isn't handled anywhere but it'll always reset the opts.lastPang.
- C.chatOutput.ehjax_send(data = C.is_afk(29) ? "softPang" : "pang")
- if (MC_TICK_CHECK)
- return
diff --git a/code/controllers/subsystem/processing/circuit.dm b/code/controllers/subsystem/processing/circuit.dm
index 880e7a16a8..ac720a7fb4 100644
--- a/code/controllers/subsystem/processing/circuit.dm
+++ b/code/controllers/subsystem/processing/circuit.dm
@@ -50,20 +50,17 @@ PROCESSING_SUBSYSTEM_DEF(circuit)
/obj/item/electronic_assembly/simple,
/obj/item/electronic_assembly/hook,
/obj/item/electronic_assembly/pda,
- /obj/item/electronic_assembly/dildo,
/obj/item/electronic_assembly/small/default,
/obj/item/electronic_assembly/small/cylinder,
/obj/item/electronic_assembly/small/scanner,
/obj/item/electronic_assembly/small/hook,
/obj/item/electronic_assembly/small/box,
- /obj/item/electronic_assembly/small/dildo,
/obj/item/electronic_assembly/medium/default,
/obj/item/electronic_assembly/medium/box,
/obj/item/electronic_assembly/medium/clam,
/obj/item/electronic_assembly/medium/medical,
/obj/item/electronic_assembly/medium/gun,
/obj/item/electronic_assembly/medium/radio,
- /obj/item/electronic_assembly/medium/dildo,
/obj/item/electronic_assembly/large/default,
/obj/item/electronic_assembly/large/scope,
/obj/item/electronic_assembly/large/terminal,
diff --git a/code/controllers/subsystem/processing/instruments.dm b/code/controllers/subsystem/processing/instruments.dm
index a4e0d7703f..ee0fd1ea00 100644
--- a/code/controllers/subsystem/processing/instruments.dm
+++ b/code/controllers/subsystem/processing/instruments.dm
@@ -4,16 +4,26 @@ PROCESSING_SUBSYSTEM_DEF(instruments)
init_order = INIT_ORDER_INSTRUMENTS
flags = SS_KEEP_TIMING
priority = FIRE_PRIORITY_INSTRUMENTS
- var/static/list/datum/instrument/instrument_data = list() //id = datum
+ /// List of all instrument data, associative id = datum
+ var/static/list/datum/instrument/instrument_data = list()
+ /// List of all song datums.
var/static/list/datum/song/songs = list()
+ /// Max lines in songs
var/static/musician_maxlines = 600
+ /// Max characters per line in songs
var/static/musician_maxlinechars = 300
+ /// Deciseconds between hearchecks. Too high and instruments seem to lag when people are moving around in terms of who can hear it. Too low and the server lags from this.
var/static/musician_hearcheck_mindelay = 5
+ /// Maximum instrument channels total instruments are allowed to use. This is so you don't have instruments deadlocking all sound channels.
var/static/max_instrument_channels = MAX_INSTRUMENT_CHANNELS
+ /// Current number of channels allocated for instruments
var/static/current_instrument_channels = 0
+ /// Single cached list for synthesizer instrument ids, so you don't have to have a new list with every synthesizer.
+ var/static/list/synthesizer_instrument_ids
/datum/controller/subsystem/processing/instruments/Initialize()
initialize_instrument_data()
+ synthesizer_instrument_ids = get_allowed_instrument_ids()
return ..()
/datum/controller/subsystem/processing/instruments/proc/on_song_new(datum/song/S)
@@ -29,7 +39,10 @@ PROCESSING_SUBSYSTEM_DEF(instruments)
continue
I = new path
I.Initialize()
- instrument_data[I.id || "[I.type]"] = I
+ if(!I.id)
+ qdel(I)
+ continue
+ instrument_data[I.id] = I
CHECK_TICK
/datum/controller/subsystem/processing/instruments/proc/get_instrument(id_or_path)
diff --git a/code/controllers/subsystem/research.dm b/code/controllers/subsystem/research.dm
index 60c5dd5b99..ac9db60ccc 100644
--- a/code/controllers/subsystem/research.dm
+++ b/code/controllers/subsystem/research.dm
@@ -294,6 +294,17 @@ SUBSYSTEM_DEF(research)
//[88nodes * 5000points/node] / [1.5hr * 90min/hr * 60s/min]
//Around 450000 points max???
+ /// The global list of raw anomaly types that have been refined, for hard limits.
+ var/list/created_anomaly_types = list()
+ /// The hard limits of cores created for each anomaly type. For faster code lookup without switch statements.
+ var/list/anomaly_hard_limit_by_type = list(
+ ANOMALY_CORE_BLUESPACE = MAX_CORES_BLUESPACE,
+ ANOMALY_CORE_PYRO = MAX_CORES_PYRO,
+ ANOMALY_CORE_GRAVITATIONAL = MAX_CORES_GRAVITATIONAL,
+ ANOMALY_CORE_VORTEX = MAX_CORES_VORTEX,
+ ANOMALY_CORE_FLUX = MAX_CORES_FLUX
+ )
+
/datum/controller/subsystem/research/Initialize()
point_types = TECHWEB_POINT_TYPE_LIST_ASSOCIATIVE_NAMES
initialize_all_techweb_designs()
diff --git a/code/controllers/subsystem/server_maint.dm b/code/controllers/subsystem/server_maint.dm
index b77c78c4bb..2427fbd277 100644
--- a/code/controllers/subsystem/server_maint.dm
+++ b/code/controllers/subsystem/server_maint.dm
@@ -76,9 +76,7 @@ SUBSYSTEM_DEF(server_maint)
if(!thing)
continue
var/client/C = thing
- var/datum/chatOutput/co = C.chatOutput
- if(co)
- co.ehjax_send(data = "roundrestart")
+ C?.tgui_panel?.send_roundrestart()
if(server) //if you set a server location in config.txt, it sends you there instead of trying to reconnect to the same world address. -- NeoFite
C << link("byond://[server]")
var/datum/tgs_version/tgsversion = world.TgsVersion()
diff --git a/code/controllers/subsystem/shuttle.dm b/code/controllers/subsystem/shuttle.dm
index 9bda1cd233..c802448536 100644
--- a/code/controllers/subsystem/shuttle.dm
+++ b/code/controllers/subsystem/shuttle.dm
@@ -775,12 +775,14 @@ SUBSYSTEM_DEF(shuttle)
preview_shuttle = null
-/datum/controller/subsystem/shuttle/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.admin_state)
- ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
- if(!ui)
- ui = new(user, src, ui_key, "ShuttleManipulator", name, 800, 600, master_ui, state)
- ui.open()
+/datum/controller/subsystem/shuttle/ui_state(mob/user)
+ return GLOB.admin_state
+/datum/controller/subsystem/shuttle/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "ShuttleManipulator")
+ ui.open()
/datum/controller/subsystem/shuttle/ui_data(mob/user)
var/list/data = list()
diff --git a/code/controllers/subsystem/tgui.dm b/code/controllers/subsystem/tgui.dm
index 58bc28fa2f..a5526d2c03 100644
--- a/code/controllers/subsystem/tgui.dm
+++ b/code/controllers/subsystem/tgui.dm
@@ -1,3 +1,12 @@
+/**
+ * tgui subsystem
+ *
+ * Contains all tgui state and subsystem code.
+ *
+ * Copyright (c) 2020 Aleksej Komarov
+ * SPDX-License-Identifier: MIT
+ */
+
SUBSYSTEM_DEF(tgui)
name = "tgui"
wait = 9
@@ -5,33 +14,338 @@ SUBSYSTEM_DEF(tgui)
priority = FIRE_PRIORITY_TGUI
runlevels = RUNLEVEL_LOBBY | RUNLEVELS_DEFAULT
- var/list/currentrun = list()
- var/list/open_uis = list() // A list of open UIs, grouped by src_object and ui_key.
- var/list/processing_uis = list() // A list of processing UIs, ungrouped.
- var/basehtml // The HTML base used for all UIs.
+ /// A list of UIs scheduled to process
+ var/list/current_run = list()
+ /// A list of open UIs
+ var/list/open_uis = list()
+ /// A list of open UIs, grouped by src_object.
+ var/list/open_uis_by_src = list()
+ /// The HTML base used for all UIs.
+ var/basehtml
/datum/controller/subsystem/tgui/PreInit()
- basehtml = file2text('tgui/packages/tgui/public/tgui.html')
+ basehtml = file2text('tgui/packages/tgui/public/tgui.html')
/datum/controller/subsystem/tgui/Shutdown()
close_all_uis()
/datum/controller/subsystem/tgui/stat_entry()
- ..("P:[processing_uis.len]")
+ ..("P:[open_uis.len]")
/datum/controller/subsystem/tgui/fire(resumed = 0)
- if (!resumed)
- src.currentrun = processing_uis.Copy()
- //cache for sanic speed (lists are references anyways)
- var/list/currentrun = src.currentrun
-
- while(currentrun.len)
- var/datum/tgui/ui = currentrun[currentrun.len]
- currentrun.len--
+ if(!resumed)
+ src.current_run = open_uis.Copy()
+ // Cache for sanic speed (lists are references anyways)
+ var/list/current_run = src.current_run
+ while(current_run.len)
+ var/datum/tgui/ui = current_run[current_run.len]
+ current_run.len--
+ // TODO: Move user/src_object check to process()
if(ui && ui.user && ui.src_object)
ui.process()
else
- processing_uis.Remove(ui)
- if (MC_TICK_CHECK)
+ open_uis.Remove(ui)
+ if(MC_TICK_CHECK)
return
+/**
+ * public
+ *
+ * Requests a usable tgui window from the pool.
+ * Returns null if pool was exhausted.
+ *
+ * required user mob
+ * return datum/tgui
+ */
+/datum/controller/subsystem/tgui/proc/request_pooled_window(mob/user)
+ if(!user.client)
+ return null
+ var/list/windows = user.client.tgui_windows
+ var/window_id
+ var/datum/tgui_window/window
+ var/window_found = FALSE
+ // Find a usable window
+ for(var/i in 1 to TGUI_WINDOW_HARD_LIMIT)
+ window_id = TGUI_WINDOW_ID(i)
+ window = windows[window_id]
+ // As we are looping, create missing window datums
+ if(!window)
+ window = new(user.client, window_id, pooled = TRUE)
+ // Skip windows with acquired locks
+ if(window.locked)
+ continue
+ if(window.status == TGUI_WINDOW_READY)
+ return window
+ if(window.status == TGUI_WINDOW_CLOSED)
+ window.status = TGUI_WINDOW_LOADING
+ window_found = TRUE
+ break
+ if(!window_found)
+ log_tgui(user, "Error: Pool exhausted")
+ return null
+ return window
+
+/**
+ * public
+ *
+ * Force closes all tgui windows.
+ *
+ * required user mob
+ */
+/datum/controller/subsystem/tgui/proc/force_close_all_windows(mob/user)
+ log_tgui(user, "force_close_all_windows")
+ if(user.client)
+ user.client.tgui_windows = list()
+ for(var/i in 1 to TGUI_WINDOW_HARD_LIMIT)
+ var/window_id = TGUI_WINDOW_ID(i)
+ user << browse(null, "window=[window_id]")
+
+/**
+ * public
+ *
+ * Force closes the tgui window by window_id.
+ *
+ * required user mob
+ * required window_id string
+ */
+/datum/controller/subsystem/tgui/proc/force_close_window(mob/user, window_id)
+ log_tgui(user, "force_close_window")
+ // Close all tgui datums based on window_id.
+ for(var/datum/tgui/ui in user.tgui_open_uis)
+ if(ui.window && ui.window.id == window_id)
+ ui.close(can_be_suspended = FALSE)
+ // Unset machine just to be sure.
+ user.unset_machine()
+ // Close window directly just to be sure.
+ user << browse(null, "window=[window_id]")
+
+/**
+ * public
+ *
+ * Try to find an instance of a UI, and push an update to it.
+ *
+ * required user mob The mob who opened/is using the UI.
+ * required src_object datum The object/datum which owns the UI.
+ * optional ui datum/tgui The UI to be updated, if it exists.
+ * optional force_open bool If the UI should be re-opened instead of updated.
+ *
+ * return datum/tgui The found UI.
+ */
+/datum/controller/subsystem/tgui/proc/try_update_ui(
+ mob/user,
+ datum/src_object,
+ datum/tgui/ui)
+ // Look up a UI if it wasn't passed
+ if(isnull(ui))
+ ui = get_open_ui(user, src_object)
+ // Couldn't find a UI.
+ if(isnull(ui))
+ return null
+ ui.process_status()
+ // UI ended up with the closed status
+ // or is actively trying to close itself.
+ // FIXME: Doesn't actually fix the paper bug.
+ if(ui.status <= UI_CLOSE)
+ ui.close()
+ return null
+ ui.send_update()
+ return ui
+
+/**
+ * public
+ *
+ * Get a open UI given a user and src_object.
+ *
+ * required user mob The mob who opened/is using the UI.
+ * required src_object datum The object/datum which owns the UI.
+ *
+ * return datum/tgui The found UI.
+ */
+/datum/controller/subsystem/tgui/proc/get_open_ui(mob/user, datum/src_object)
+ var/key = "[REF(src_object)]"
+ // No UIs opened for this src_object
+ if(isnull(open_uis_by_src[key]) || !istype(open_uis_by_src[key], /list))
+ return null
+ for(var/datum/tgui/ui in open_uis_by_src[key])
+ // Make sure we have the right user
+ if(ui.user == user)
+ return ui
+ return null
+
+/**
+ * public
+ *
+ * Update all UIs attached to src_object.
+ *
+ * required src_object datum The object/datum which owns the UIs.
+ *
+ * return int The number of UIs updated.
+ */
+/datum/controller/subsystem/tgui/proc/update_uis(datum/src_object)
+ var/count = 0
+ var/key = "[REF(src_object)]"
+ // No UIs opened for this src_object
+ if(isnull(open_uis_by_src[key]) || !istype(open_uis_by_src[key], /list))
+ return count
+ for(var/datum/tgui/ui in open_uis_by_src[key])
+ // Check if UI is valid.
+ if(ui && ui.src_object && ui.user && ui.src_object.ui_host(ui.user))
+ ui.process(force = 1)
+ count++
+ return count
+
+/**
+ * public
+ *
+ * Close all UIs attached to src_object.
+ *
+ * required src_object datum The object/datum which owns the UIs.
+ *
+ * return int The number of UIs closed.
+ */
+/datum/controller/subsystem/tgui/proc/close_uis(datum/src_object)
+ var/count = 0
+ var/key = "[REF(src_object)]"
+ // No UIs opened for this src_object
+ if(isnull(open_uis_by_src[key]) || !istype(open_uis_by_src[key], /list))
+ return count
+ for(var/datum/tgui/ui in open_uis_by_src[key])
+ // Check if UI is valid.
+ if(ui && ui.src_object && ui.user && ui.src_object.ui_host(ui.user))
+ ui.close()
+ count++
+ return count
+
+/**
+ * public
+ *
+ * Close all UIs regardless of their attachment to src_object.
+ *
+ * return int The number of UIs closed.
+ */
+/datum/controller/subsystem/tgui/proc/close_all_uis()
+ var/count = 0
+ for(var/key in open_uis_by_src)
+ for(var/datum/tgui/ui in open_uis_by_src[key])
+ // Check if UI is valid.
+ if(ui && ui.src_object && ui.user && ui.src_object.ui_host(ui.user))
+ ui.close()
+ count++
+ return count
+
+/**
+ * public
+ *
+ * Update all UIs belonging to a user.
+ *
+ * required user mob The mob who opened/is using the UI.
+ * optional src_object datum If provided, only update UIs belonging this src_object.
+ *
+ * return int The number of UIs updated.
+ */
+/datum/controller/subsystem/tgui/proc/update_user_uis(mob/user, datum/src_object)
+ var/count = 0
+ if(length(user?.tgui_open_uis) == 0)
+ return count
+ for(var/datum/tgui/ui in user.tgui_open_uis)
+ if(isnull(src_object) || ui.src_object == src_object)
+ ui.process(force = 1)
+ count++
+ return count
+
+/**
+ * public
+ *
+ * Close all UIs belonging to a user.
+ *
+ * required user mob The mob who opened/is using the UI.
+ * optional src_object datum If provided, only close UIs belonging this src_object.
+ *
+ * return int The number of UIs closed.
+ */
+/datum/controller/subsystem/tgui/proc/close_user_uis(mob/user, datum/src_object)
+ var/count = 0
+ if(length(user?.tgui_open_uis) == 0)
+ return count
+ for(var/datum/tgui/ui in user.tgui_open_uis)
+ if(isnull(src_object) || ui.src_object == src_object)
+ ui.close()
+ count++
+ return count
+
+/**
+ * private
+ *
+ * Add a UI to the list of open UIs.
+ *
+ * required ui datum/tgui The UI to be added.
+ */
+/datum/controller/subsystem/tgui/proc/on_open(datum/tgui/ui)
+ var/key = "[REF(ui.src_object)]"
+ if(isnull(open_uis_by_src[key]) || !istype(open_uis_by_src[key], /list))
+ open_uis_by_src[key] = list()
+ ui.user.tgui_open_uis |= ui
+ var/list/uis = open_uis_by_src[key]
+ uis |= ui
+ open_uis |= ui
+
+/**
+ * private
+ *
+ * Remove a UI from the list of open UIs.
+ *
+ * required ui datum/tgui The UI to be removed.
+ *
+ * return bool If the UI was removed or not.
+ */
+/datum/controller/subsystem/tgui/proc/on_close(datum/tgui/ui)
+ var/key = "[REF(ui.src_object)]"
+ if(isnull(open_uis_by_src[key]) || !istype(open_uis_by_src[key], /list))
+ return FALSE
+ // Remove it from the list of processing UIs.
+ open_uis.Remove(ui)
+ // If the user exists, remove it from them too.
+ if(ui.user)
+ ui.user.tgui_open_uis.Remove(ui)
+ var/list/uis = open_uis_by_src[key]
+ uis.Remove(ui)
+ if(length(uis) == 0)
+ open_uis_by_src.Remove(key)
+ return TRUE
+
+/**
+ * private
+ *
+ * Handle client logout, by closing all their UIs.
+ *
+ * required user mob The mob which logged out.
+ *
+ * return int The number of UIs closed.
+ */
+/datum/controller/subsystem/tgui/proc/on_logout(mob/user)
+ close_user_uis(user)
+
+/**
+ * private
+ *
+ * Handle clients switching mobs, by transferring their UIs.
+ *
+ * required user source The client's original mob.
+ * required user target The client's new mob.
+ *
+ * return bool If the UIs were transferred.
+ */
+/datum/controller/subsystem/tgui/proc/on_transfer(mob/source, mob/target)
+ // The old mob had no open UIs.
+ if(length(source?.tgui_open_uis) == 0)
+ return FALSE
+ if(isnull(target.tgui_open_uis) || !istype(target.tgui_open_uis, /list))
+ target.tgui_open_uis = list()
+ // Transfer all the UIs.
+ for(var/datum/tgui/ui in source.tgui_open_uis)
+ // Inform the UIs of their new owner.
+ ui.user = target
+ target.tgui_open_uis.Add(ui)
+ // Clear the old list.
+ source.tgui_open_uis.Cut()
+ return TRUE
diff --git a/code/controllers/subsystem/ticker.dm b/code/controllers/subsystem/ticker.dm
index 765f0c8382..9af86653f4 100755
--- a/code/controllers/subsystem/ticker.dm
+++ b/code/controllers/subsystem/ticker.dm
@@ -396,6 +396,7 @@ SUBSYSTEM_DEF(ticker)
SSjob.EquipRank(N, player.mind.assigned_role, 0)
if(CONFIG_GET(flag/roundstart_traits) && ishuman(N.new_character))
SSquirks.AssignQuirks(N.new_character, N.client, TRUE, TRUE, SSjob.GetJob(player.mind.assigned_role), FALSE, N)
+ N.client.prefs.post_copy_to(player)
CHECK_TICK
if(captainless)
for(var/mob/dead/new_player/N in GLOB.player_list)
@@ -711,7 +712,8 @@ SUBSYSTEM_DEF(ticker)
'sound/roundend/yeehaw.ogg',
'sound/roundend/disappointed.ogg',
'sound/roundend/gondolabridge.ogg',
- 'sound/roundend/haveabeautifultime.ogg'\
+ 'sound/roundend/haveabeautifultime.ogg',
+ 'sound/roundend/CitadelStationHasSeenBetterDays.ogg'\
)
SEND_SOUND(world, sound(round_end_sound))
diff --git a/code/datums/accents.dm b/code/datums/accents.dm
new file mode 100644
index 0000000000..ce32aa0884
--- /dev/null
+++ b/code/datums/accents.dm
@@ -0,0 +1,106 @@
+/datum/accent
+
+/datum/accent/proc/modify_speech(list/speech_args, datum/source, mob/living/carbon/owner) //transforms the message in some way
+ return speech_args
+
+/datum/accent/lizard/modify_speech(list/speech_args)
+ var/message = speech_args[SPEECH_MESSAGE]
+ var/static/regex/lizard_hiss = new("s+", "g")
+ var/static/regex/lizard_hiSS = new("S+", "g")
+ if(message[1] != "*")
+ message = lizard_hiss.Replace(message, "sss")
+ message = lizard_hiSS.Replace(message, "SSS")
+ speech_args[SPEECH_MESSAGE] = message
+ return speech_args
+
+/datum/accent/fly/modify_speech(list/speech_args)
+ var/message = speech_args[SPEECH_MESSAGE]
+ var/static/regex/fly_buzz = new("z+", "g")
+ var/static/regex/fly_buZZ = new("Z+", "g")
+ if(message[1] != "*")
+ message = fly_buzz.Replace(message, "zzz")
+ message = fly_buZZ.Replace(message, "ZZZ")
+ speech_args[SPEECH_MESSAGE] = message
+ return speech_args
+
+/datum/accent/abductor/modify_speech(list/speech_args, datum/source)
+ var/message = speech_args[SPEECH_MESSAGE]
+ var/mob/living/carbon/human/user = source
+ var/rendered = "[user.name]: [message]"
+ user.log_talk(message, LOG_SAY, tag="abductor")
+ for(var/mob/living/carbon/human/H in GLOB.alive_mob_list)
+ var/obj/item/organ/tongue/T = H.getorganslot(ORGAN_SLOT_TONGUE)
+ if(!T || T.type != type)
+ continue
+ if(H.dna && H.dna.species.id == "abductor" && user.dna && user.dna.species.id == "abductor")
+ var/datum/antagonist/abductor/A = user.mind.has_antag_datum(/datum/antagonist/abductor)
+ if(!A || !(H.mind in A.team.members))
+ continue
+ to_chat(H, rendered)
+ for(var/mob/M in GLOB.dead_mob_list)
+ var/link = FOLLOW_LINK(M, user)
+ to_chat(M, "[link] [rendered]")
+ speech_args[SPEECH_MESSAGE] = ""
+ return speech_args
+
+/datum/accent/zombie/modify_speech(list/speech_args)
+ var/message = speech_args[SPEECH_MESSAGE]
+ var/list/message_list = splittext(message, " ")
+ var/maxchanges = max(round(message_list.len / 1.5), 2)
+
+ for(var/i = rand(maxchanges / 2, maxchanges), i > 0, i--)
+ var/insertpos = rand(1, message_list.len - 1)
+ var/inserttext = message_list[insertpos]
+
+ if(!(copytext(inserttext, -3) == "..."))//3 == length("...")
+ message_list[insertpos] = inserttext + "..."
+
+ if(prob(20) && message_list.len > 3)
+ message_list.Insert(insertpos, "[pick("BRAINS", "Brains", "Braaaiinnnsss", "BRAAAIIINNSSS")]...")
+
+ speech_args[SPEECH_MESSAGE] = jointext(message_list, " ")
+ return speech_args
+
+/datum/accent/alien/modify_speech(list/speech_args, datum/source)
+ playsound(source, "hiss", 25, 1, 1)
+ return speech_args
+
+/datum/accent/fluffy/modify_speech(list/speech_args)
+ var/message = speech_args[SPEECH_MESSAGE]
+ if(message[1] != "*")
+ message = replacetext(message, "ne", "nye")
+ message = replacetext(message, "nu", "nyu")
+ message = replacetext(message, "na", "nya")
+ message = replacetext(message, "no", "nyo")
+ message = replacetext(message, "ove", "uv")
+ message = replacetext(message, "l", "w")
+ message = replacetext(message, "r", "w")
+ speech_args[SPEECH_MESSAGE] = lowertext(message)
+ return speech_args
+
+/datum/accent/span
+ var/span_flag
+
+/datum/accent/span/modify_speech(list/speech_args)
+ speech_args[SPEECH_SPANS] |= span_flag
+ return speech_args
+
+//bone tongues either have the sans accent or the papyrus accent
+/datum/accent/span/sans
+ span_flag = SPAN_SANS
+
+/datum/accent/span/papyrus
+ span_flag = SPAN_PAPYRUS
+
+/datum/accent/span/robot
+ span_flag = SPAN_ROBOT
+
+/datum/accent/dullahan/modify_speech(list/speech_args, datum/source, mob/living/carbon/owner)
+ if(owner)
+ if(isdullahan(owner))
+ var/datum/species/dullahan/D = owner.dna.species
+ if(isobj(D.myhead.loc))
+ var/obj/O = D.myhead.loc
+ O.say(speech_args[SPEECH_MESSAGE])
+ speech_args[SPEECH_MESSAGE] = ""
+ return speech_args
diff --git a/code/datums/action.dm b/code/datums/action.dm
index 0033df09d7..fbf7487e4d 100644
--- a/code/datums/action.dm
+++ b/code/datums/action.dm
@@ -157,7 +157,7 @@
/datum/action/proc/ApplyIcon(obj/screen/movable/action_button/current_button, force = FALSE)
if(icon_icon && button_icon_state && ((current_button.button_icon_state != button_icon_state) || force))
- current_button.cut_overlays(TRUE)
+ current_button.cut_overlays()
current_button.add_overlay(mutable_appearance(icon_icon, button_icon_state))
current_button.button_icon_state = button_icon_state
diff --git a/code/datums/ai_laws.dm b/code/datums/ai_laws.dm
index 9c6bb2ebfd..15115b62c0 100644
--- a/code/datums/ai_laws.dm
+++ b/code/datums/ai_laws.dm
@@ -431,25 +431,31 @@
if(!owner)
owner = M
-/datum/ai_laws/proc/get_law_list(include_zeroth = 0, show_numbers = 1)
+/**
+ * Generates a list of all laws on this datum, including rendered HTML tags if required
+ *
+ * Arguments:
+ * * include_zeroth - Operator that controls if law 0 or law 666 is returned in the set
+ * * show_numbers - Operator that controls if law numbers are prepended to the returned laws
+ * * render_html - Operator controlling if HTML tags are rendered on the returned laws
+ */
+/datum/ai_laws/proc/get_law_list(include_zeroth = FALSE, show_numbers = TRUE, render_html = TRUE)
var/list/data = list()
- if (include_zeroth && devillaws && devillaws.len)
- for(var/i in devillaws)
- data += "[show_numbers ? "666:" : ""] [i]"
+ if (include_zeroth && devillaws)
+ for(var/law in devillaws)
+ data += "[show_numbers ? "666:" : ""] [render_html ? "[law]" : law]"
if (include_zeroth && zeroth)
- data += "[show_numbers ? "0:" : ""] [zeroth]"
+ data += "[show_numbers ? "0:" : ""] [render_html ? "[zeroth]" : zeroth]"
for(var/law in hacked)
if (length(law) > 0)
- var/num = ionnum()
- data += "[show_numbers ? "[num]:" : ""] [law]"
+ data += "[show_numbers ? "[ionnum()]:" : ""] [render_html ? "[law]" : law]"
for(var/law in ion)
if (length(law) > 0)
- var/num = ionnum()
- data += "[show_numbers ? "[num]:" : ""] [law]"
+ data += "[show_numbers ? "[ionnum()]:" : ""] [render_html ? "[law]" : law]"
var/number = 1
for(var/law in inherent)
@@ -459,6 +465,7 @@
for(var/law in supplied)
if (length(law) > 0)
- data += "[show_numbers ? "[number]:" : ""] [law]"
+ data += "[show_numbers ? "[number]:" : ""] [render_html ? "[law]" : law]"
number++
return data
+
diff --git a/code/datums/browser.dm b/code/datums/browser.dm
index 96b2a2a294..dbe60817bd 100644
--- a/code/datums/browser.dm
+++ b/code/datums/browser.dm
@@ -8,14 +8,14 @@
var/window_options = "can_close=1;can_minimize=1;can_maximize=0;can_resize=1;titlebar=1;" // window option is set using window_id
var/stylesheets[0]
var/scripts[0]
- var/title_image
var/head_elements
var/body_elements
var/head_content = ""
var/content = ""
+ var/static/datum/asset/simple/namespaced/common/common_asset = get_asset_datum(/datum/asset/simple/namespaced/common)
-/datum/browser/New(nuser, nwindow_id, ntitle = 0, nwidth = 0, nheight = 0, var/atom/nref = null)
+/datum/browser/New(nuser, nwindow_id, ntitle = 0, nwidth = 0, nheight = 0, atom/nref = null)
user = nuser
window_id = nwindow_id
@@ -27,7 +27,6 @@
height = nheight
if (nref)
ref = nref
- add_stylesheet("common", 'html/browser/common.css') // this CSS sheet is common to all UIs
/datum/browser/proc/add_head_content(nhead_content)
head_content = nhead_content
@@ -35,22 +34,21 @@
/datum/browser/proc/set_window_options(nwindow_options)
window_options = nwindow_options
-/datum/browser/proc/set_title_image(ntitle_image)
- //title_image = ntitle_image
-
/datum/browser/proc/add_stylesheet(name, file)
if(istype(name, /datum/asset/spritesheet))
var/datum/asset/spritesheet/sheet = name
stylesheets["spritesheet_[sheet.name].css"] = "data/spritesheets/[sheet.name]"
else
var/asset_name = "[name].css"
+
stylesheets[asset_name] = file
- if(!SSassets.cache[asset_name])
- register_asset(asset_name, file)
+
+ if (!SSassets.cache[asset_name])
+ SSassets.transport.register_asset(asset_name, file)
/datum/browser/proc/add_script(name, file)
scripts["[ckey(name)].js"] = file
- register_asset("[ckey(name)].js", file)
+ SSassets.transport.register_asset("[ckey(name)].js", file)
/datum/browser/proc/set_content(ncontent)
content = ncontent
@@ -60,15 +58,13 @@
/datum/browser/proc/get_header()
var/file
+ head_content += ""
for (file in stylesheets)
- head_content += ""
+ head_content += ""
+
for (file in scripts)
- head_content += ""
-
- var/title_attributes = "class='uiTitle'"
- if (title_image)
- title_attributes = "class='uiTitle icon' style='background-image: url([title_image]);'"
+ head_content += ""
return {"
@@ -79,7 +75,7 @@
- [title ? "
[title]
" : ""]
+ [title ? "
[title]
" : ""]
"}
//" This is here because else the rest of the file looks like a string in notepad++.
@@ -105,10 +101,11 @@
var/window_size = ""
if(width && height)
window_size = "size=[width]x[height];"
+ common_asset.send(user)
if(stylesheets.len)
- send_asset_list(user, stylesheets)
+ SSassets.transport.send_assets(user, stylesheets)
if(scripts.len)
- send_asset_list(user, scripts)
+ SSassets.transport.send_assets(user, scripts)
user << browse(get_content(), "window=[window_id];[window_size][window_options]")
if(use_onclose)
setup_onclose()
@@ -169,7 +166,7 @@
return Button3
//Same shit, but it returns the button number, could at some point support unlimited button amounts.
-/proc/askuser(var/mob/User,Message, Title, Button1="Ok", Button2, Button3, StealFocus = 1, Timeout = 6000)
+/proc/askuser(mob/User,Message, Title, Button1="Ok", Button2, Button3, StealFocus = 1, Timeout = 6000)
if (!istype(User))
if (istype(User, /client/))
var/client/C = User
@@ -188,7 +185,7 @@
var/selectedbutton = 0
var/stealfocus
-/datum/browser/modal/New(nuser, nwindow_id, ntitle = 0, nwidth = 0, nheight = 0, var/atom/nref = null, StealFocus = 1, Timeout = 6000)
+/datum/browser/modal/New(nuser, nwindow_id, ntitle = 0, nwidth = 0, nheight = 0, atom/nref = null, StealFocus = 1, Timeout = 6000)
..()
stealfocus = StealFocus
if (!StealFocus)
diff --git a/code/datums/components/butchering.dm b/code/datums/components/butchering.dm
index b5b8849155..4f56f58d23 100644
--- a/code/datums/components/butchering.dm
+++ b/code/datums/components/butchering.dm
@@ -67,7 +67,7 @@
H.apply_damage(source.force, BRUTE, BODY_ZONE_HEAD, wound_bonus=CANT_WOUND) // easy tiger, we'll get to that in a sec
var/obj/item/bodypart/slit_throat = H.get_bodypart(BODY_ZONE_HEAD)
if(slit_throat)
- var/datum/wound/brute/cut/critical/screaming_through_a_slit_throat = new
+ var/datum/wound/slash/critical/screaming_through_a_slit_throat = new
screaming_through_a_slit_throat.apply_wound(slit_throat)
H.apply_status_effect(/datum/status_effect/neck_slice)
diff --git a/code/datums/components/chasm.dm b/code/datums/components/chasm.dm
index 0bb6c4a0af..f5a34bfca2 100644
--- a/code/datums/components/chasm.dm
+++ b/code/datums/components/chasm.dm
@@ -76,19 +76,11 @@
return FALSE
if(M.is_flying())
return FALSE
- if(ishuman(AM))
- var/mob/living/carbon/human/H = AM
- if(istype(H.belt, /obj/item/wormhole_jaunter))
- var/obj/item/wormhole_jaunter/J = H.belt
- //To freak out any bystanders
- H.visible_message("[H] falls into [parent]!")
- J.chasm_react(H)
- return FALSE
return TRUE
/datum/component/chasm/proc/drop(atom/movable/AM)
//Make sure the item is still there after our sleep
- if(!AM || QDELETED(AM))
+ if(!AM || QDELETED(AM) || SEND_SIGNAL(AM, COMSIG_MOVABLE_CHASM_DROP, src))
return
falling_atoms[AM] = (falling_atoms[AM] || 0) + 1
var/turf/T = target_turf
diff --git a/code/datums/components/crafting/crafting.dm b/code/datums/components/crafting/crafting.dm
index b9a1b5ec3b..1a9f4c0512 100644
--- a/code/datums/components/crafting/crafting.dm
+++ b/code/datums/components/crafting/crafting.dm
@@ -203,9 +203,9 @@
Set var amt to the value current cycle req is pointing to, its amount of type we need to delete
Get var/surroundings list of things accessable to crafting by get_environment()
Check the type of the current cycle req
- If its reagent then do a while loop, inside it try to locate() reagent containers, inside such containers try to locate needed reagent, if there isnt remove thing from surroundings
+ If its reagent then do a while loop, inside it try to locate() reagent containers, inside such containers try to locate needed reagent, if there isn't remove thing from surroundings
If there is enough reagent in the search result then delete the needed amount, create the same type of reagent with the same data var and put it into deletion list
- If there isnt enough take all of that reagent from the container, put into deletion list, substract the amt var by the volume of reagent, remove the container from surroundings list and keep searching
+ If there isn't enough take all of that reagent from the container, put into deletion list, substract the amt var by the volume of reagent, remove the container from surroundings list and keep searching
While doing above stuff check deletion list if it already has such reagnet, if yes merge instead of adding second one
If its stack check if it has enough amount
If yes create new stack with the needed amount and put in into deletion list, substract taken amount from the stack
@@ -216,7 +216,7 @@
Then do a loop over parts var of the recipe
Do similar stuff to what we have done above, but now in deletion list, until the parts conditions are satisfied keep taking from the deletion list and putting it into parts list for return
- After its done loop over deletion list and delete all the shit that wasnt taken by parts loop
+ After its done loop over deletion list and delete all the shit that wasn't taken by parts loop
del_reqs return the list of parts resulting object will receive as argument of CheckParts proc, on the atom level it will add them all to the contents, on all other levels it calls ..() and does whatever is needed afterwards but from contents list already
*/
@@ -323,9 +323,12 @@
if(user == parent)
ui_interact(user)
+/datum/component/personal_crafting/ui_state(mob/user)
+ return GLOB.not_incapacitated_turf_state
+
//For the UI related things we're going to assume the user is a mob rather than typesetting it to an atom as the UI isn't generated if the parent is an atom
-/datum/component/personal_crafting/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.not_incapacitated_turf_state)
- ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+/datum/component/personal_crafting/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
cur_category = categories[1]
if(islist(categories[cur_category]))
@@ -333,7 +336,7 @@
cur_subcategory = subcats[1]
else
cur_subcategory = CAT_NONE
- ui = new(user, src, ui_key, "PersonalCrafting", "Crafting Menu", 700, 800, master_ui, state)
+ ui = new(user, src, "PersonalCrafting")
ui.open()
/datum/component/personal_crafting/ui_data(mob/user)
@@ -413,13 +416,8 @@
display_compact = !display_compact
. = TRUE
if("set_category")
- if(!isnull(params["category"]))
- cur_category = params["category"]
- if(!isnull(params["subcategory"]))
- if(params["subcategory"] == "0")
- cur_subcategory = ""
- else
- cur_subcategory = params["subcategory"]
+ cur_category = params["category"]
+ cur_subcategory = params["subcategory"] || ""
. = TRUE
/datum/component/personal_crafting/proc/build_recipe_data(datum/crafting_recipe/R)
diff --git a/code/datums/components/crafting/recipes/recipes_clothing.dm b/code/datums/components/crafting/recipes/recipes_clothing.dm
index 2a24c0b451..df0a2a091a 100644
--- a/code/datums/components/crafting/recipes/recipes_clothing.dm
+++ b/code/datums/components/crafting/recipes/recipes_clothing.dm
@@ -40,13 +40,23 @@
reqs = list(/obj/item/paper = 20)
category = CAT_CLOTHING
+/datum/crafting_recipe/balaclavabreath
+ name = "Breathaclava"
+ result = /obj/item/clothing/mask/balaclava/breath
+ time = 10
+ reqs = list(/obj/item/clothing/mask/balaclava = 1,
+ /obj/item/clothing/mask/breath = 1)
+ category = CAT_CLOTHING
+
+
+
/datum/crafting_recipe/armwraps
name = "Armwraps"
result = /obj/item/clothing/gloves/fingerless/pugilist
time = 60
tools = list(TOOL_WIRECUTTER)
reqs = list(/obj/item/stack/sheet/cloth = 4,
- /obj/item/stack/sheet/durathread = 2,
+ /obj/item/stack/sticky_tape = 2,
/obj/item/stack/sheet/leather = 2)
category = CAT_CLOTHING
@@ -263,6 +273,16 @@
time = 30
category = CAT_CLOTHING
+/datum/crafting_recipe/twinsheath
+ name = "Twin Sword Sheath"
+ result = /obj/item/storage/belt/sabre/twin
+ reqs = list(/obj/item/stack/sheet/mineral/wood = 3,
+ /obj/item/stack/sheet/leather = 8)
+ tools = list(TOOL_WIRECUTTER)
+ time = 70
+ category = CAT_CLOTHING
+
+
/datum/crafting_recipe/durathread_reinforcement_kit
name = "Durathread Reinforcement Kit"
result = /obj/item/armorkit
diff --git a/code/datums/components/crafting/recipes/recipes_misc.dm b/code/datums/components/crafting/recipes/recipes_misc.dm
index 214c1b1427..14987bac44 100644
--- a/code/datums/components/crafting/recipes/recipes_misc.dm
+++ b/code/datums/components/crafting/recipes/recipes_misc.dm
@@ -2,6 +2,15 @@
//Large Objects//
/////////////////
+/datum/crafting_recipe/plunger
+ name = "Plunger"
+ result = /obj/item/plunger
+ time = 1
+ reqs = list(/obj/item/stack/sheet/plastic = 1,
+ /obj/item/stack/sheet/mineral/wood = 1)
+ category = CAT_MISC
+ subcategory = CAT_TOOL
+
/datum/crafting_recipe/showercurtain
name = "Shower Curtains"
reqs = list(/obj/item/stack/sheet/cloth = 2,
@@ -111,6 +120,53 @@
category = CAT_MISC
always_availible = FALSE // Disabled til learned
+/datum/crafting_recipe/furnace
+ name = "Sandstone Furnace"
+ result = /obj/structure/furnace
+ time = 300
+ reqs = list(/obj/item/stack/sheet/mineral/sandstone = 15,
+ /obj/item/stack/sheet/metal = 4,
+ /obj/item/stack/rods = 2)
+ tools = list(TOOL_CROWBAR)
+ subcategory = CAT_MISCELLANEOUS
+ category = CAT_MISC
+
+/datum/crafting_recipe/tableanvil
+ name = "Table Anvil"
+ result = /obj/structure/anvil/obtainable/table
+ time = 300
+ reqs = list(/obj/item/stack/sheet/metal = 4,
+ /obj/item/stack/rods = 2)
+ tools = list(TOOL_SCREWDRIVER, TOOL_WRENCH, TOOL_WELDER)
+ subcategory = CAT_MISCELLANEOUS
+ category = CAT_MISC
+
+/datum/crafting_recipe/sandvil
+ name = "Sandstone Anvil"
+ result = /obj/structure/anvil/obtainable/sandstone
+ time = 300
+ reqs = list(/obj/item/stack/sheet/mineral/sandstone = 24)
+ tools = list(TOOL_CROWBAR)
+ subcategory = CAT_MISCELLANEOUS
+ category = CAT_MISC
+
+/datum/crafting_recipe/basaltblock
+ name = "Sintered Basalt Block"
+ result = /obj/item/basaltblock
+ time = 200
+ reqs = list(/obj/item/stack/ore/glass/basalt = 50)
+ tools = list(TOOL_WELDER)
+ subcategory = CAT_MISCELLANEOUS
+ category = CAT_MISC
+
+/datum/crafting_recipe/basaltanvil
+ name = "Basalt Anvil"
+ result = /obj/structure/anvil/obtainable/basalt
+ time = 200
+ reqs = list(/obj/item/basaltblock = 5)
+ tools = list(TOOL_CROWBAR)
+ subcategory = CAT_MISCELLANEOUS
+ category = CAT_MISC
///////////////////
//Tools & Storage//
///////////////////
@@ -126,7 +182,7 @@
/datum/crafting_recipe/brute_pack
name = "Suture Pack"
- result = /obj/item/stack/medical/suture/one
+ result = /obj/item/stack/medical/suture/five
time = 1
reqs = list(/obj/item/stack/medical/gauze = 1,
/datum/reagent/medicine/styptic_powder = 10)
@@ -135,7 +191,7 @@
/datum/crafting_recipe/burn_pack
name = "Regenerative Mesh"
- result = /obj/item/stack/medical/mesh/one
+ result = /obj/item/stack/medical/mesh/five
time = 1
reqs = list(/obj/item/stack/medical/gauze = 1,
/datum/reagent/medicine/silver_sulfadiazine = 10)
@@ -166,6 +222,17 @@
subcategory = CAT_TOOL
category = CAT_MISC
+/datum/crafting_recipe/toolboxhammer
+ name = "Toolbox Hammer"
+ result = /obj/item/melee/smith/hammer/toolbox
+ tools = list(TOOL_SCREWDRIVER, TOOL_WRENCH, TOOL_WELDER)
+ reqs = list(/obj/item/storage/toolbox = 1,
+ /obj/item/stack/sheet/metal = 4,
+ /obj/item/stack/rods = 2)
+ time = 40
+ subcategory = CAT_TOOL
+ category = CAT_MISC
+
/datum/crafting_recipe/papersack
name = "Paper Sack"
result = /obj/item/storage/box/papersack
@@ -188,7 +255,7 @@
result = /obj/item/screwdriver/bronze
reqs = list(/obj/item/screwdriver = 1,
/obj/item/stack/cable_coil = 10,
- /obj/item/stack/tile/bronze = 1,
+ /obj/item/stack/sheet/bronze = 1,
/datum/reagent/water = 15)
time = 40
subcategory = CAT_TOOL
@@ -200,7 +267,7 @@
result = /obj/item/weldingtool/bronze
reqs = list(/obj/item/weldingtool = 1,
/obj/item/stack/cable_coil = 10,
- /obj/item/stack/tile/bronze = 1,
+ /obj/item/stack/sheet/bronze = 1,
/datum/reagent/water = 15)
time = 40
subcategory = CAT_TOOL
@@ -212,7 +279,7 @@
result = /obj/item/wirecutters/bronze
reqs = list(/obj/item/wirecutters = 1,
/obj/item/stack/cable_coil = 10,
- /obj/item/stack/tile/bronze = 1,
+ /obj/item/stack/sheet/bronze = 1,
/datum/reagent/water = 15)
time = 40
subcategory = CAT_TOOL
@@ -224,7 +291,7 @@
result = /obj/item/crowbar/bronze
reqs = list(/obj/item/crowbar = 1,
/obj/item/stack/cable_coil = 10,
- /obj/item/stack/tile/bronze = 1,
+ /obj/item/stack/sheet/bronze = 1,
/datum/reagent/water = 15)
time = 40
subcategory = CAT_TOOL
@@ -236,7 +303,7 @@
result = /obj/item/wrench/bronze
reqs = list(/obj/item/wrench = 1,
/obj/item/stack/cable_coil = 10,
- /obj/item/stack/tile/bronze = 1,
+ /obj/item/stack/sheet/bronze = 1,
/datum/reagent/water = 15)
time = 40
subcategory = CAT_TOOL
@@ -269,6 +336,19 @@
subcategory = CAT_TOOL
category = CAT_MISC
+/datum/crafting_recipe/heretic/codex
+ name = "Codex Cicatrix"
+ result = /obj/item/forbidden_book
+ tools = list(/obj/item/pen)
+ reqs = list(/obj/item/paper = 5,
+ /obj/item/organ/eyes = 1,
+ /obj/item/organ/heart = 1,
+ /obj/item/stack/sheet/animalhide/human = 1)
+ time = 150
+ subcategory = CAT_MISCELLANEOUS
+ category = CAT_MISC
+ always_availible = FALSE
+
////////////
//Vehicles//
////////////
@@ -324,7 +404,7 @@
result = /obj/item/toy/sword/cx
subcategory = CAT_MISCELLANEOUS
category = CAT_MISC
-
+
/datum/crafting_recipe/catgirlplushie
name = "Catgirl Plushie"
reqs = list(/obj/item/toy/plush/hairball = 3)
@@ -336,6 +416,25 @@
//Unsorted//
////////////
+
+
+/datum/crafting_recipe/stick
+ name = "Stick"
+ time = 30
+ reqs = list(/obj/item/stack/sheet/mineral/wood = 1)
+ result = /obj/item/stick
+ subcategory = CAT_MISCELLANEOUS
+ category = CAT_MISC
+
+
+/datum/crafting_recipe/swordhilt
+ name = "Sword Hilt"
+ time = 30
+ reqs = list(/obj/item/stack/sheet/mineral/wood = 2)
+ result = /obj/item/swordhandle
+ subcategory = CAT_MISCELLANEOUS
+ category = CAT_MISC
+
/datum/crafting_recipe/blackcarpet
name = "Black Carpet"
reqs = list(/obj/item/stack/tile/carpet = 50, /obj/item/toy/crayon/black = 1)
diff --git a/code/datums/components/embedded.dm b/code/datums/components/embedded.dm
index ce124646f4..8c4e62979b 100644
--- a/code/datums/components/embedded.dm
+++ b/code/datums/components/embedded.dm
@@ -29,7 +29,6 @@
*/
-
/datum/component/embedded
dupe_mode = COMPONENT_DUPE_ALLOWED
var/obj/item/bodypart/limb
@@ -120,7 +119,7 @@
UnregisterSignal(weapon, list(COMSIG_MOVABLE_MOVED, COMSIG_PARENT_QDELETING))
if(overlay)
var/atom/A = parent
- A.cut_overlay(overlay, TRUE)
+ UnregisterSignal(A,COMSIG_ATOM_UPDATE_OVERLAYS)
qdel(overlay)
return ..()
@@ -139,30 +138,36 @@
limb.embedded_objects |= weapon // on the inside... on the inside...
weapon.forceMove(victim)
RegisterSignal(weapon, list(COMSIG_MOVABLE_MOVED, COMSIG_PARENT_QDELETING), .proc/byeItemCarbon)
-
+ var/damage = 0
if(harmful)
victim.visible_message("[weapon] embeds itself in [victim]'s [limb.name]!",ignored_mobs=victim)
to_chat(victim, "[weapon] embeds itself in your [limb.name]!")
victim.throw_alert("embeddedobject", /obj/screen/alert/embeddedobject)
playsound(victim,'sound/weapons/bladeslice.ogg', 40)
weapon.add_mob_blood(victim)//it embedded itself in you, of course it's bloody!
- var/damage = weapon.w_class * impact_pain_mult
- limb.receive_damage(brute=(1-pain_stam_pct) * damage, stamina=pain_stam_pct * damage, wound_bonus=-30, sharpness = TRUE)
+ damage = weapon.w_class * impact_pain_mult
SEND_SIGNAL(victim, COMSIG_ADD_MOOD_EVENT, "embedded", /datum/mood_event/embedded)
else
victim.visible_message("[weapon] sticks itself to [victim]'s [limb.name]!",ignored_mobs=victim)
to_chat(victim, "[weapon] sticks itself to your [limb.name]!")
+ if(damage > 0)
+ var/armor = victim.run_armor_check(limb.body_zone, "melee", "Your armor has protected your [limb.name].", "Your armor has softened a hit to your [limb.name].",weapon.armour_penetration)
+ limb.receive_damage(brute=(1-pain_stam_pct) * damage, stamina=pain_stam_pct * damage, blocked=armor, sharpness = weapon.get_sharpness())
+
/// Called every time a carbon with a harmful embed moves, rolling a chance for the item to cause pain. The chance is halved if the carbon is crawling or walking.
/datum/component/embedded/proc/jostleCheck()
var/mob/living/carbon/victim = parent
- var/chance = jostle_chance
+ var/damage = weapon.w_class * pain_mult
+ var/pain_chance_current = jostle_chance
if(victim.m_intent == MOVE_INTENT_WALK || !(victim.mobility_flags & MOBILITY_STAND))
- chance *= 0.5
+ pain_chance_current *= 0.5
- if(harmful && prob(chance))
- var/damage = weapon.w_class * jostle_pain_mult
+ if(pain_stam_pct && IS_STAMCRIT(victim)) //if it's a less-lethal embed, give them a break if they're already stamcritted
+ pain_chance_current *= 0.2
+ damage *= 0.5
+ if(harmful && prob(pain_chance_current))
limb.receive_damage(brute=(1-pain_stam_pct) * damage, stamina=pain_stam_pct * damage, wound_bonus = CANT_WOUND)
to_chat(victim, "[weapon] embedded in your [limb.name] jostles and stings!")
@@ -199,7 +204,7 @@
if(harmful)
var/damage = weapon.w_class * remove_pain_mult
- limb.receive_damage(brute=(1-pain_stam_pct) * damage, stamina=pain_stam_pct * damage, sharpness=TRUE) //It hurts to rip it out, get surgery you dingus.
+ limb.receive_damage(brute=(1-pain_stam_pct) * damage, stamina=pain_stam_pct * damage, wound_bonus = CANT_WOUND) //It hurts to rip it out, get surgery you dingus.
victim.emote("scream")
victim.visible_message("[victim] successfully rips [weapon] out of [victim.p_their()] [limb.name]!", "You successfully remove [weapon] from your [limb.name].")
else
@@ -279,11 +284,13 @@
limb.receive_damage(brute=(1-pain_stam_pct) * damage, stamina=pain_stam_pct * damage, wound_bonus = CANT_WOUND)
to_chat(victim, "[weapon] embedded in your [limb.name] hurts!")
- if(prob(fall_chance))
+ var/fall_chance_current = fall_chance
+ if(victim.mobility_flags & ~MOBILITY_STAND)
+ fall_chance_current *= 0.2
+
+ if(prob(fall_chance_current))
fallOutCarbon()
-
-
////////////////////////////////////////
//////////////TURF PROCS////////////////
////////////////////////////////////////
@@ -319,7 +326,8 @@
var/matrix/M = matrix()
M.Translate(pixelX, pixelY)
overlay.transform = M
- hit.add_overlay(overlay, TRUE)
+ RegisterSignal(hit,COMSIG_ATOM_UPDATE_OVERLAYS,.proc/apply_overlay)
+ hit.update_icon()
if(harmful)
hit.visible_message("[weapon] embeds itself in [hit]!")
@@ -332,6 +340,8 @@
else
hit.visible_message("[weapon] sticks itself to [hit]!")
+/datum/component/embedded/proc/apply_overlay(atom/source, list/overlay_list)
+ overlay_list += overlay
/datum/component/embedded/proc/examineTurf(datum/source, mob/user, list/examine_list)
if(harmful)
diff --git a/code/datums/components/fantasy/prefixes.dm b/code/datums/components/fantasy/prefixes.dm
index b6de85cab0..0ada00a2e8 100644
--- a/code/datums/components/fantasy/prefixes.dm
+++ b/code/datums/components/fantasy/prefixes.dm
@@ -45,8 +45,9 @@
/datum/fantasy_affix/tactical/apply(datum/component/fantasy/comp, newName)
var/obj/item/master = comp.parent
- master.AddElement(/datum/element/tactical)
- comp.appliedElements += list(/datum/element/tactical)
+ var/list/dat = list(/datum/element/tactical)
+ master._AddElement(dat)
+ comp.appliedElements += list(dat)
return "tactical [newName]"
/datum/fantasy_affix/pyromantic
diff --git a/code/datums/components/gps.dm b/code/datums/components/gps.dm
index 200ec2b956..c2b3ad1f30 100644
--- a/code/datums/components/gps.dm
+++ b/code/datums/components/gps.dm
@@ -80,19 +80,15 @@ GLOBAL_LIST_EMPTY(GPS_list)
to_chat(user, "[parent] is now tracking, and visible to other GPS devices.")
tracking = TRUE
-/datum/component/gps/item/ui_interact(mob/user, ui_key = "gps", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) // Remember to use the appropriate state.
+/datum/component/gps/item/ui_interact(mob/user, datum/tgui/ui)
if(emped)
to_chat(user, "[parent] fizzles weakly.")
return
- ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+ ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
- // Variable window height, depending on how many GPS units there are
- // to show, clamped to relatively safe range.
- var/gps_window_height = clamp(325 + GLOB.GPS_list.len * 14, 325, 700)
- ui = new(user, src, ui_key, "Gps", "Global Positioning System", 470, gps_window_height, master_ui, state) //width, height
+ ui = new(user, src, "Gps")
ui.open()
-
- ui.set_autoupdate(state = updating)
+ ui.set_autoupdate(updating)
/datum/component/gps/item/ui_data(mob/user)
var/list/data = list()
diff --git a/code/datums/components/honkspam.dm b/code/datums/components/honkspam.dm
new file mode 100644
index 0000000000..73b5e3335a
--- /dev/null
+++ b/code/datums/components/honkspam.dm
@@ -0,0 +1,22 @@
+// This used to be in paper.dm, it was some snowflake code that was
+// used ONLY on april's fool. I moved it to a component so it could be
+// used in other places
+
+/datum/component/honkspam
+ dupe_mode = COMPONENT_DUPE_UNIQUE
+ var/spam_flag = FALSE
+
+/datum/component/honkspam/Initialize()
+ if(!isitem(parent))
+ return COMPONENT_INCOMPATIBLE
+ RegisterSignal(parent, COMSIG_ITEM_ATTACK_SELF, .proc/interact)
+
+/datum/component/honkspam/proc/reset_spamflag()
+ spam_flag = FALSE
+
+/datum/component/honkspam/proc/interact(mob/user)
+ if(!spam_flag)
+ spam_flag = TRUE
+ var/obj/item/parent_item = parent
+ playsound(parent_item.loc, 'sound/items/bikehorn.ogg', 50, TRUE)
+ addtimer(CALLBACK(src, .proc/reset_spamflag), 2 SECONDS)
diff --git a/code/datums/components/killerqueen.dm b/code/datums/components/killerqueen.dm
new file mode 100644
index 0000000000..1c660d0a87
--- /dev/null
+++ b/code/datums/components/killerqueen.dm
@@ -0,0 +1,91 @@
+/**
+ * KILLER QUEEN
+ *
+ * Simple contact bomb component
+ * Blows up the first person to touch it.
+ */
+/datum/component/killerqueen
+ can_transfer = TRUE
+ /// strength of explosion on the touch-er. 0 to disable. usually only used if the normal explosion is disabled (this is the default).
+ var/ex_strength = EXPLODE_HEAVY
+ /// callback to invoke with (parent, victim) before standard detonation - useful for losing a reference to this component or implementing custom behavior. return FALSE to prevent explosion.
+ var/datum/callback/pre_explode
+ /// callback to invoke with (parent) when deleting without an explosion
+ var/datum/callback/failure
+ /// did we explode
+ var/exploded = FALSE
+ /// examine message
+ var/examine_message
+ /// light explosion radius
+ var/light = 0
+ /// heavy explosion radius
+ var/heavy = 0
+ /// dev explosion radius
+ var/dev = 0
+ /// flame explosion radius
+ var/flame = 0
+ /// only triggered by living mobs
+ var/living_only = TRUE
+
+
+/datum/component/killerqueen/Initialize(ex_strength = EXPLODE_HEAVY, datum/callback/pre_explode, datum/callback/failure, examine_message, light = 0, heavy = 0, dev = 0, flame = 0, living_only = TRUE)
+ . = ..()
+ if(. & COMPONENT_INCOMPATIBLE)
+ return
+ if(!isatom(parent))
+ return COMPONENT_INCOMPATIBLE
+ src.ex_strength = ex_strength
+ src.pre_explode = pre_explode
+ src.failure = failure
+ src.examine_message = examine_message
+ src.light = light
+ src.heavy = heavy
+ src.dev = dev
+ src.flame = flame
+ src.living_only = living_only
+
+/datum/component/killerqueen/Destroy()
+ if(!exploded)
+ failure?.Invoke(parent)
+ return ..()
+
+/datum/component/killerqueen/RegisterWithParent()
+ . = ..()
+ RegisterSignal(parent, list(COMSIG_ATOM_ATTACK_HAND, COMSIG_ATOM_ATTACK_PAW, COMSIG_ATOM_ATTACK_ANIMAL), .proc/touch_detonate)
+ RegisterSignal(parent, COMSIG_MOVABLE_BUMP, .proc/bump_detonate)
+ RegisterSignal(parent, COMSIG_PARENT_ATTACKBY, .proc/attackby_detonate)
+ RegisterSignal(parent, COMSIG_PARENT_EXAMINE, .proc/on_examine)
+
+/datum/component/killerqueen/UnregisterFromParent()
+ . = ..()
+ UnregisterSignal(parent, list(COMSIG_ATOM_ATTACK_ANIMAL, COMSIG_ATOM_ATTACK_HAND, COMSIG_ATOM_ATTACK_PAW,
+ COMSIG_MOVABLE_BUMP, COMSIG_PARENT_ATTACKBY, COMSIG_PARENT_EXAMINE))
+
+/datum/component/killerqueen/proc/attackby_detonate(datum/source, obj/item/I, mob/user)
+ detonate(user)
+
+/datum/component/killerqueen/proc/bump_detonate(datum/source, atom/A)
+ detonate(A)
+
+/datum/component/killerqueen/proc/touch_detonate(datum/source, mob/user)
+ detonate(user)
+
+/datum/component/killerqueen/proc/on_examine(datum/source, mob/examiner, list/examine_return)
+ if(examine_message)
+ examine_return += examine_message
+
+/datum/component/killerqueen/proc/detonate(atom/victim)
+ if(!isliving(victim) && living_only)
+ return
+ if(pre_explode && !pre_explode.Invoke(parent, victim))
+ return
+ if(ex_strength)
+ victim.ex_act(ex_strength)
+ if(light || heavy || dev || flame)
+ explosion(parent, dev, heavy, light, flame_range = flame)
+ else
+ var/turf/T = get_turf(parent)
+ playsound(T, 'sound/effects/explosion2.ogg', 200, 1)
+ new /obj/effect/temp_visual/explosion(T)
+ exploded = TRUE
+ qdel(src)
diff --git a/code/datums/components/label.dm b/code/datums/components/label.dm
new file mode 100644
index 0000000000..c6d0c595eb
--- /dev/null
+++ b/code/datums/components/label.dm
@@ -0,0 +1,87 @@
+/**
+ The label component.
+
+ This component is used to manage labels applied by the hand labeler.
+
+ Atoms can only have one instance of this component, and therefore only one label at a time.
+ This is to avoid having names like "Backpack (label1) (label2) (label3)". This is annoying and abnoxious to read.
+
+ When a player clicks the atom with a hand labeler to apply a label, this component gets applied to it.
+ If the labeler is off, the component will be removed from it, and the label will be removed from its name.
+ */
+/datum/component/label
+ dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS
+ /// The name of the label the player is applying to the parent.
+ var/label_name
+
+/datum/component/label/Initialize(_label_name)
+ if(!isatom(parent))
+ return COMPONENT_INCOMPATIBLE
+
+ label_name = _label_name
+ apply_label()
+
+/datum/component/label/RegisterWithParent()
+ RegisterSignal(parent, COMSIG_PARENT_ATTACKBY, .proc/OnAttackby)
+ RegisterSignal(parent, COMSIG_PARENT_EXAMINE, .proc/Examine)
+
+/datum/component/label/UnregisterFromParent()
+ UnregisterSignal(parent, list(COMSIG_PARENT_ATTACKBY, COMSIG_PARENT_EXAMINE))
+
+/**
+ This proc will fire after the parent is hit by a hand labeler which is trying to apply another label.
+ Since the parent already has a label, it will remove the old one from the parent's name, and apply the new one.
+*/
+/datum/component/label/InheritComponent(datum/component/label/new_comp , i_am_original, _label_name)
+ remove_label()
+ if(new_comp)
+ label_name = new_comp.label_name
+ else
+ label_name = _label_name
+ apply_label()
+
+/**
+ This proc will trigger when any object is used to attack the parent.
+
+ If the attacking object is not a hand labeler, it will return.
+ If the attacking object is a hand labeler it will restore the name of the parent to what it was before this component was added to it, and the component will be deleted.
+
+ Arguments:
+ * source: The parent.
+ * attacker: The object that is hitting the parent.
+ * user: The mob who is wielding the attacking object.
+*/
+/datum/component/label/proc/OnAttackby(datum/source, obj/item/attacker, mob/user)
+ // If the attacking object is not a hand labeler or its mode is 1 (has a label ready to apply), return.
+ // The hand labeler should be off (mode is 0), in order to remove a label.
+ var/obj/item/hand_labeler/labeler = attacker
+ if(!istype(labeler) || labeler.mode)
+ return
+
+ remove_label()
+ playsound(parent, 'sound/items/poster_ripped.ogg', 20, TRUE)
+ to_chat(user, "You remove the label from [parent].")
+ qdel(src) // Remove the component from the object.
+
+/**
+ This proc will trigger when someone examines the parent.
+ It will attach the text found in the body of the proc to the `examine_list` and display it to the player examining the parent.
+
+ Arguments:
+ * source: The parent.
+ * user: The mob exmaining the parent.
+ * examine_list: The current list of text getting passed from the parent's normal examine() proc.
+*/
+/datum/component/label/proc/Examine(datum/source, mob/user, list/examine_list)
+ examine_list += "It has a label with some words written on it. Use a hand labeler to remove it."
+
+/// Applies a label to the name of the parent in the format of: "parent_name (label)"
+/datum/component/label/proc/apply_label()
+ var/atom/owner = parent
+ owner.name += " ([label_name])"
+
+/// Removes the label from the parent's name
+/datum/component/label/proc/remove_label()
+ var/atom/owner = parent
+ owner.name = replacetext(owner.name, "([label_name])", "") // Remove the label text from the parent's name, wherever it's located.
+ owner.name = trim(owner.name) // Shave off any white space from the beginning or end of the parent's name.
diff --git a/code/datums/components/mood.dm b/code/datums/components/mood.dm
index 90fea9ec8a..87bc681651 100644
--- a/code/datums/components/mood.dm
+++ b/code/datums/components/mood.dm
@@ -307,6 +307,10 @@
/datum/component/mood/proc/HandleNutrition(mob/living/L)
+ if(isethereal(L))
+ HandleCharge(L)
+ if(HAS_TRAIT(L, TRAIT_NOHUNGER))
+ return FALSE //no mood events for nutrition
switch(L.nutrition)
if(NUTRITION_LEVEL_FULL to INFINITY)
add_event(null, "nutrition", /datum/mood_event/fat)
@@ -321,6 +325,22 @@
if(0 to NUTRITION_LEVEL_STARVING)
add_event(null, "nutrition", /datum/mood_event/starving)
+/datum/component/mood/proc/HandleCharge(mob/living/carbon/human/H)
+ var/datum/species/ethereal/E = H.dna.species
+ switch(E.get_charge(H))
+ if(ETHEREAL_CHARGE_NONE to ETHEREAL_CHARGE_LOWPOWER)
+ add_event(null, "charge", /datum/mood_event/decharged)
+ if(ETHEREAL_CHARGE_LOWPOWER to ETHEREAL_CHARGE_NORMAL)
+ add_event(null, "charge", /datum/mood_event/lowpower)
+ if(ETHEREAL_CHARGE_NORMAL to ETHEREAL_CHARGE_ALMOSTFULL)
+ clear_event(null, "charge")
+ if(ETHEREAL_CHARGE_ALMOSTFULL to ETHEREAL_CHARGE_FULL)
+ add_event(null, "charge", /datum/mood_event/charged)
+ if(ETHEREAL_CHARGE_FULL to ETHEREAL_CHARGE_OVERLOAD)
+ add_event(null, "charge", /datum/mood_event/overcharged)
+ if(ETHEREAL_CHARGE_OVERLOAD to ETHEREAL_CHARGE_DANGEROUS)
+ add_event(null, "charge", /datum/mood_event/supercharged)
+
/datum/component/mood/proc/update_beauty(area/A)
if(A.outdoors) //if we're outside, we don't care.
clear_event(null, "area_beauty")
diff --git a/code/datums/components/ntnet_interface.dm b/code/datums/components/ntnet_interface.dm
index 6159c7c2c4..06d69f0ce3 100644
--- a/code/datums/components/ntnet_interface.dm
+++ b/code/datums/components/ntnet_interface.dm
@@ -1,4 +1,4 @@
-//Thing meant for allowing datums and objects to access a NTnet network datum.
+//Thing meant for allowing datums and objects to access an NTnet network datum.
/datum/proc/ntnet_receive(datum/netdata/data)
return
diff --git a/code/datums/components/pellet_cloud.dm b/code/datums/components/pellet_cloud.dm
index 938d9f5f56..6a1028cc65 100644
--- a/code/datums/components/pellet_cloud.dm
+++ b/code/datums/components/pellet_cloud.dm
@@ -1,3 +1,8 @@
+// the following defines are used for [/datum/component/pellet_cloud/var/list/wound_info_by_part] to store the damage, wound_bonus, and bw_bonus for each bodypart hit
+#define CLOUD_POSITION_DAMAGE 1
+#define CLOUD_POSITION_W_BONUS 2
+#define CLOUD_POSITION_BW_BONUS 3
+
/*
* This component is used when you want to create a bunch of shrapnel or projectiles (say, shrapnel from a fragmentation grenade, or buckshot from a shotgun) from a central point,
* without necessarily printing a separate message for every single impact. This component should be instantiated right when you need it (like the moment of firing), then activated
@@ -29,7 +34,10 @@
var/list/pellets = list()
/// An associated list with the atom hit as the key and how many pellets they've eaten for the value, for printing aggregate messages
var/list/targets_hit = list()
- /// For grenades, any /mob/living's the grenade is moved onto, see [/datum/component/pellet_cloud/proc/handle_martyrs()]
+
+ /// Another associated list for hit bodyparts on carbons so we can track how much wounding potential we have for each bodypart
+ var/list/wound_info_by_part = list()
+ /// For grenades, any /mob/living's the grenade is moved onto, see [/datum/component/pellet_cloud/proc/handle_martyrs]
var/list/bodies
/// For grenades, tracking people who die covering a grenade for achievement purposes, see [/datum/component/pellet_cloud/proc/handle_martyrs()]
var/list/purple_hearts
@@ -64,6 +72,7 @@
/datum/component/pellet_cloud/Destroy(force, silent)
purple_hearts = null
pellets = null
+ wound_info_by_part = null
targets_hit = null
bodies = null
return ..()
@@ -187,10 +196,26 @@
break
///One of our pellets hit something, record what it was and check if we're done (terminated == num_pellets)
-/datum/component/pellet_cloud/proc/pellet_hit(obj/item/projectile/P, atom/movable/firer, atom/target, Angle)
+/datum/component/pellet_cloud/proc/pellet_hit(obj/item/projectile/P, atom/movable/firer, atom/target, Angle, hit_zone)
pellets -= P
terminated++
hits++
+ var/obj/item/bodypart/hit_part
+ if(iscarbon(target) && hit_zone)
+ var/mob/living/carbon/hit_carbon = target
+ hit_part = hit_carbon.get_bodypart(hit_zone)
+ if(hit_part)
+ target = hit_part
+ if(P.wound_bonus != CANT_WOUND) // handle wounding
+ // unfortunately, due to how pellet clouds handle finalizing only after every pellet is accounted for, that also means there might be a short delay in dealing wounds if one pellet goes wide
+ // while buckshot may reach a target or miss it all in one tick, we also have to account for possible ricochets that may take a bit longer to hit the target
+ if(isnull(wound_info_by_part[hit_part]))
+ wound_info_by_part[hit_part] = list(0, 0, 0)
+ wound_info_by_part[hit_part][CLOUD_POSITION_DAMAGE] += P.damage // these account for decay
+ wound_info_by_part[hit_part][CLOUD_POSITION_W_BONUS] += P.wound_bonus
+ wound_info_by_part[hit_part][CLOUD_POSITION_BW_BONUS] += P.bare_wound_bonus
+ P.wound_bonus = CANT_WOUND // actual wounding will be handled aggregate
+
targets_hit[target]++
if(targets_hit[target] == 1)
RegisterSignal(target, COMSIG_PARENT_QDELETING, .proc/on_target_qdel, override=TRUE)
@@ -231,13 +256,23 @@
for(var/atom/target in targets_hit)
var/num_hits = targets_hit[target]
UnregisterSignal(target, COMSIG_PARENT_QDELETING)
- if(num_hits > 1)
- target.visible_message("[target] is hit by [num_hits] [proj_name]s!", null, null, COMBAT_MESSAGE_RANGE, target)
- to_chat(target, "You're hit by [num_hits] [proj_name]s!")
- else
- target.visible_message("[target] is hit by a [proj_name]!", null, null, COMBAT_MESSAGE_RANGE, target)
- to_chat(target, "You're hit by a [proj_name]!")
+ var/obj/item/bodypart/hit_part
+ if(isbodypart(target))
+ hit_part = target
+ target = hit_part.owner
+ var/damage_dealt = wound_info_by_part[hit_part][CLOUD_POSITION_DAMAGE]
+ var/w_bonus = wound_info_by_part[hit_part][CLOUD_POSITION_W_BONUS]
+ var/bw_bonus = wound_info_by_part[hit_part][CLOUD_POSITION_BW_BONUS]
+ var/wound_type = (initial(P.damage_type) == BRUTE) ? WOUND_BLUNT : WOUND_BURN // sharpness is handled in the wound rolling
+ wound_info_by_part[hit_part] = null
+ hit_part.painless_wound_roll(wound_type, damage_dealt, w_bonus, bw_bonus, initial(P.sharpness))
+ if(num_hits > 1)
+ target.visible_message("[target] is hit by [num_hits] [proj_name]s[hit_part ? " in the [hit_part.name]" : ""]!", null, null, COMBAT_MESSAGE_RANGE, target)
+ to_chat(target, "You're hit by [num_hits] [proj_name]s[hit_part ? " in the [hit_part.name]" : ""]!")
+ else
+ target.visible_message("[target] is hit by a [proj_name][hit_part ? " in the [hit_part.name]" : ""]!", null, null, COMBAT_MESSAGE_RANGE, target)
+ to_chat(target, "You're hit by a [proj_name][hit_part ? " in the [hit_part.name]" : ""]!")
UnregisterSignal(parent, COMSIG_PARENT_PREQDELETED)
if(queued_delete)
qdel(parent)
@@ -281,3 +316,7 @@
targets_hit -= target
bodies -= target
purple_hearts -= target
+
+#undef CLOUD_POSITION_DAMAGE
+#undef CLOUD_POSITION_W_BONUS
+#undef CLOUD_POSITION_BW_BONUS
diff --git a/code/datums/components/plumbing/_plumbing.dm b/code/datums/components/plumbing/_plumbing.dm
new file mode 100644
index 0000000000..6592e41103
--- /dev/null
+++ b/code/datums/components/plumbing/_plumbing.dm
@@ -0,0 +1,215 @@
+/datum/component/plumbing
+ ///Index with "1" = /datum/ductnet/theductpointingnorth etc. "1" being the num2text from NORTH define
+ var/list/datum/ductnet/ducts = list()
+ ///shortcut to our parents' reagent holder
+ var/datum/reagents/reagents
+ ///TRUE if we wanna add proper pipe outless under our parent object. this is pretty good if i may so so myself
+ var/use_overlays = TRUE
+ ///We can't just cut all of the parents' overlays, so we'll track them here
+ var/list/image/ducterlays
+ ///directions in wich we act as a supplier
+ var/supply_connects
+ ///direction in wich we act as a demander
+ var/demand_connects
+ ///FALSE to pretty much just not exist in the plumbing world so we can be moved, TRUE to go plumbo mode
+ var/active = FALSE
+ ///if TRUE connects will spin with the parent object visually and codually, so you can have it work in any direction. FALSE if you want it to be static
+ var/turn_connects = TRUE
+
+/datum/component/plumbing/Initialize(start=TRUE, _turn_connects=TRUE) //turn_connects for wheter or not we spin with the object to change our pipes
+ if(parent && !istype(parent, /atom/movable))
+ return COMPONENT_INCOMPATIBLE
+ var/atom/movable/AM = parent
+ if(!AM.reagents)
+ return COMPONENT_INCOMPATIBLE
+ reagents = AM.reagents
+ turn_connects = _turn_connects
+
+ RegisterSignal(parent, list(COMSIG_MOVABLE_MOVED,COMSIG_PARENT_PREQDELETED), .proc/disable)
+ RegisterSignal(parent, list(COMSIG_OBJ_DEFAULT_UNFASTEN_WRENCH), .proc/toggle_active)
+
+ if(start)
+ enable()
+
+ if(use_overlays)
+ create_overlays()
+
+/datum/component/plumbing/process()
+ if(!demand_connects || !reagents)
+ STOP_PROCESSING(SSfluids, src)
+ return
+ if(reagents.total_volume < reagents.maximum_volume)
+ for(var/D in GLOB.cardinals)
+ if(D & demand_connects)
+ send_request(D)
+///Can we be added to the ductnet?
+/datum/component/plumbing/proc/can_add(datum/ductnet/D, dir)
+ if(!active)
+ return
+ if(!dir || !D)
+ return FALSE
+ if(num2text(dir) in ducts)
+ return FALSE
+
+ return TRUE
+///called from in process(). only calls process_request(), but can be overwritten for children with special behaviour
+/datum/component/plumbing/proc/send_request(dir)
+ process_request(amount = MACHINE_REAGENT_TRANSFER, reagent = null, dir = dir)
+///check who can give us what we want, and how many each of them will give us
+/datum/component/plumbing/proc/process_request(amount, reagent, dir)
+ var/list/valid_suppliers = list()
+ var/datum/ductnet/net
+ if(!ducts.Find(num2text(dir)))
+ return
+ net = ducts[num2text(dir)]
+ for(var/A in net.suppliers)
+ var/datum/component/plumbing/supplier = A
+ if(supplier.can_give(amount, reagent, net))
+ valid_suppliers += supplier
+ for(var/A in valid_suppliers)
+ var/datum/component/plumbing/give = A
+ give.transfer_to(src, amount / valid_suppliers.len, reagent, net)
+///returns TRUE when they can give the specified amount and reagent. called by process request
+/datum/component/plumbing/proc/can_give(amount, reagent, datum/ductnet/net)
+ if(amount <= 0)
+ return
+
+ if(reagent) //only asked for one type of reagent
+ for(var/A in reagents.reagent_list)
+ var/datum/reagent/R = A
+ if(R.type == reagent)
+ return TRUE
+ else if(reagents.total_volume > 0) //take whatever
+ return TRUE
+///this is where the reagent is actually transferred and is thus the finish point of our process()
+/datum/component/plumbing/proc/transfer_to(datum/component/plumbing/target, amount, reagent, datum/ductnet/net)
+ if(!reagents || !target || !target.reagents)
+ return FALSE
+ if(reagent)
+ reagents.trans_id_to(target.parent, reagent, amount)
+ else
+ reagents.trans_to(target.parent, amount)
+///We create our luxurious piping overlays/underlays, to indicate where we do what. only called once if use_overlays = TRUE in Initialize()
+/datum/component/plumbing/proc/create_overlays()
+ var/atom/movable/AM = parent
+ for(var/image/I in ducterlays)
+ AM.overlays.Remove(I)
+ qdel(I)
+ ducterlays = list()
+ for(var/D in GLOB.cardinals)
+ var/color
+ var/direction
+ if(D & demand_connects)
+ color = "red" //red because red is mean and it takes
+ else if(D & supply_connects)
+ color = "blue" //blue is nice and gives
+ else
+ continue
+ var/image/I
+ if(turn_connects)
+ switch(D)
+ if(NORTH)
+ direction = "north"
+ if(SOUTH)
+ direction = "south"
+ if(EAST)
+ direction = "east"
+ if(WEST)
+ direction = "west"
+ I = image('icons/obj/plumbing/plumbers.dmi', "[direction]-[color]", layer = AM.layer - 1)
+ else
+ I = image('icons/obj/plumbing/plumbers.dmi', color, layer = AM.layer - 1) //color is not color as in the var, it's just the name
+ I.dir = D
+ AM.add_overlay(I)
+ ducterlays += I
+///we stop acting like a plumbing thing and disconnect if we are, so we can safely be moved and stuff
+/datum/component/plumbing/proc/disable()
+ if(!active)
+ return
+ STOP_PROCESSING(SSfluids, src)
+ for(var/A in ducts)
+ var/datum/ductnet/D = ducts[A]
+ D.remove_plumber(src)
+ active = FALSE
+ for(var/D in GLOB.cardinals)
+ if(D & (demand_connects | supply_connects))
+ for(var/obj/machinery/duct/duct in get_step(parent, D))
+ duct.attempt_connect()
+
+///settle wherever we are, and start behaving like a piece of plumbing
+/datum/component/plumbing/proc/enable()
+ if(active)
+ return
+ update_dir()
+ active = TRUE
+ var/atom/movable/AM = parent
+ for(var/obj/machinery/duct/D in AM.loc) //Destroy any ducts under us. Ducts also self destruct if placed under a plumbing machine. machines disable when they get moved
+ if(D.anchored) //that should cover everything
+ D.disconnect_duct()
+
+ if(demand_connects)
+ START_PROCESSING(SSfluids, src)
+
+ for(var/D in GLOB.cardinals)
+ if(D & (demand_connects | supply_connects))
+ for(var/atom/movable/A in get_step(parent, D))
+ if(istype(A, /obj/machinery/duct))
+ var/obj/machinery/duct/duct = A
+ duct.attempt_connect()
+ else
+ var/datum/component/plumbing/P = A.GetComponent(/datum/component/plumbing)
+ if(P)
+ direct_connect(P, D)
+
+/// Toggle our machinery on or off. This is called by a hook from default_unfasten_wrench with anchored as only param, so we dont have to copypaste this on every object that can move
+/datum/component/plumbing/proc/toggle_active(obj/O, new_state)
+ if(new_state)
+ enable()
+ else
+ disable()
+/** We update our connects only when we settle down by taking our current and original direction to find our new connects
+* If someone wants it to fucking spin while connected to something go actually knock yourself out
+*/
+/datum/component/plumbing/proc/update_dir()
+ if(!turn_connects)
+ return
+ var/atom/movable/AM = parent
+ var/new_demand_connects
+ var/new_supply_connects
+ var/new_dir = AM.dir
+ var/angle = 180 - dir2angle(new_dir)
+ if(new_dir == SOUTH)
+ demand_connects = initial(demand_connects)
+ supply_connects = initial(supply_connects)
+ else
+ for(var/D in GLOB.cardinals)
+ if(D & initial(demand_connects))
+ new_demand_connects += turn(D, angle)
+ if(D & initial(supply_connects))
+ new_supply_connects += turn(D, angle)
+ demand_connects = new_demand_connects
+ supply_connects = new_supply_connects
+///Give the direction of a pipe, and it'll return wich direction it originally was when it's object pointed SOUTH
+/datum/component/plumbing/proc/get_original_direction(dir)
+ var/atom/movable/AM = parent
+ return turn(dir, dir2angle(AM.dir) - 180)
+//special case in-case we want to connect directly with another machine without a duct
+/datum/component/plumbing/proc/direct_connect(datum/component/plumbing/P, dir)
+ if(!P.active)
+ return
+ var/opposite_dir = turn(dir, 180)
+ if(P.demand_connects & opposite_dir && supply_connects & dir || P.supply_connects & opposite_dir && demand_connects & dir) //make sure we arent connecting two supplies or demands
+ var/datum/ductnet/net = new()
+ net.add_plumber(src, dir)
+ net.add_plumber(P, opposite_dir)
+
+///has one pipe input that only takes, example is manual output pipe
+/datum/component/plumbing/simple_demand
+ demand_connects = NORTH
+///has one pipe output that only supplies. example is liquid pump and manual input pipe
+/datum/component/plumbing/simple_supply
+ supply_connects = NORTH
+///input and output, like a holding tank
+/datum/component/plumbing/tank
+ demand_connects = WEST
+ supply_connects = EAST
diff --git a/code/datums/components/plumbing/chemical_acclimator.dm b/code/datums/components/plumbing/chemical_acclimator.dm
new file mode 100644
index 0000000000..1cbe6ff017
--- /dev/null
+++ b/code/datums/components/plumbing/chemical_acclimator.dm
@@ -0,0 +1,21 @@
+/datum/component/plumbing/acclimator
+ demand_connects = WEST
+ supply_connects = EAST
+ var/obj/machinery/plumbing/acclimator/AC
+
+/datum/component/plumbing/acclimator/Initialize(start=TRUE, _turn_connects=TRUE)
+ . = ..()
+ if(!istype(parent, /obj/machinery/plumbing/acclimator))
+ return COMPONENT_INCOMPATIBLE
+ AC = parent
+
+/datum/component/plumbing/acclimator/can_give(amount, reagent)
+ . = ..()
+ if(. && AC.emptying)
+ return TRUE
+ return FALSE
+///We're overriding process and not send_request, because all process does is do the requests, so we might aswell cut out the middle man and save some code from running
+/datum/component/plumbing/acclimator/process()
+ if(AC.emptying)
+ return
+ . = ..()
diff --git a/code/datums/components/plumbing/filter.dm b/code/datums/components/plumbing/filter.dm
new file mode 100644
index 0000000000..76b76323c5
--- /dev/null
+++ b/code/datums/components/plumbing/filter.dm
@@ -0,0 +1,59 @@
+///The magical plumbing component used by the chemical filters. The different supply connects behave differently depending on the filters set on the chemical filter
+/datum/component/plumbing/filter
+ demand_connects = NORTH
+ supply_connects = SOUTH | EAST | WEST //SOUTH is straight, EAST is left and WEST is right. We look from the perspective of the insert
+
+/datum/component/plumbing/filter/Initialize()
+ . = ..()
+ if(!istype(parent, /obj/machinery/plumbing/filter))
+ return COMPONENT_INCOMPATIBLE
+
+/datum/component/plumbing/filter/can_give(amount, reagent, datum/ductnet/net)
+ . = ..()
+ if(.)
+ var/direction
+ for(var/A in ducts)
+ if(ducts[A] == net)
+ direction = get_original_direction(text2num(A)) //we need it relative to the direction, so filters don't change when we turn the filter
+ break
+ if(!direction)
+ return FALSE
+ if(reagent)
+ if(!can_give_in_direction(direction, reagent))
+ return FALSE
+
+/datum/component/plumbing/filter/transfer_to(datum/component/plumbing/target, amount, reagent, datum/ductnet/net)
+ if(!reagents || !target || !target.reagents)
+ return FALSE
+ var/direction
+ for(var/A in ducts)
+ if(ducts[A] == net)
+ direction = get_original_direction(text2num(A))
+ break
+ if(reagent)
+ reagents.trans_id_to(target.parent, reagent, amount)
+ else
+ for(var/A in reagents.reagent_list)
+ var/datum/reagent/R = A
+ if(!can_give_in_direction(direction, R.type))
+ continue
+ var/new_amount
+ if(R.volume < amount)
+ new_amount = amount - R.volume
+ reagents.trans_id_to(target.parent, R.type, amount)
+ amount = new_amount
+ if(amount <= 0)
+ break
+///We check if the direction and reagent are valid to give. Needed for filters since different outputs have different behaviours
+/datum/component/plumbing/filter/proc/can_give_in_direction(dir, reagent)
+ var/obj/machinery/plumbing/filter/F = parent
+ switch(dir)
+ if(SOUTH) //straight
+ if(!F.left.Find(reagent) && !F.right.Find(reagent))
+ return TRUE
+ if(WEST) //right
+ if(F.right.Find(reagent))
+ return TRUE
+ if(EAST) //left
+ if(F.left.Find(reagent))
+ return TRUE
diff --git a/code/datums/components/plumbing/reaction_chamber.dm b/code/datums/components/plumbing/reaction_chamber.dm
new file mode 100644
index 0000000000..90f4e621da
--- /dev/null
+++ b/code/datums/components/plumbing/reaction_chamber.dm
@@ -0,0 +1,38 @@
+/datum/component/plumbing/reaction_chamber
+ demand_connects = WEST
+ supply_connects = EAST
+
+/datum/component/plumbing/reaction_chamber/Initialize(start=TRUE, _turn_connects=TRUE)
+ . = ..()
+ if(!istype(parent, /obj/machinery/plumbing/reaction_chamber))
+ return COMPONENT_INCOMPATIBLE
+
+/datum/component/plumbing/reaction_chamber/can_give(amount, reagent, datum/ductnet/net)
+ . = ..()
+ var/obj/machinery/plumbing/reaction_chamber/RC = parent
+ if(!. || !RC.emptying)
+ return FALSE
+
+/datum/component/plumbing/reaction_chamber/send_request(dir)
+ var/obj/machinery/plumbing/reaction_chamber/RC = parent
+ if(RC.emptying || !LAZYLEN(RC.required_reagents))
+ return
+ for(var/RT in RC.required_reagents)
+ var/has_reagent = FALSE
+ for(var/A in reagents.reagent_list)
+ var/datum/reagent/RD = A
+ if(RT == RD.type)
+ has_reagent = TRUE
+ if(RD.volume < RC.required_reagents[RT])
+ process_request(min(RC.required_reagents[RT] - RD.volume, MACHINE_REAGENT_TRANSFER) , RT, dir)
+ return
+ if(!has_reagent)
+ process_request(min(RC.required_reagents[RT], MACHINE_REAGENT_TRANSFER), RT, dir)
+ return
+
+ RC.reagent_flags &= ~NO_REACT
+ reagents.handle_reactions()
+
+ RC.emptying = TRUE //If we move this up, it'll instantly get turned off since any reaction always sets the reagent_total to zero. Other option is make the reaction update
+ //everything for every chemical removed, wich isn't a good option either.
+ RC.on_reagent_change() //We need to check it now, because some reactions leave nothing left.
diff --git a/code/datums/components/plumbing/splitter.dm b/code/datums/components/plumbing/splitter.dm
new file mode 100644
index 0000000000..7194e8803b
--- /dev/null
+++ b/code/datums/components/plumbing/splitter.dm
@@ -0,0 +1,45 @@
+/datum/component/plumbing/splitter
+ demand_connects = NORTH
+ supply_connects = SOUTH | EAST
+
+/datum/component/plumbing/splitter/Initialize()
+ . = ..()
+ if(. && !istype(parent, /obj/machinery/plumbing/splitter))
+ return FALSE
+
+/datum/component/plumbing/splitter/can_give(amount, reagent, datum/ductnet/net)
+ . = ..()
+ if(!.)
+ return
+ . = FALSE
+ var/direction
+ for(var/A in ducts)
+ if(ducts[A] == net)
+ direction = get_original_direction(text2num(A))
+ break
+ var/obj/machinery/plumbing/splitter/S = parent
+ switch(direction)
+ if(SOUTH)
+ if(S.turn_straight && S.transfer_straight <= amount)
+ S.turn_straight = FALSE
+ return TRUE
+ if(EAST)
+ if(!S.turn_straight && S.transfer_side <= amount)
+ S.turn_straight = TRUE
+ return TRUE
+
+/datum/component/plumbing/splitter/transfer_to(datum/component/plumbing/target, amount, reagent, datum/ductnet/net)
+ var/direction
+ for(var/A in ducts)
+ if(ducts[A] == net)
+ direction = get_original_direction(text2num(A))
+ break
+ var/obj/machinery/plumbing/splitter/S = parent
+ switch(direction)
+ if(SOUTH)
+ if(amount >= S.transfer_straight)
+ amount = S.transfer_straight
+ if(EAST)
+ if(amount >= S.transfer_side)
+ amount = S.transfer_side
+ . = ..()
diff --git a/code/datums/components/radioactive.dm b/code/datums/components/radioactive.dm
index f12e8bf007..8afa4353b2 100644
--- a/code/datums/components/radioactive.dm
+++ b/code/datums/components/radioactive.dm
@@ -29,10 +29,18 @@
if(strength > RAD_MINIMUM_CONTAMINATION)
SSradiation.warn(src)
+ //Let's make er glow
+ //This relies on parent not being a turf or something. IF YOU CHANGE THAT, CHANGE THIS
+ var/atom/movable/master = parent
+ master.add_filter("rad_glow", 2, list("type" = "outline", "color" = "#39ff1430", "size" = 2))
+ addtimer(CALLBACK(src, .proc/glow_loop, master), rand(1,19))//Things should look uneven
+
START_PROCESSING(SSradiation, src)
/datum/component/radioactive/Destroy()
STOP_PROCESSING(SSradiation, src)
+ var/atom/movable/master = parent
+ master.remove_filter("rad_glow")
return ..()
/datum/component/radioactive/process()
@@ -46,6 +54,13 @@
if(strength <= RAD_BACKGROUND_RADIATION)
return PROCESS_KILL
+
+/datum/component/radioactive/proc/glow_loop(atom/movable/master)
+ var/filter = master.get_filter("rad_glow")
+ if(filter)
+ animate(filter, alpha = 110, time = 15, loop = -1)
+ animate(alpha = 40, time = 25)
+
/datum/component/radioactive/InheritComponent(datum/component/C, i_am_original, _strength, _source, _half_life, _can_contaminate)
if(!i_am_original)
return
diff --git a/code/datums/components/shielded.dm b/code/datums/components/shielded.dm
index 2052e57f49..3cf70e80ef 100644
--- a/code/datums/components/shielded.dm
+++ b/code/datums/components/shielded.dm
@@ -14,6 +14,7 @@
var/mob/living/holder //who is currently benefiting from the shield.
var/dissipating = FALSE //Is this shield meant to dissipate over time instead of recharging.
var/del_on_overload = FALSE //will delete itself once it has no charges left.
+ var/cached_vis_overlay //text identifier of the visual overlay.
/datum/component/shielded/Initialize(current, max = 3, delay = 20 SECONDS, rate = 1, slots, state = "shield-old", broken, \
sound = 'sound/magic/charge.ogg', end_sound = 'sound/machines/ding.ogg', diss = FALSE, del_overload = FALSE)
@@ -47,9 +48,8 @@
holder = L
var/to_add = charges >= 1 ? shield_state : broken_state
if(to_add)
- var/mutable_appearance/M = mutable_appearance('icons/effects/effects.dmi', to_add)
- M.layer = (L.layer > MOB_LAYER ? L.layer : MOB_LAYER) + 0.01
- holder.add_overlay(M, TRUE)
+ var/layer = (L.layer > MOB_LAYER ? L.layer : MOB_LAYER) + 0.01
+ SSvis_overlays.add_vis_overlay(L, 'icons/effects/effects.dmi', to_add, layer, GAME_PLANE, L.dir)
/datum/component/shielded/UnregisterFromParent()
. = ..()
@@ -57,9 +57,9 @@
UnregisterSignal(parent, list(COMSIG_ITEM_RUN_BLOCK,COMSIG_ITEM_CHECK_BLOCK,COMSIG_ITEM_EQUIPPED,COMSIG_ITEM_DROPPED))
if(holder)
UnregisterSignal(holder, list(COMSIG_LIVING_RUN_BLOCK, COMSIG_LIVING_GET_BLOCKING_ITEMS))
- var/to_remove = charges >= 1 ? shield_state : broken_state
- if(to_remove)
- holder.cut_overlay(mutable_appearance('icons/effects/effects.dmi', to_remove), TRUE)
+ if(cached_vis_overlay)
+ SSvis_overlays.remove_vis_overlay(holder, cached_vis_overlay)
+ cached_vis_overlay = null
holder = null
/datum/component/shielded/process()
@@ -80,7 +80,7 @@
holder.visible_message("[holder]'s shield overloads!")
qdel(src)
return
- if(holder && (old_charges < 1 && charges >= 1) || (!del_on_overload && old_charges >= 1 && charges < 1))
+ if(holder && ((old_charges < 1 && charges >= 1) || (!del_on_overload && old_charges >= 1 && charges < 1)))
update_shield_overlay(charges < 1)
/datum/component/shielded/proc/adjust_charges(amount)
@@ -93,20 +93,19 @@
holder.visible_message("[holder]'s shield overloads!")
qdel(src)
return
- if(holder && (old_charges < 1 && charges >= 1) || (!del_on_overload && old_charges >= 1 && charges < 1))
+ if(holder && ((old_charges < 1 && charges >= 1) || (!del_on_overload && old_charges >= 1 && charges < 1)))
update_shield_overlay(charges < 1)
/datum/component/shielded/proc/update_shield_overlay(broken)
if(!holder)
return
- var/to_remove = broken ? shield_state : broken_state
var/to_add = broken ? broken_state : shield_state
- if(to_remove)
- holder.cut_overlay(mutable_appearance('icons/effects/effects.dmi', to_remove), TRUE)
+ if(cached_vis_overlay)
+ SSvis_overlays.remove_vis_overlay(holder, cached_vis_overlay)
+ cached_vis_overlay = null
if(to_add)
- var/mutable_appearance/M = mutable_appearance('icons/effects/effects.dmi', to_add)
- M.layer = (holder.layer > MOB_LAYER ? holder.layer : MOB_LAYER) + 0.01
- holder.add_overlay(M, TRUE)
+ var/layer = (holder.layer > MOB_LAYER ? holder.layer : MOB_LAYER) + 0.01
+ SSvis_overlays.add_vis_overlay(holder, 'icons/effects/effects.dmi', to_add, layer, GAME_PLANE, holder.dir)
/datum/component/shielded/proc/on_equip(obj/item/source, mob/living/equipper, slot)
if(!(accepted_slots & slotdefine2slotbit(slot)))
@@ -117,17 +116,16 @@
RegisterSignal(equipper, COMSIG_LIVING_GET_BLOCKING_ITEMS, .proc/include_shield)
var/to_add = charges >= 1 ? shield_state : broken_state
if(to_add)
- var/mutable_appearance/M = mutable_appearance('icons/effects/effects.dmi', to_add)
- M.layer = (holder.layer > MOB_LAYER ? holder.layer : MOB_LAYER) + 0.01
- equipper.add_overlay(M, TRUE)
+ var/layer = (holder.layer > MOB_LAYER ? holder.layer : MOB_LAYER) + 0.01
+ cached_vis_overlay = SSvis_overlays.add_vis_overlay(holder, 'icons/effects/effects.dmi', to_add, layer, GAME_PLANE, holder.dir)
/datum/component/shielded/proc/on_drop(obj/item/source, mob/dropper)
if(holder == dropper)
UnregisterSignal(holder, COMSIG_LIVING_GET_BLOCKING_ITEMS)
UnregisterSignal(parent, list(COMSIG_ITEM_RUN_BLOCK, COMSIG_ITEM_CHECK_BLOCK))
- var/to_remove = charges >= 1 ? shield_state : broken_state
- if(to_remove)
- holder.cut_overlay(mutable_appearance('icons/effects/effects.dmi', to_remove), TRUE)
+ if(cached_vis_overlay)
+ SSvis_overlays.remove_vis_overlay(holder, cached_vis_overlay)
+ cached_vis_overlay = null
holder = null
/datum/component/shielded/proc/include_shield(mob/source, list/items)
diff --git a/code/datums/components/squeak.dm b/code/datums/components/squeak.dm
index 792222b27b..df44aef4de 100644
--- a/code/datums/components/squeak.dm
+++ b/code/datums/components/squeak.dm
@@ -11,6 +11,13 @@
// This is to stop squeak spam from inhand usage
var/last_use = 0
var/use_delay = 20
+
+ // squeak cooldowns
+ var/last_squeak = 0
+ var/squeak_delay = 5
+
+ /// chance we'll be stopped from squeaking by cooldown when something crossing us squeaks
+ var/cross_squeak_delay_chance = 33 // about 3 things can squeak at a time
/datum/component/squeak/Initialize(custom_sounds, volume_override, chance_override, step_delay_override, use_delay_override)
if(!isatom(parent))
@@ -19,6 +26,7 @@
if(ismovable(parent))
RegisterSignal(parent, list(COMSIG_MOVABLE_BUMP, COMSIG_MOVABLE_IMPACT), .proc/play_squeak)
RegisterSignal(parent, list(COMSIG_MOVABLE_CROSSED, COMSIG_ITEM_WEARERCROSSED), .proc/play_squeak_crossed)
+ RegisterSignal(parent, COMSIG_CROSS_SQUEAKED, .proc/delay_squeak)
RegisterSignal(parent, COMSIG_MOVABLE_DISPOSING, .proc/disposing_react)
if(isitem(parent))
RegisterSignal(parent, list(COMSIG_ITEM_ATTACK, COMSIG_ITEM_ATTACK_OBJ, COMSIG_ITEM_HIT_REACT), .proc/play_squeak)
@@ -39,20 +47,28 @@
use_delay = use_delay_override
/datum/component/squeak/proc/play_squeak()
+ do_play_squeak()
+
+/datum/component/squeak/proc/do_play_squeak(bypass_cooldown = FALSE)
+ if(!bypass_cooldown && ((last_squeak + squeak_delay) >= world.time))
+ return FALSE
if(prob(squeak_chance))
if(!override_squeak_sounds)
playsound(parent, pickweight(default_squeak_sounds), volume, 1, -1)
else
playsound(parent, pickweight(override_squeak_sounds), volume, 1, -1)
+ last_squeak = world.time
+ return TRUE
+ return FALSE
/datum/component/squeak/proc/step_squeak()
if(steps > step_delay)
- play_squeak()
+ do_play_squeak(TRUE)
steps = 0
else
steps++
-/datum/component/squeak/proc/play_squeak_crossed(atom/movable/AM)
+/datum/component/squeak/proc/play_squeak_crossed(datum/source, atom/movable/AM)
if(isitem(AM))
var/obj/item/I = AM
if(I.item_flags & ABSTRACT)
@@ -63,13 +79,18 @@
return
var/atom/current_parent = parent
if(isturf(current_parent.loc))
- play_squeak()
+ if(do_play_squeak())
+ SEND_SIGNAL(AM, COMSIG_CROSS_SQUEAKED)
/datum/component/squeak/proc/use_squeak()
if(last_use + use_delay < world.time)
last_use = world.time
play_squeak()
+/datum/component/squeak/proc/delay_squeak()
+ if(prob(cross_squeak_delay_chance))
+ last_squeak = world.time
+
/datum/component/squeak/proc/on_equip(datum/source, mob/equipper, slot)
RegisterSignal(equipper, COMSIG_MOVABLE_DISPOSING, .proc/disposing_react, TRUE)
diff --git a/code/datums/components/twohanded.dm b/code/datums/components/twohanded.dm
index 77a9a79bf4..bcbf979101 100644
--- a/code/datums/components/twohanded.dm
+++ b/code/datums/components/twohanded.dm
@@ -105,6 +105,8 @@
/// Triggered on attack self of the item containing the component
/datum/component/two_handed/proc/on_attack_self(datum/source, mob/user)
+ if(!user.is_holding(parent))
+ return //give no quarter to telekinesis powergaemrs (telekinetic wielding will desync the offhand and result in all sorts of bugs so no until someone codes it properly)
if(wielded)
unwield(user)
else
diff --git a/code/datums/components/uplink.dm b/code/datums/components/uplink.dm
index c7bbe01b2f..4a9c558d39 100644
--- a/code/datums/components/uplink.dm
+++ b/code/datums/components/uplink.dm
@@ -29,8 +29,8 @@ GLOBAL_LIST_EMPTY(uplinks)
var/saved_player_population = 0
var/list/filters = list()
-
-/datum/component/uplink/Initialize(_owner, _lockable = TRUE, _enabled = FALSE, datum/game_mode/_gamemode, starting_tc = 20, datum/ui_state/_checkstate, datum/traitor_class/traitor_class)
+
+/datum/component/uplink/Initialize(_owner, _lockable = TRUE, _enabled = FALSE, datum/game_mode/_gamemode, starting_tc = 20, datum/traitor_class/traitor_class)
if(!isitem(parent))
return COMPONENT_INCOMPATIBLE
@@ -143,23 +143,21 @@ GLOBAL_LIST_EMPTY(uplinks)
// an unlocked uplink blocks also opening the PDA or headset menu
return COMPONENT_NO_INTERACT
-/datum/component/uplink/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \
- datum/tgui/master_ui = null, datum/ui_state/state = GLOB.inventory_state)
+/datum/component/uplink/ui_state(mob/user)
+ if(istype(parent, /obj/item/implant/uplink))
+ return GLOB.not_incapacitated_state
+ return GLOB.inventory_state
+
+/datum/component/uplink/ui_interact(mob/user, datum/tgui/ui)
active = TRUE
- ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+ ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
- ui = new(user, src, ui_key, "Uplink", name, 620, 580, master_ui, state)
+ ui = new(user, src, "Uplink", name)
// This UI is only ever opened by one person,
// and never is updated outside of user input.
ui.set_autoupdate(FALSE)
ui.open()
-/datum/component/uplink/ui_host(mob/user)
- if(istype(parent, /obj/item/implant)) //implants are like organs, not really located inside mobs codewise.
- var/obj/item/implant/I = parent
- return I.imp_in
- return ..()
-
/datum/component/uplink/ui_data(mob/user)
if(!user.mind)
return
@@ -187,8 +185,18 @@ GLOBAL_LIST_EMPTY(uplinks)
is_inaccessible = FALSE
if(is_inaccessible)
continue
+ /*
+ if(I.restricted_species) //catpeople specfic gloves.
+ if(ishuman(user))
+ var/is_inaccessible = TRUE
+ var/mob/living/carbon/human/H = user
+ for(var/F in I.restricted_species)
+ if(F == H.dna.species.id || debug)
+ is_inaccessible = FALSE
+ break
if(is_inaccessible)
continue
+ */
cat["items"] += list(list(
"name" = I.name,
"cost" = I.cost,
diff --git a/code/datums/datacore.dm b/code/datums/datacore.dm
index cf942f077f..5aeb7626f6 100644
--- a/code/datums/datacore.dm
+++ b/code/datums/datacore.dm
@@ -1,13 +1,13 @@
-
+//TODO: someone please get rid of this shit
/datum/datacore
- var/medical[] = list()
+ var/list/medical = list()
var/medicalPrintCount = 0
- var/general[] = list()
- var/security[] = list()
+ var/list/general = list()
+ var/list/security = list()
var/securityPrintCount = 0
var/securityCrimeCounter = 0
- //This list tracks characters spawned in the world and cannot be modified in-game. Currently referenced by respawn_character().
- var/locked[] = list()
+ ///This list tracks characters spawned in the world and cannot be modified in-game. Currently referenced by respawn_character().
+ var/list/locked = list()
/datum/data
var/name = "data"
@@ -91,6 +91,42 @@
if(foundrecord)
foundrecord.fields["rank"] = assignment
+/datum/datacore/proc/get_manifest_tg() //copypasted from tg, renamed to avoid namespace conflicts
+ var/list/manifest_out = list()
+ var/list/departments = list(
+ "Command" = GLOB.command_positions,
+ "Security" = GLOB.security_positions,
+ "Engineering" = GLOB.engineering_positions,
+ "Medical" = GLOB.medical_positions,
+ "Science" = GLOB.science_positions,
+ "Supply" = GLOB.supply_positions,
+ "Service" = GLOB.civilian_positions,
+ "Silicon" = GLOB.nonhuman_positions
+ )
+ for(var/datum/data/record/t in GLOB.data_core.general)
+ var/name = t.fields["name"]
+ var/rank = t.fields["rank"]
+ var/has_department = FALSE
+ for(var/department in departments)
+ var/list/jobs = departments[department]
+ if(rank in jobs)
+ if(!manifest_out[department])
+ manifest_out[department] = list()
+ manifest_out[department] += list(list(
+ "name" = name,
+ "rank" = rank
+ ))
+ has_department = TRUE
+ break
+ if(!has_department)
+ if(!manifest_out["Misc"])
+ manifest_out["Misc"] = list()
+ manifest_out["Misc"] += list(list(
+ "name" = name,
+ "rank" = rank
+ ))
+ return manifest_out
+
/datum/datacore/proc/get_manifest(monochrome, OOC)
var/list/heads = list()
var/list/sec = list()
diff --git a/code/datums/datumvars.dm b/code/datums/datumvars.dm
index 348f9e6778..a91549ab4c 100644
--- a/code/datums/datumvars.dm
+++ b/code/datums/datumvars.dm
@@ -31,6 +31,9 @@
VV_DROPDOWN_OPTION(VV_HK_EXPOSE, "Show VV To Player")
VV_DROPDOWN_OPTION(VV_HK_ADDCOMPONENT, "Add Component/Element")
VV_DROPDOWN_OPTION(VV_HK_MODIFY_TRAITS, "Modify Traits")
+ #ifdef REFERENCE_TRACKING
+ VV_DROPDOWN_OPTION(VV_HK_VIEW_REFERENCES, "View References")
+ #endif
//This proc is only called if everything topic-wise is verified. The only verifications that should happen here is things like permission checks!
//href_list is a reference, modifying it in these procs WILL change the rest of the proc in topic.dm of admin/view_variables!
diff --git a/code/datums/diseases/advance/symptoms/fire.dm b/code/datums/diseases/advance/symptoms/fire.dm
index ea1897b67d..891d8a286a 100644
--- a/code/datums/diseases/advance/symptoms/fire.dm
+++ b/code/datums/diseases/advance/symptoms/fire.dm
@@ -113,9 +113,9 @@ Bonus
symptom_delay_max = 90
var/chems = FALSE
var/explosion_power = 1
- threshold_desc = "Resistance 9: Doubles the intensity of the effect, but reduces its frequency. \
- Stage Speed 8: Increases explosion radius when the host is wet. \
- Transmission 8: Additionally synthesizes chlorine trifluoride and napalm inside the host."
+ threshold_desc = list("Resistance 9" = "Doubles the intensity of the effect, but reduces its frequency.",
+ "Stage Speed 8" = "Increases explosion radius when the host is wet.",
+ "Transmission 8" = "Additionally synthesizes chlorine trifluoride and napalm inside the host.")
/datum/symptom/alkali/Start(datum/disease/advance/A)
if(!..())
diff --git a/code/datums/diseases/advance/symptoms/species.dm b/code/datums/diseases/advance/symptoms/species.dm
index 49a3cf8d07..2d5255842b 100644
--- a/code/datums/diseases/advance/symptoms/species.dm
+++ b/code/datums/diseases/advance/symptoms/species.dm
@@ -31,4 +31,3 @@
/datum/symptom/inorganic_adaptation/OnRemove(datum/disease/advance/A)
A.infectable_biotypes &= ~MOB_MINERAL
-
diff --git a/code/datums/dna.dm b/code/datums/dna.dm
index 8d80bc3f36..9486029fdd 100644
--- a/code/datums/dna.dm
+++ b/code/datums/dna.dm
@@ -50,6 +50,7 @@
destination.dna.skin_tone_override = skin_tone_override
destination.dna.features = features.Copy()
destination.set_species(species.type, icon_update=0)
+ destination.dna.species.say_mod = species.say_mod
destination.dna.real_name = real_name
destination.dna.nameless = nameless
destination.dna.custom_species = custom_species
@@ -74,6 +75,7 @@
new_dna.skin_tone_override = skin_tone_override
new_dna.features = features.Copy()
new_dna.species = new species.type
+ new_dna.species.say_mod = species.say_mod
new_dna.real_name = real_name
new_dna.nameless = nameless
new_dna.custom_species = custom_species
@@ -135,10 +137,10 @@
L[DNA_COLOR_TWO_BLOCK] = sanitize_hexcolor(features["mcolor2"], 6)
L[DNA_COLOR_THREE_BLOCK] = sanitize_hexcolor(features["mcolor3"], 6)
if(!GLOB.mam_tails_list.len)
- init_sprite_accessory_subtypes(/datum/sprite_accessory/mam_tails, GLOB.mam_tails_list)
+ init_sprite_accessory_subtypes(/datum/sprite_accessory/tails/mam_tails, GLOB.mam_tails_list)
L[DNA_MUTANTTAIL_BLOCK] = construct_block(GLOB.mam_tails_list.Find(features["mam_tail"]), GLOB.mam_tails_list.len)
if(!GLOB.mam_ears_list.len)
- init_sprite_accessory_subtypes(/datum/sprite_accessory/mam_ears, GLOB.mam_ears_list)
+ init_sprite_accessory_subtypes(/datum/sprite_accessory/ears/mam_ears, GLOB.mam_ears_list)
L[DNA_MUTANTEAR_BLOCK] = construct_block(GLOB.mam_ears_list.Find(features["mam_ears"]), GLOB.mam_ears_list.len)
if(!GLOB.mam_body_markings_list.len)
init_sprite_accessory_subtypes(/datum/sprite_accessory/mam_body_markings, GLOB.mam_body_markings_list)
diff --git a/code/datums/ductnet.dm b/code/datums/ductnet.dm
new file mode 100644
index 0000000000..14a74a67c4
--- /dev/null
+++ b/code/datums/ductnet.dm
@@ -0,0 +1,65 @@
+///We handle the unity part of plumbing. We track who is connected to who.
+/datum/ductnet
+ var/list/suppliers = list()
+ var/list/demanders = list()
+ var/list/obj/machinery/duct/ducts = list()
+
+ var/capacity
+///Add a duct to our network
+/datum/ductnet/proc/add_duct(obj/machinery/duct/D)
+ if(!D || (D in ducts))
+ return
+ ducts += D
+ D.duct = src
+///Remove a duct from our network and commit suicide, because this is probably easier than to check who that duct was connected to and what part of us was lost
+/datum/ductnet/proc/remove_duct(obj/machinery/duct/ducting)
+ destroy_network(FALSE)
+ for(var/obj/machinery/duct/D in ducting.neighbours)
+ addtimer(CALLBACK(D, /obj/machinery/duct/proc/reconnect), 0) //all needs to happen after the original duct that was destroyed finishes destroying itself
+ addtimer(CALLBACK(D, /obj/machinery/duct/proc/generate_connects), 0)
+ qdel(src)
+///add a plumbing object to either demanders or suppliers
+/datum/ductnet/proc/add_plumber(datum/component/plumbing/P, dir)
+ if(!P.can_add(src, dir))
+ return FALSE
+ P.ducts[num2text(dir)] = src
+ if(dir & P.supply_connects)
+ suppliers += P
+ else if(dir & P.demand_connects)
+ demanders += P
+ return TRUE
+///remove a plumber. we dont delete ourselves because ductnets dont persist through plumbing objects
+/datum/ductnet/proc/remove_plumber(datum/component/plumbing/P)
+ suppliers.Remove(P) //we're probably only in one of these, but Remove() is inherently sane so this is fine
+ demanders.Remove(P)
+
+ for(var/dir in P.ducts)
+ if(P.ducts[dir] == src)
+ P.ducts -= dir
+ if(!ducts.len) //there were no ducts, so it was a direct connection. we destroy ourselves since a ductnet with only one plumber and no ducts is worthless
+ destroy_network()
+///we combine ductnets. this occurs when someone connects to seperate sets of fluid ducts
+/datum/ductnet/proc/assimilate(datum/ductnet/D)
+ ducts.Add(D.ducts)
+ suppliers.Add(D.suppliers)
+ demanders.Add(D.demanders)
+ for(var/A in D.suppliers + D.demanders)
+ var/datum/component/plumbing/P = A
+ for(var/s in P.ducts)
+ if(P.ducts[s] != D)
+ continue
+ P.ducts[s] = src //all your ducts are belong to us
+ for(var/A in D.ducts)
+ var/obj/machinery/duct/M = A
+ M.duct = src //forget your old master
+
+ destroy_network()
+///destroy the network and tell all our ducts and plumbers we are gone
+/datum/ductnet/proc/destroy_network(delete=TRUE)
+ for(var/A in suppliers + demanders)
+ remove_plumber(A)
+ for(var/A in ducts)
+ var/obj/machinery/duct/D = A
+ D.duct = null
+ if(delete) //I don't want code to run with qdeleted objects because that can never be good, so keep this in-case the ductnet has some business left to attend to before commiting suicide
+ qdel(src)
diff --git a/code/datums/elements/bsa_blocker.dm b/code/datums/elements/bsa_blocker.dm
new file mode 100644
index 0000000000..61140ad0ed
--- /dev/null
+++ b/code/datums/elements/bsa_blocker.dm
@@ -0,0 +1,10 @@
+//blocks bluespace artillery beams that try to fly through
+//look not all elements need to be fancy
+/datum/element/bsa_blocker/Attach(datum/target)
+ if(!isatom(target))
+ return ELEMENT_INCOMPATIBLE
+ RegisterSignal(target, COMSIG_ATOM_BSA_BEAM, .proc/block_bsa)
+ return ..()
+
+/datum/element/bsa_blocker/proc/block_bsa()
+ return COMSIG_ATOM_BLOCKS_BSA_BEAM
diff --git a/code/datums/elements/decal.dm b/code/datums/elements/decal.dm
index 4bd482915a..a20d46c813 100644
--- a/code/datums/elements/decal.dm
+++ b/code/datums/elements/decal.dm
@@ -32,40 +32,43 @@
RegisterSignal(A, COMSIG_COMPONENT_CLEAN_ACT, .proc/clean_react)
if(description)
RegisterSignal(A, COMSIG_PARENT_EXAMINE, .proc/examine)
-
- apply(A, TRUE)
+ RegisterSignal(A, COMSIG_ATOM_UPDATE_OVERLAYS, .proc/apply_overlay, TRUE)
num_decals_per_atom[A]++
+ apply(A)
/datum/element/decal/Detach(datum/target)
var/atom/A = target
- remove(A, A.dir)
- UnregisterSignal(A, list(COMSIG_ATOM_DIR_CHANGE, COMSIG_COMPONENT_CLEAN_ACT, COMSIG_PARENT_EXAMINE))
- LAZYREMOVE(num_decals_per_atom, A)
+ num_decals_per_atom[A]--
+ if(!num_decals_per_atom[A])
+ UnregisterSignal(A, list(COMSIG_ATOM_DIR_CHANGE, COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZE,
+ COMSIG_COMPONENT_CLEAN_ACT, COMSIG_PARENT_EXAMINE, COMSIG_ATOM_UPDATE_OVERLAYS))
+ LAZYREMOVE(num_decals_per_atom, A)
+ apply(A)
return ..()
-/datum/element/decal/proc/remove(atom/target, old_dir)
- pic.dir = first_dir == NORTH ? target.dir : turn(first_dir, dir2angle(old_dir))
- for(var/i in 1 to num_decals_per_atom[target])
- target.cut_overlay(pic, TRUE)
+/datum/element/decal/proc/apply(atom/target)
+ if(target.flags_1 & INITIALIZED_1)
+ target.update_icon() //could use some queuing here now maybe.
+ else if(!QDELETED(target) && num_decals_per_atom[target] == 1)
+ RegisterSignal(target, COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZE, .proc/late_update_icon)
if(isitem(target))
addtimer(CALLBACK(target, /obj/item/.proc/update_slot_icon), 0, TIMER_UNIQUE)
-/datum/element/decal/proc/apply(atom/target, init = FALSE)
- pic.dir = first_dir == NORTH ? target.dir : turn(first_dir, dir2angle(target.dir))
- if(init)
- target.add_overlay(pic, TRUE)
- else
- for(var/i in 1 to num_decals_per_atom[target])
- target.add_overlay(pic, TRUE)
- if(isitem(target))
- addtimer(CALLBACK(target, /obj/item/.proc/update_slot_icon), 0, TIMER_UNIQUE)
+/datum/element/decal/proc/late_update_icon(atom/source)
+ source.update_icon()
+ UnregisterSignal(source,COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZE)
-/datum/element/decal/proc/rotate_react(datum/source, old_dir, new_dir)
+/datum/element/decal/proc/apply_overlay(atom/source, list/overlay_list)
+ if(first_dir)
+ pic.dir = first_dir == SOUTH ? source.dir : turn(first_dir, dir2angle(source.dir)-180) //Never turn a dir by 0.
+ for(var/i in 1 to num_decals_per_atom[source])
+ overlay_list += pic
+
+/datum/element/decal/proc/rotate_react(atom/source, old_dir, new_dir)
if(old_dir == new_dir)
return
- remove(source, old_dir)
- apply(source)
+ source.update_icon()
/datum/element/decal/proc/clean_react(datum/source, strength)
if(strength >= cleanable)
diff --git a/code/datums/elements/embed.dm b/code/datums/elements/embed.dm
index 8a7acde9cb..876414330f 100644
--- a/code/datums/elements/embed.dm
+++ b/code/datums/elements/embed.dm
@@ -2,7 +2,7 @@
The presence of this element allows an item (or a projectile carrying an item) to embed itself in a human or turf when it is thrown into a target (whether by hand, gun, or explosive wave) with either
at least 4 throwspeed (EMBED_THROWSPEED_THRESHOLD) or ignore_throwspeed_threshold set to TRUE. Items meant to be used as shrapnel for projectiles should have ignore_throwspeed_threshold set to true.
- Whether we're dealing with a direct /obj/item (throwing a knife at someone) or an /obj/projectile with a shrapnel_type, how we handle things plays out the same, with one extra step separating them.
+ Whether we're dealing with a direct /obj/item (throwing a knife at someone) or an /obj/item/projectile with a shrapnel_type, how we handle things plays out the same, with one extra step separating them.
Items simply make their COMSIG_MOVABLE_IMPACT or COMSIG_MOVABLE_IMPACT_ZONE check (against a closed turf or a carbon, respectively), while projectiles check on COMSIG_PROJECTILE_SELF_ON_HIT.
Upon a projectile hitting a valid target, it spawns whatever type of payload it has defined, then has that try to embed itself in the target on its own.
@@ -37,10 +37,10 @@
if(!isitem(target) && !isprojectile(target))
return ELEMENT_INCOMPATIBLE
+ RegisterSignal(target, COMSIG_ELEMENT_ATTACH, .proc/severancePackage)
if(isitem(target))
RegisterSignal(target, COMSIG_MOVABLE_IMPACT_ZONE, .proc/checkEmbedMob)
RegisterSignal(target, COMSIG_MOVABLE_IMPACT, .proc/checkEmbedOther)
- RegisterSignal(target, COMSIG_ELEMENT_ATTACH, .proc/severancePackage)
RegisterSignal(target, COMSIG_PARENT_EXAMINE, .proc/examined)
RegisterSignal(target, COMSIG_EMBED_TRY_FORCE, .proc/tryForceEmbed)
RegisterSignal(target, COMSIG_ITEM_DISABLE_EMBED, .proc/detachFromWeapon)
@@ -68,7 +68,7 @@
if(isitem(target))
UnregisterSignal(target, list(COMSIG_MOVABLE_IMPACT_ZONE, COMSIG_ELEMENT_ATTACH, COMSIG_MOVABLE_IMPACT, COMSIG_PARENT_EXAMINE, COMSIG_EMBED_TRY_FORCE, COMSIG_ITEM_DISABLE_EMBED))
else
- UnregisterSignal(target, list(COMSIG_PROJECTILE_SELF_ON_HIT))
+ UnregisterSignal(target, list(COMSIG_PROJECTILE_SELF_ON_HIT, COMSIG_ELEMENT_ATTACH))
/// Checking to see if we're gonna embed into a human
@@ -79,13 +79,13 @@
var/actual_chance = embed_chance
if(!weapon.isEmbedHarmless()) // all the armor in the world won't save you from a kick me sign
- var/armor = max(victim.run_armor_check(hit_zone, "bullet", silent=TRUE), victim.run_armor_check(hit_zone, "bomb", silent=TRUE)) // we'll be nice and take the better of bullet and bomb armor
+ var/armor = max(victim.run_armor_check(hit_zone, "bullet", silent=TRUE), victim.run_armor_check(hit_zone, "bomb", silent=TRUE)) * 0.5 // we'll be nice and take the better of bullet and bomb armor, halved
if(armor) // we only care about armor penetration if there's actually armor to penetrate
var/pen_mod = -armor + weapon.armour_penetration // even a little bit of armor can make a big difference for shrapnel with large negative armor pen
actual_chance += pen_mod // doing the armor pen as a separate calc just in case this ever gets expanded on
if(actual_chance <= 0)
- victim.visible_message("[weapon] bounces off [victim]'s armor!", "[weapon] bounces off your armor!", vision_distance = COMBAT_MESSAGE_RANGE)
+ victim.visible_message("[weapon] bounces off [victim]'s armor, unable to embed!", "[weapon] bounces off your armor, unable to embed!", vision_distance = COMBAT_MESSAGE_RANGE)
return
var/roll_embed = prob(actual_chance)
@@ -147,7 +147,7 @@
return TRUE
///A different embed element has been attached, so we'll detach and let them handle things
-/datum/element/embed/proc/severancePackage(obj/item/weapon, datum/element/E)
+/datum/element/embed/proc/severancePackage(obj/weapon, datum/element/E)
if(istype(E, /datum/element/embed))
Detach(weapon)
@@ -169,46 +169,35 @@
* it to call tryForceEmbed() on its own embed element (it's out of our hands here, our projectile is done), where it will run through all the checks it needs to.
*/
/datum/element/embed/proc/checkEmbedProjectile(obj/item/projectile/P, atom/movable/firer, atom/hit, angle, hit_zone)
- if(!iscarbon(hit) && !isclosedturf(hit))
+ if(!iscarbon(hit))
Detach(P)
return // we don't care
var/obj/item/payload = new payload_type(get_turf(hit))
- var/did_embed
- if(iscarbon(hit))
- var/mob/living/carbon/C = hit
- var/obj/item/bodypart/limb
- limb = C.get_bodypart(hit_zone)
- if(!limb)
- limb = C.get_bodypart()
- did_embed = payload.tryEmbed(limb)
- else
- did_embed = payload.tryEmbed(hit)
+ if(istype(payload, /obj/item/shrapnel/bullet))
+ payload.name = P.name
+ payload.embedding = P.embedding
+ payload.updateEmbedding()
+ var/mob/living/carbon/C = hit
+ var/obj/item/bodypart/limb = C.get_bodypart(hit_zone)
+ if(!limb)
+ limb = C.get_bodypart()
- if(!did_embed)
- payload.failedEmbed()
+ payload.tryEmbed(limb)
Detach(P)
/**
- * tryForceEmbed() is called here when we fire COMSIG_EMBED_TRY_FORCE from [/obj/item/proc/tryEmbed]. Mostly, this means we're a piece of shrapnel from a projectile that just impacted something, and we're trying to embed in it.
- *
- * The reason for this extra mucking about is avoiding having to do an extra hitby(), and annoying the target by impacting them once with the projectile, then again with the shrapnel (which likely represents said bullet), and possibly
- * AGAIN if we actually embed. This way, we save on at least one message. Runs the standard embed checks on the mob/turf.
- *
* Arguments:
- * * I- what we're trying to embed, obviously
- * * target- what we're trying to shish-kabob, either a bodypart, a carbon, or a closed turf
+ * * I- the item we're trying to insert into the target
+ * * target- what we're trying to shish-kabob, either a bodypart or a carbon
* * hit_zone- if our target is a carbon, try to hit them in this zone, if we don't have one, pick a random one. If our target is a bodypart, we already know where we're hitting.
* * forced- if we want this to succeed 100%
*/
/datum/element/embed/proc/tryForceEmbed(obj/item/I, atom/target, hit_zone, forced=FALSE)
var/obj/item/bodypart/limb
var/mob/living/carbon/C
- var/turf/closed/T
-
if(!forced && !prob(embed_chance))
return
-
if(iscarbon(target))
C = target
if(!hit_zone)
@@ -218,10 +207,5 @@
limb = target
hit_zone = limb.body_zone
C = limb.owner
- else if(isclosedturf(target))
- T = target
-
- if(C)
- return checkEmbedMob(I, C, hit_zone, forced=TRUE)
- else if(T)
- return checkEmbedOther(I, T, forced=TRUE)
+ checkEmbedMob(I, C, hit_zone, forced=TRUE)
+ return TRUE
diff --git a/code/datums/elements/photosynthesis.dm b/code/datums/elements/photosynthesis.dm
index 93e4369b8b..4fe0615b1f 100644
--- a/code/datums/elements/photosynthesis.dm
+++ b/code/datums/elements/photosynthesis.dm
@@ -60,7 +60,7 @@
if(L.stat == DEAD)
continue
if(light_nutrition_gain)
- L.adjust_nutrition(light_amount * light_nutrition_gain * attached_atoms[AM], NUTRITION_LEVEL_FULL)
+ L.adjust_nutrition(light_amount * light_nutrition_gain * attached_atoms[AM], NUTRITION_LEVEL_WELL_FED)
if(light_amount > bonus_lum || light_amount < malus_lum)
var/mult = ((light_amount > bonus_lum) ? 1 : -1) * attached_atoms[AM]
if(light_bruteheal)
diff --git a/code/datums/elements/polychromic.dm b/code/datums/elements/polychromic.dm
index 1c438c86c5..7ae0d04b05 100644
--- a/code/datums/elements/polychromic.dm
+++ b/code/datums/elements/polychromic.dm
@@ -61,7 +61,7 @@
A.AddElement(/datum/element/update_icon_updates_onmob)
RegisterSignal(A, COMSIG_ITEM_WORN_OVERLAYS, .proc/apply_worn_overlays)
if(suits_with_helmet_typecache[A.type])
- RegisterSignal(A, COMSIG_SUIT_MADE_HELMET, .proc/register_helmet)
+ RegisterSignal(A, COMSIG_SUIT_MADE_HELMET, .proc/register_helmet) //you better work now you slut
else if(_flags & POLYCHROMIC_ACTION && ismob(A)) //in the event mob update icon procs are ever standarized.
var/datum/action/polychromic/P = new(A)
RegisterSignal(P, COMSIG_ACTION_TRIGGER, .proc/activate_action)
@@ -166,6 +166,15 @@
examine_list += "Alt-click to recolor it."
/datum/element/polychromic/proc/register_helmet(atom/source, obj/item/clothing/head/H)
+ if(!isitem(H)) //backup in case if it messes up somehow
+ if(istype(source,/obj/item/clothing/suit/hooded)) //so how come it be like this, where toggleable headslots are named separately (helmet/hood) anyways?
+ var/obj/item/clothing/suit/hooded/sourcesuit = source
+ H = sourcesuit.hood
+ else if(istype(source,/obj/item/clothing/suit/space/hardsuit))
+ var/obj/item/clothing/suit/space/hardsuit/sourcesuit = source
+ H = sourcesuit.helmet
+ else
+ return
suit_by_helmet[H] = source
helmet_by_suit[source] = H
colors_by_atom[H] = colors_by_atom[source]
diff --git a/code/datums/emotes.dm b/code/datums/emotes.dm
index 6660bafcaf..e1147df225 100644
--- a/code/datums/emotes.dm
+++ b/code/datums/emotes.dm
@@ -102,8 +102,9 @@
/datum/emote/proc/can_run_emote(mob/user, status_check = TRUE, intentional = FALSE)
. = TRUE
- if(!is_type_in_typecache(user, mob_type_allowed_typecache))
- return FALSE
+ if(mob_type_allowed_typecache) //empty list = anyone can use it unless specifically blacklisted
+ if(!is_type_in_typecache(user, mob_type_allowed_typecache))
+ return FALSE
if(is_type_in_typecache(user, mob_type_blacklist_typecache))
return FALSE
if(status_check && !is_type_in_typecache(user, mob_type_ignore_stat_typecache))
diff --git a/code/datums/explosion.dm b/code/datums/explosion.dm
index dc32902e5e..246226ceba 100644
--- a/code/datums/explosion.dm
+++ b/code/datums/explosion.dm
@@ -103,13 +103,21 @@ GLOBAL_LIST_EMPTY(explosions)
// 3/7/14 will calculate to 80 + 35
var/far_dist = 0
- far_dist += heavy_impact_range * 5
+ far_dist += heavy_impact_range * 15 // Large explosions carry further
far_dist += devastation_range * 20
if(!silent)
var/frequency = get_rand_frequency()
var/sound/explosion_sound = sound(get_sfx("explosion"))
var/sound/far_explosion_sound = sound('sound/effects/explosionfar.ogg')
+ var/sound/creaking_explosion_sound = sound(get_sfx("explosion_creaking"))
+ var/sound/hull_creaking_sound = sound(get_sfx("hull_creaking"))
+ var/sound/explosion_echo_sound = sound('sound/effects/explosion_distant.ogg')
+ var/on_station = SSmapping.level_trait(epicenter.z, ZTRAIT_STATION)
+ var/creaking_explosion = FALSE
+
+ if(prob(devastation_range*30+heavy_impact_range*5) && on_station) // Huge explosions are near guaranteed to make the station creak and whine, smaller ones might.
+ creaking_explosion = TRUE // prob over 100 always returns true
for(var/mob/M in GLOB.player_list)
// Double check for client
@@ -126,11 +134,29 @@ GLOBAL_LIST_EMPTY(explosions)
shake_camera(M, 25, clamp(baseshakeamount, 0, 10))
// You hear a far explosion if you're outside the blast radius. Small bombs shouldn't be heard all over the station.
else if(dist <= far_dist)
- var/far_volume = clamp(far_dist, 30, 50) // Volume is based on explosion size and dist
- far_volume += (dist <= far_dist * 0.5 ? 50 : 0) // add 50 volume if the mob is pretty close to the explosion
- M.playsound_local(epicenter, null, far_volume, 1, frequency, falloff = 5, S = far_explosion_sound)
- if(baseshakeamount > 0)
+ var/far_volume = clamp(far_dist/2, 40, 60) // Volume is based on explosion size and dist
+ if(creaking_explosion)
+ M.playsound_local(epicenter, null, far_volume, 1, frequency, S = creaking_explosion_sound, distance_multiplier = 0)
+ else if(prob(75))
+ M.playsound_local(epicenter, null, far_volume, 1, frequency, S = far_explosion_sound, distance_multiplier = 0) // Far sound
+ else
+ M.playsound_local(epicenter, null, far_volume, 1, frequency, S = explosion_echo_sound, distance_multiplier = 0) // Echo sound
+
+ if(baseshakeamount > 0 || devastation_range)
+ if(!baseshakeamount) // Devastating explosions rock the station and ground
+ baseshakeamount = devastation_range*3
shake_camera(M, 10, clamp(baseshakeamount*0.25, 0, 2.5))
+
+ else if(M.can_hear() && !isspaceturf(get_turf(M)) && heavy_impact_range) // Big enough explosions echo throughout the hull
+ var/echo_volume = 40
+ if(devastation_range)
+ baseshakeamount = devastation_range
+ shake_camera(M, 10, clamp(baseshakeamount*0.25, 0, 2.5))
+ echo_volume = 60
+ M.playsound_local(epicenter, null, echo_volume, 1, frequency, S = explosion_echo_sound, distance_multiplier = 0)
+
+ if(creaking_explosion) // 5 seconds after the bang, the station begins to creak
+ addtimer(CALLBACK(M, /mob/proc/playsound_local, epicenter, null, rand(25, 40), 1, frequency, null, null, FALSE, hull_creaking_sound, null, null, null, null, 0), 5 SECONDS)
EX_PREPROCESS_CHECK_TICK
//postpone processing for a bit
@@ -196,14 +222,12 @@ GLOBAL_LIST_EMPTY(explosions)
//------- EX_ACT AND TURF FIRES -------
- if(T == epicenter) // Ensures explosives detonating from bags trigger other explosives in that bag
- var/list/items = list()
- for(var/I in T)
- var/atom/A = I
- if (!(A.flags_1 & PREVENT_CONTENTS_EXPLOSION_1)) //The atom/contents_explosion() proc returns null if the contents ex_acting has been handled by the atom, and TRUE if it hasn't.
- items += A.GetAllContents()
- for(var/O in items)
- var/atom/A = O
+ if((T == epicenter) && !QDELETED(explosion_source) && ismovable(explosion_source) && (get_turf(explosion_source) == T)) // Ensures explosives detonating from bags trigger other explosives in that bag
+ var/list/atoms = list()
+ for(var/atom/A in explosion_source.loc) // the ismovableatom check 2 lines above makes sure we don't nuke an /area
+ atoms += A
+ for(var/i in atoms)
+ var/atom/A = i
if(!QDELETED(A))
A.ex_act(dist)
diff --git a/code/datums/holocall.dm b/code/datums/holocall.dm
index 0f4f02ae2a..4bf02e8587 100644
--- a/code/datums/holocall.dm
+++ b/code/datums/holocall.dm
@@ -32,20 +32,29 @@
var/datum/action/innate/end_holocall/hangup //hangup action
var/call_start_time
+ var/head_call = FALSE //calls from a head of staff autoconnect, if the receiving pad is not secure.
//creates a holocall made by `caller` from `calling_pad` to `callees`
-/datum/holocall/New(mob/living/caller, obj/machinery/holopad/calling_pad, list/callees)
+/datum/holocall/New(mob/living/caller, obj/machinery/holopad/calling_pad, list/callees, elevated_access = FALSE)
call_start_time = world.time
user = caller
calling_pad.outgoing_call = src
calling_holopad = calling_pad
+ head_call = elevated_access
dialed_holopads = list()
for(var/I in callees)
var/obj/machinery/holopad/H = I
if(!QDELETED(H) && H.is_operational())
dialed_holopads += H
- H.say("Incoming call.")
+ if(head_call)
+ if(H.secure)
+ calling_pad.say("Auto-connection refused, falling back to call mode.")
+ H.say("Incoming call.")
+ else
+ H.say("Incoming connection.")
+ else
+ H.say("Incoming call.")
LAZYADD(H.holo_calls, src)
if(!dialed_holopads.len)
@@ -79,6 +88,7 @@
dialed_holopads.Cut()
if(calling_holopad)
+ calling_holopad.calling = FALSE
calling_holopad.outgoing_call = null
calling_holopad.SetLightsAndPower()
calling_holopad = null
@@ -145,6 +155,7 @@
if(!Check())
return
+ calling_holopad.calling = FALSE
hologram = H.activate_holo(user)
hologram.HC = src
@@ -160,6 +171,8 @@
hangup = new(eye, src)
hangup.Grant(user)
+ playsound(H, 'sound/machines/ping.ogg', 100)
+ H.say("Connection established.")
//Checks the validity of a holocall and qdels itself if it's not. Returns TRUE if valid, FALSE otherwise
/datum/holocall/proc/Check()
@@ -178,7 +191,6 @@
. = world.time < (call_start_time + HOLOPAD_MAX_DIAL_TIME)
if(!.)
calling_holopad.say("No answer received.")
- calling_holopad.temp = ""
if(!.)
testing("Holocall Check fail")
@@ -241,10 +253,10 @@
record.caller_image = holodiskOriginal.record.caller_image
record.entries = holodiskOriginal.record.entries.Copy()
record.language = holodiskOriginal.record.language
- to_chat(user, "You copy the record from [holodiskOriginal] to [src] by connecting the ports!")
+ to_chat(user, "You copy the record from [holodiskOriginal] to [src] by connecting the ports!")
name = holodiskOriginal.name
else
- to_chat(user, "[holodiskOriginal] has no record on it!")
+ to_chat(user, "[holodiskOriginal] has no record on it!")
..()
/obj/item/disk/holodisk/proc/build_record()
@@ -331,6 +343,21 @@
DELAY 20"}
/datum/preset_holoimage/engineer
+ outfit_type = /datum/outfit/job/engineer
+
+/datum/preset_holoimage/engineer/rig
+ outfit_type = /datum/outfit/job/engineer/gloved/rig
+
+/datum/preset_holoimage/engineer/ce
+ outfit_type = /datum/outfit/job/ce
+
+/datum/preset_holoimage/engineer/ce/rig
+ outfit_type = /datum/outfit/job/engineer/gloved/rig
+
+/datum/preset_holoimage/engineer/atmos
+ outfit_type = /datum/outfit/job/atmos
+
+/datum/preset_holoimage/engineer/atmos/rig
outfit_type = /datum/outfit/job/engineer/gloved/rig
/datum/preset_holoimage/researcher
@@ -350,3 +377,90 @@
/datum/preset_holoimage/clown
outfit_type = /datum/outfit/job/clown
+
+/obj/item/disk/holodisk/donutstation/whiteship
+ name = "Blackbox Print-out #DS024"
+ desc = "A holodisk containing the last viable recording of DS024's blackbox."
+ preset_image_type = /datum/preset_holoimage/engineer/ce
+ preset_record_text = {"
+ NAME Geysr Shorthalt
+ SAY Engine renovations complete and the ships been loaded. We all ready?
+ DELAY 25
+ PRESET /datum/preset_holoimage/engineer
+ NAME Jacob Ullman
+ SAY Lets blow this popsicle stand of a station.
+ DELAY 20
+ PRESET /datum/preset_holoimage/engineer/atmos
+ NAME Lindsey Cuffler
+ SAY Uh, sir? Shouldn't we call for a secondary shuttle? The bluespace drive on this thing made an awfully weird noise when we jumped here..
+ DELAY 30
+ PRESET /datum/preset_holoimage/engineer/ce
+ NAME Geysr Shorthalt
+ SAY Pah! Ship techie at the dock said to give it a good few kicks if it started acting up, let me just..
+ DELAY 25
+ SOUND punch
+ SOUND sparks
+ DELAY 10
+ SOUND punch
+ SOUND sparks
+ DELAY 10
+ SOUND punch
+ SOUND sparks
+ SOUND warpspeed
+ DELAY 15
+ PRESET /datum/preset_holoimage/engineer/atmos
+ NAME Lindsey Cuffler
+ SAY Uhh.. is it supposed to be doing that??
+ DELAY 15
+ PRESET /datum/preset_holoimage/engineer/ce
+ NAME Geysr Shorthalt
+ SAY See? Working as intended. Now, are we all ready?
+ DELAY 10
+ PRESET /datum/preset_holoimage/engineer
+ NAME Jacob Ullman
+ SAY Is it supposed to be glowing like that?
+ DELAY 20
+ SOUND explosion
+
+ "}
+
+/obj/item/disk/holodisk/ruin/snowengieruin
+ name = "Blackbox Print-out #EB412"
+ desc = "A holodisk containing the last moments of EB412. There's a bloody fingerprint on it."
+ preset_image_type = /datum/preset_holoimage/engineer
+ preset_record_text = {"
+ NAME Dave Tundrale
+ SAY Maria, how's Build?
+ DELAY 10
+ NAME Maria Dell
+ PRESET /datum/preset_holoimage/engineer/atmos
+ SAY It's fine, don't worry. I've got Plastic on it. And frankly, i'm kinda busy with, the, uhhm, incinerator.
+ DELAY 30
+ NAME Dave Tundrale
+ PRESET /datum/preset_holoimage/engineer
+ SAY Aight, wonderful. The science mans been kinda shit though. No RCDs-
+ DELAY 20
+ NAME Maria Dell
+ PRESET /datum/preset_holoimage/engineer/atmos
+ SAY Enough about your RCDs. They're not even that important, just bui-
+ DELAY 15
+ SOUND explosion
+ DELAY 10
+ SAY Oh, shit!
+ DELAY 10
+ PRESET /datum/preset_holoimage/engineer/atmos/rig
+ LANGUAGE /datum/language/narsie
+ NAME Unknown
+ SAY RISE, MY LORD!!
+ DELAY 10
+ LANGUAGE /datum/language/common
+ NAME Plastic
+ PRESET /datum/preset_holoimage/engineer/rig
+ SAY Fuck, fuck, fuck!
+ DELAY 20
+ SAY It's loose! CALL THE FUCKING SHUTT-
+ DELAY 10
+ PRESET /datum/preset_holoimage/corgi
+ NAME Blackbox Automated Message
+ SAY Connection lost. Dumping audio logs to disk.
+ DELAY 50"}
diff --git a/code/datums/hud.dm b/code/datums/hud.dm
index c1811fb9b3..1c23d5f2a0 100644
--- a/code/datums/hud.dm
+++ b/code/datums/hud.dm
@@ -28,7 +28,8 @@ GLOBAL_LIST_INIT(huds, list(
ANTAG_HUD_CLOCKWORK = new/datum/atom_hud/antag(),
ANTAG_HUD_BROTHER = new/datum/atom_hud/antag/hidden(),
ANTAG_HUD_BLOODSUCKER = new/datum/atom_hud/antag/bloodsucker(),
- ANTAG_HUD_FUGITIVE = new/datum/atom_hud/antag()
+ ANTAG_HUD_FUGITIVE = new/datum/atom_hud/antag(),
+ ANTAG_HUD_HERETIC = new/datum/atom_hud/antag/hidden()
))
/datum/atom_hud
diff --git a/code/datums/materials/basemats.dm b/code/datums/materials/basemats.dm
index 721af65449..76e60bc6dc 100644
--- a/code/datums/materials/basemats.dm
+++ b/code/datums/materials/basemats.dm
@@ -35,23 +35,24 @@ Unless you know what you're doing, only use the first three numbers. They're in
value_per_unit = 0.025
beauty_modifier = 0.075
-///Slight force increase
+///Slight force decrease. It's gold, it's soft as fuck.
/datum/material/gold
name = "gold"
desc = "Gold"
color = list(340/255, 240/255, 50/255,0, 0,0,0,0, 0,0,0,0, 0,0,0,1, 0,0,0,0) //gold is shiny, but not as bright as bananium
- strength_modifier = 1.2
+ strength_modifier = 0.8
categories = list(MAT_CATEGORY_ORE = TRUE, MAT_CATEGORY_RIGID = TRUE, MAT_CATEGORY_BASE_RECIPES = TRUE)
sheet_type = /obj/item/stack/sheet/mineral/gold
value_per_unit = 0.0625
beauty_modifier = 0.15
armor_modifiers = list("melee" = 1.1, "bullet" = 1.1, "laser" = 1.15, "energy" = 1.15, "bomb" = 1, "bio" = 1, "rad" = 1, "fire" = 0.7, "acid" = 1.1)
-///Has no special properties
+///Small force increase, for diamond swords
/datum/material/diamond
name = "diamond"
desc = "Highly pressurized carbon"
color = list(48/255, 272/255, 301/255,0, 0,0,0,0, 0,0,0,0, 0,0,0,1, 0,0,0,0)
+ strength_modifier = 1.1
alpha = 132
categories = list(MAT_CATEGORY_ORE = TRUE, MAT_CATEGORY_RIGID = TRUE, MAT_CATEGORY_BASE_RECIPES = TRUE)
sheet_type = /obj/item/stack/sheet/mineral/diamond
@@ -106,6 +107,7 @@ Unless you know what you're doing, only use the first three numbers. They're in
name = "bluespace crystal"
desc = "Crystals with bluespace properties"
color = list(119/255, 217/255, 396/255,0, 0,0,0,0, 0,0,0,0, 0,0,0,1, 0,0,0,0)
+ integrity_modifier = 0.2 //these things shatter when thrown.
alpha = 200
categories = list(MAT_CATEGORY_ORE = TRUE)
beauty_modifier = 0.5
@@ -139,7 +141,7 @@ Unless you know what you're doing, only use the first three numbers. They're in
name = "titanium"
desc = "Titanium"
color = "#b3c0c7"
- strength_modifier = 1.3
+ strength_modifier = 1.1
categories = list(MAT_CATEGORY_ORE = TRUE, MAT_CATEGORY_RIGID = TRUE, MAT_CATEGORY_BASE_RECIPES = TRUE)
sheet_type = /obj/item/stack/sheet/mineral/titanium
value_per_unit = 0.0625
@@ -203,7 +205,7 @@ Unless you know what you're doing, only use the first three numbers. They're in
name = "adamantine"
desc = "A powerful material made out of magic, I mean science!"
color = "#6d7e8e"
- strength_modifier = 1.5
+ strength_modifier = 1.3
categories = list(MAT_CATEGORY_RIGID = TRUE, MAT_CATEGORY_BASE_RECIPES = TRUE)
sheet_type = /obj/item/stack/sheet/mineral/adamantine
value_per_unit = 0.25
@@ -276,18 +278,31 @@ Unless you know what you're doing, only use the first three numbers. They're in
desc = "Mir'ntrath barhah Nar'sie."
color = "#3C3434"
categories = list(MAT_CATEGORY_RIGID = TRUE, MAT_CATEGORY_BASE_RECIPES = TRUE)
+ strength_modifier = 1.2
sheet_type = /obj/item/stack/sheet/runed_metal
value_per_unit = 0.75
armor_modifiers = list("melee" = 1.2, "bullet" = 1.2, "laser" = 1, "energy" = 1, "bomb" = 1.2, "bio" = 1.2, "rad" = 1.5, "fire" = 1.5, "acid" = 1.5)
beauty_modifier = -0.15
texture_layer_icon_state = "runed"
+/datum/material/brass
+ name = "brass"
+ desc = "Tybel gb-Ratvar"
+ color = "#917010"
+ categories = list(MAT_CATEGORY_RIGID = TRUE, MAT_CATEGORY_BASE_RECIPES = TRUE)
+ strength_modifier = 1.3 // Replicant Alloy is very good for skull beatings..
+ sheet_type = /obj/item/stack/tile/brass
+ value_per_unit = 0.75
+ armor_modifiers = list("melee" = 1.4, "bullet" = 1.4, "laser" = 0, "energy" = 0, "bomb" = 1.4, "bio" = 1.2, "rad" = 1.5, "fire" = 1.5, "acid" = 1.5) //But it has.. a few problems that can't easily be compensated for.
+ beauty_modifier = 0.3 //It really beats the cold plain plating of the station, doesn't it?
+
/datum/material/bronze
name = "bronze"
desc = "Clock Cult? Never heard of it."
color = "#92661A"
+ strength_modifier = 1.1
categories = list(MAT_CATEGORY_RIGID = TRUE, MAT_CATEGORY_BASE_RECIPES = TRUE)
- sheet_type = /obj/item/stack/tile/bronze
+ sheet_type = /obj/item/stack/sheet/bronze
value_per_unit = 0.025
armor_modifiers = list("melee" = 1, "bullet" = 1, "laser" = 1, "energy" = 1, "bomb" = 1, "bio" = 1, "rad" = 1.5, "fire" = 1.5, "acid" = 1.5)
beauty_modifier = 0.2
diff --git a/code/datums/mind.dm b/code/datums/mind.dm
index cb99fdc5a3..0731cf8bf4 100644
--- a/code/datums/mind.dm
+++ b/code/datums/mind.dm
@@ -41,6 +41,8 @@
var/special_role
var/list/restricted_roles = list()
+ var/hide_ckey = FALSE //hide ckey from round-end report
+
var/list/spell_list = list() // Wizard mode & "Give Spell" badmin button.
var/linglink
@@ -69,6 +71,7 @@
///What character we spawned in as- either at roundstart or latejoin, so we know for persistent scars if we ended as the same person or not
var/mob/original_character
+
/datum/mind/New(var/key)
skill_holder = new(src)
src.key = key
@@ -137,6 +140,8 @@
if(L.client?.prefs && L.client.prefs.auto_ooc && L.client.prefs.chat_toggles & CHAT_OOC)
DISABLE_BITFIELD(L.client.prefs.chat_toggles,CHAT_OOC)
+ hide_ckey = current.client?.prefs?.hide_ckey
+
SEND_SIGNAL(src, COMSIG_MIND_TRANSFER, new_character, old_character)
SEND_SIGNAL(new_character, COMSIG_MOB_ON_NEW_MIND)
@@ -780,6 +785,7 @@
if(!mind.name)
mind.name = real_name
mind.current = src
+ mind.hide_ckey = client?.prefs?.hide_ckey
/mob/living/carbon/mind_initialize()
..()
diff --git a/code/datums/mood_events/generic_negative_events.dm b/code/datums/mood_events/generic_negative_events.dm
index 787d6375a7..1abe077980 100644
--- a/code/datums/mood_events/generic_negative_events.dm
+++ b/code/datums/mood_events/generic_negative_events.dm
@@ -162,6 +162,11 @@
mood_change = -8
timeout = 3 MINUTES
+/datum/mood_event/gates_of_mansus
+ description = "LIVING IN A PERFORMANCE IS WORSE THAN DEATH\n"
+ mood_change = -25
+ timeout = 4 MINUTES
+
//These are unused so far but I want to remember them to use them later
/datum/mood_event/cloned_corpse
@@ -199,6 +204,11 @@
mood_change = -1
timeout = 2 MINUTES
+/datum/mood_event/plush_bite
+ description = "IT BIT ME!! OW!\n"
+ mood_change = -3
+ timeout = 2 MINUTES
+
//Cursed stuff below
/datum/mood_event/emptypred
diff --git a/code/datums/mood_events/generic_positive_events.dm b/code/datums/mood_events/generic_positive_events.dm
index 8f98917ed3..b73756c2b1 100644
--- a/code/datums/mood_events/generic_positive_events.dm
+++ b/code/datums/mood_events/generic_positive_events.dm
@@ -70,6 +70,11 @@
mood_change = 40 //maybe being a cultist isnt that bad after all
hidden = TRUE
+/datum/mood_event/heretics
+ description = "THE HIGHER I RISE, THE MORE I SEE.\n"
+ mood_change = 12 //maybe being a cultist isnt that bad after all
+ hidden = TRUE
+
/datum/mood_event/family_heirloom
description = "My family heirloom is safe with me.\n"
mood_change = 1
diff --git a/code/datums/mood_events/needs_events.dm b/code/datums/mood_events/needs_events.dm
index 962681eb94..b307bcb0aa 100644
--- a/code/datums/mood_events/needs_events.dm
+++ b/code/datums/mood_events/needs_events.dm
@@ -19,6 +19,27 @@
description = "I'm starving!\n"
mood_change = -15
+//charge
+/datum/mood_event/supercharged
+ description = "I can't possibly keep all this power inside, I need to release some quick!\n"
+ mood_change = -10
+
+/datum/mood_event/overcharged
+ description = "I feel dangerously overcharged, perhaps I should release some power.\n"
+ mood_change = -4
+
+/datum/mood_event/charged
+ description = "I feel the power in my veins!\n"
+ mood_change = 6
+
+/datum/mood_event/lowpower
+ description = "My power is running low, I should go charge up somewhere.\n"
+ mood_change = -6
+
+/datum/mood_event/decharged
+ description = "I'm in desperate need of some electricity!\n"
+ mood_change = -10
+
//Disgust
/datum/mood_event/gross
description = "I saw something gross.\n"
diff --git a/code/datums/mutations/_mutations.dm b/code/datums/mutations/_mutations.dm
index fb5f6ed5f6..3bddfdaa63 100644
--- a/code/datums/mutations/_mutations.dm
+++ b/code/datums/mutations/_mutations.dm
@@ -11,7 +11,7 @@
var/lowest_value = 256 * 8
var/text_gain_indication = ""
var/text_lose_indication = ""
- var/list/mutable_appearance/visual_indicators = list()
+ var/list/visual_indicators = list()
var/obj/effect/proc_holder/spell/power
var/layer_used = MUTATIONS_LAYER //which mutation layer to use
var/list/species_allowed = list() //to restrict mutation to only certain species
diff --git a/code/datums/mutations/actions.dm b/code/datums/mutations/actions.dm
index 7e6d567cfa..bd9ecaeeaa 100644
--- a/code/datums/mutations/actions.dm
+++ b/code/datums/mutations/actions.dm
@@ -410,7 +410,7 @@
throw_speed = 4
embedding = list("embedded_pain_multiplier" = 4, "embed_chance" = 100, "embedded_fall_chance" = 0)
w_class = WEIGHT_CLASS_SMALL
- sharpness = IS_SHARP
+ sharpness = SHARP_POINTY
var/mob/living/carbon/human/fired_by
/// if we missed our target
var/missed = TRUE
diff --git a/code/datums/mutations/antenna.dm b/code/datums/mutations/antenna.dm
index 978802fd80..ad08b8ebdc 100644
--- a/code/datums/mutations/antenna.dm
+++ b/code/datums/mutations/antenna.dm
@@ -90,6 +90,8 @@
to_chat(user, "You catch some drifting memories of their past conversations...")
for(var/spoken_memory in recent_speech)
to_chat(user, "[recent_speech[spoken_memory]]")
+ if(usr in GLOB.rockpaperscissors_players)
+ to_chat(user, "They're planning on playing [GLOB.rockpaperscissors_players[usr][1]]")
if(iscarbon(M))
var/mob/living/carbon/human/H = M
to_chat(user, "You find that their intent is to [H.a_intent]...")
diff --git a/code/datums/mutations/hulk.dm b/code/datums/mutations/hulk.dm
index bc7b9171a8..5d363124d9 100644
--- a/code/datums/mutations/hulk.dm
+++ b/code/datums/mutations/hulk.dm
@@ -27,6 +27,24 @@
target.attack_hulk(owner)
return INTERRUPT_UNARMED_ATTACK | NO_AUTO_CLICKDELAY_HANDLING
+/**
+ *Checks damage of a hulk's arm and applies bone wounds as necessary.
+ *
+ *Called by specific atoms being attacked, such as walls. If an atom
+ *does not call this proc, than punching that atom will not cause
+ *arm breaking (even if the atom deals recoil damage to hulks).
+ *Arguments:
+ *arg1 is the arm to evaluate damage of and possibly break.
+ */
+/datum/mutation/human/hulk/proc/break_an_arm(obj/item/bodypart/arm)
+ switch(arm.brute_dam)
+ if(45 to 50)
+ arm.force_wound_upwards(/datum/wound/blunt/critical)
+ if(41 to 45)
+ arm.force_wound_upwards(/datum/wound/blunt/severe)
+ if(35 to 41)
+ arm.force_wound_upwards(/datum/wound/blunt/moderate)
+
/datum/mutation/human/hulk/on_life()
if(owner.health < 0)
on_losing(owner)
diff --git a/code/datums/radiation_wave.dm b/code/datums/radiation_wave.dm
index fe018a8761..8dc3b64e16 100644
--- a/code/datums/radiation_wave.dm
+++ b/code/datums/radiation_wave.dm
@@ -105,7 +105,6 @@
// modify the ignored_things list in __HELPERS/radiation.dm instead
var/static/list/blacklisted = typecacheof(list(
/turf,
- /mob,
/obj/structure/cable,
/obj/machinery/atmospherics,
/obj/item/ammo_casing,
diff --git a/code/datums/ruins/space.dm b/code/datums/ruins/space.dm
index 5d86290f8f..64b8692ccd 100644
--- a/code/datums/ruins/space.dm
+++ b/code/datums/ruins/space.dm
@@ -325,6 +325,12 @@
name = "Abductor Replication Lab"
description = "Some scientists tried and almost succeeded to recreate abductor tools. Somewhat slower and a bit less modern than their originals, these tools are the best you can get if you aren't an alien."
+/datum/map_template/ruin/space/spacediner
+ id = "spacediner"
+ suffix = "spacediner.dmm"
+ name = "Space Diner"
+ description = "Come, traveler of the bluespace planes. Sit, enjoy a drink and take one of the fair maidens for a night. The exit is the way you came in, via that teleporter thingy, but do remember to stay safe."
+
//Space ruins for the station z
/datum/map_template/ruin/spacenearstation
prefix = "_maps/RandomRuins/SpaceRuinsStation/"
diff --git a/code/datums/skills/_check_skills.dm b/code/datums/skills/_check_skills.dm
index 41939dbee3..91b36a123f 100644
--- a/code/datums/skills/_check_skills.dm
+++ b/code/datums/skills/_check_skills.dm
@@ -14,11 +14,14 @@
mind.skill_holder.ui_interact(src)
-/datum/skill_holder/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.always_state)
- ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+/datum/skill_holder/ui_state(mob/user)
+ return GLOB.always_state
+
+/datum/skill_holder/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
- ui = new(user, src, ui_key, "SkillPanel", "[owner.name]'s Skills", 620, 580, master_ui, state)
- ui.set_autoupdate(FALSE) // This UI is only ever opened by one person, and never is updated outside of user input.
+ ui = new(user, src, "SkillPanel", "[owner.name]'s Skills")
+ ui.set_autoupdate(FALSE)
ui.open()
else if(need_static_data_update)
update_static_data(user)
diff --git a/code/datums/skills/_skill_holder.dm b/code/datums/skills/_skill_holder.dm
index 73748417c3..83c95c6439 100644
--- a/code/datums/skills/_skill_holder.dm
+++ b/code/datums/skills/_skill_holder.dm
@@ -110,7 +110,7 @@
/**
* Automatic skill increase, multiplied by skill affinity if existing.
- * Only works if skill is numerical.
+ * Only works if skill is numerical or levelled..
*/
/datum/mind/proc/auto_gain_experience(skill, value, maximum, silent = FALSE)
if(!ispath(skill, /datum/skill))
diff --git a/code/datums/skills/blacksmithing.dm b/code/datums/skills/blacksmithing.dm
new file mode 100644
index 0000000000..0bddae5562
--- /dev/null
+++ b/code/datums/skills/blacksmithing.dm
@@ -0,0 +1,6 @@
+/datum/skill/level/dorfy/blacksmithing
+ name = "Blacksmithing"
+ desc = "Making metal into fancy shapes using heat and force. Higher levels increase both your working speed at an anvil as well as the quality of your works."
+ name_color = COLOR_FLOORTILE_GRAY
+ skill_traits = list(SKILL_SANITY, SKILL_INTELLIGENCE, SKILL_USE_TOOL, SKILL_TRAINING_TOOL)
+ ui_category = SKILL_UI_CAT_MISC
diff --git a/code/datums/spawners_menu.dm b/code/datums/spawners_menu.dm
index d66445b8a1..95a7d8e633 100644
--- a/code/datums/spawners_menu.dm
+++ b/code/datums/spawners_menu.dm
@@ -6,10 +6,13 @@
qdel(src)
owner = new_owner
-/datum/spawners_menu/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.observer_state)
- ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+/datum/spawners_menu/ui_state(mob/user)
+ return GLOB.observer_state
+
+/datum/spawners_menu/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
- ui = new(user, src, ui_key, "SpawnersMenu", "Spawners Menu", 700, 600, master_ui, state)
+ ui = new(user, src, "SpawnersMenu")
ui.open()
/datum/spawners_menu/ui_data(mob/user)
@@ -42,11 +45,15 @@
if(..())
return
- var/spawner_ref = pick(GLOB.mob_spawners[params["name"]])
- var/obj/effect/mob_spawn/MS = locate(spawner_ref) in GLOB.poi_list
- if(!MS)
+ var/group_name = params["name"]
+ if(!group_name || !(group_name in GLOB.mob_spawners))
+ return
+ var/list/spawnerlist = GLOB.mob_spawners[group_name]
+ if(!spawnerlist.len)
+ return
+ var/obj/effect/mob_spawn/MS = pick(spawnerlist)
+ if(!istype(MS) || !(MS in GLOB.poi_list))
return
-
switch(action)
if("jump")
if(MS)
@@ -55,4 +62,4 @@
if("spawn")
if(MS)
MS.attack_ghost(owner)
- . = TRUE
\ No newline at end of file
+ . = TRUE
diff --git a/code/datums/status_effects/buffs.dm b/code/datums/status_effects/buffs.dm
index 179ed765c5..ef3979d822 100644
--- a/code/datums/status_effects/buffs.dm
+++ b/code/datums/status_effects/buffs.dm
@@ -580,4 +580,63 @@
/datum/status_effect/regenerative_core/on_remove()
. = ..()
REMOVE_TRAIT(owner, TRAIT_IGNOREDAMAGESLOWDOWN, "regenerative_core")
- owner.updatehealth()
\ No newline at end of file
+ owner.updatehealth()
+
+/datum/status_effect/panacea
+ id = "Anatomic Panacea"
+ duration = 100
+ tick_interval = 10
+ alert_type = /obj/screen/alert/status_effect/panacea
+
+/obj/screen/alert/status_effect/panacea
+ name = "Panacea"
+ desc = "We purge the impurities from our body."
+ icon_state = "panacea"
+
+// Changeling's anatomic panacea now in buff form. Directly fixes issues instead of injecting chems
+/datum/status_effect/panacea/tick()
+ var/mob/living/carbon/M = owner
+
+ //Heal brain damage and toxyloss, alongside trauma
+ owner.adjustOrganLoss(ORGAN_SLOT_BRAIN, -8)
+ owner.adjustToxLoss(-6, forced = TRUE)
+ M.cure_trauma_type(resilience = TRAUMA_RESILIENCE_BASIC)
+ //Purges 50 rads per tick
+ if(owner.radiation > 0)
+ owner.radiation -= min(owner.radiation, 50)
+ //Mutadone effects
+ owner.jitteriness = 0
+ if(owner.has_dna())
+ M.dna.remove_all_mutations(mutadone = TRUE)
+ if(!QDELETED(owner)) //We were a monkey, now a human
+ ..()
+ // Purges toxins
+ for(var/datum/reagent/toxin/R in owner.reagents.reagent_list)
+ owner.reagents.remove_reagent(R.type, 5)
+ //Antihol effects
+ M.reagents.remove_all_type(/datum/reagent/consumable/ethanol, 10, 0, 1)
+ M.drunkenness = max(M.drunkenness - 10, 0)
+ owner.dizziness = 0
+ owner.drowsyness = 0
+ owner.slurring = 0
+ owner.confused = 0
+ //Organ and disease cure moved from panacea.dm to buff proc
+ var/list/bad_organs = list(
+ owner.getorgan(/obj/item/organ/body_egg),
+ owner.getorgan(/obj/item/organ/zombie_infection))
+ for(var/o in bad_organs)
+ var/obj/item/organ/O = o
+ if(!istype(O))
+ continue
+ O.Remove()
+ if(iscarbon(owner))
+ var/mob/living/carbon/C = owner
+ C.vomit(0, toxic = TRUE)
+ O.forceMove(get_turf(owner))
+ if(isliving(owner))
+ var/mob/living/L = owner
+ for(var/thing in L.diseases)
+ var/datum/disease/D = thing
+ if(D.severity == DISEASE_SEVERITY_POSITIVE)
+ continue
+ D.cure()
diff --git a/code/datums/status_effects/debuffs.dm b/code/datums/status_effects/debuffs.dm
index aa27c6424e..a2f534115a 100644
--- a/code/datums/status_effects/debuffs.dm
+++ b/code/datums/status_effects/debuffs.dm
@@ -97,6 +97,21 @@
duration = set_duration
return ..()
+/datum/status_effect/off_balance
+ id = "offbalance"
+ alert_type = null
+
+/datum/status_effect/off_balance/on_creation(mob/living/new_owner, set_duration)
+ if(isnum(set_duration))
+ duration = set_duration
+ return ..()
+
+/datum/status_effect/off_balance/on_remove()
+ var/active_item = owner.get_active_held_item()
+ if(is_type_in_typecache(active_item, GLOB.shove_disarming_types))
+ owner.visible_message("[owner.name] regains their grip on \the [active_item]!", "You regain your grip on \the [active_item]", null, COMBAT_MESSAGE_RANGE)
+ return ..()
+
/obj/screen/alert/status_effect/asleep
name = "Asleep"
desc = "You've fallen asleep. Wait a bit and you should wake up. Unless you don't, considering how helpless you are."
@@ -388,6 +403,229 @@
owner.underlays -= marked_underlay //if this is being called, we should have an owner at this point.
..()
+/datum/status_effect/eldritch
+ duration = 15 SECONDS
+ status_type = STATUS_EFFECT_REPLACE
+ alert_type = null
+ on_remove_on_mob_delete = TRUE
+ ///underlay used to indicate that someone is marked
+ var/mutable_appearance/marked_underlay
+ ///path for the underlay
+ var/effect_sprite = ""
+
+/datum/status_effect/eldritch/on_creation(mob/living/new_owner, ...)
+ marked_underlay = mutable_appearance('icons/effects/effects.dmi', effect_sprite,BELOW_MOB_LAYER)
+ return ..()
+
+/datum/status_effect/eldritch/on_apply()
+ . = ..()
+ if(owner.mob_size >= MOB_SIZE_HUMAN)
+ RegisterSignal(owner,COMSIG_ATOM_UPDATE_OVERLAYS,.proc/update_owner_underlay)
+ owner.update_icon()
+ return TRUE
+ return FALSE
+
+/datum/status_effect/eldritch/on_remove()
+ UnregisterSignal(owner,COMSIG_ATOM_UPDATE_OVERLAYS)
+ owner.update_icon()
+ return ..()
+
+/datum/status_effect/eldritch/proc/update_owner_underlay(atom/source, list/overlays)
+ overlays += marked_underlay
+
+/datum/status_effect/eldritch/Destroy()
+ QDEL_NULL(marked_underlay)
+ return ..()
+
+/**
+ * What happens when this mark gets popped
+ *
+ * Adds actual functionality to each mark
+ */
+/datum/status_effect/eldritch/proc/on_effect()
+ playsound(owner, 'sound/magic/repulse.ogg', 75, TRUE)
+ qdel(src) //what happens when this is procced.
+
+//Each mark has diffrent effects when it is destroyed that combine with the mansus grasp effect.
+/datum/status_effect/eldritch/flesh
+ id = "flesh_mark"
+ effect_sprite = "emark1"
+
+/datum/status_effect/eldritch/flesh/on_effect()
+
+ if(ishuman(owner))
+ var/mob/living/carbon/human/H = owner
+ var/obj/item/bodypart/bodypart = pick(H.bodyparts)
+ var/datum/wound/slash/severe/crit_wound = new
+ crit_wound.apply_wound(bodypart)
+ return ..()
+
+/datum/status_effect/eldritch/ash
+ id = "ash_mark"
+ effect_sprite = "emark2"
+ ///Dictates how much damage and stamina loss this mark will cause.
+ var/repetitions = 1
+
+/datum/status_effect/eldritch/ash/on_creation(mob/living/new_owner, _repetition = 5)
+ . = ..()
+ repetitions = min(1,_repetition)
+
+/datum/status_effect/eldritch/ash/on_effect()
+ if(iscarbon(owner))
+ var/mob/living/carbon/carbon_owner = owner
+ carbon_owner.adjustStaminaLoss(10 * repetitions)
+ carbon_owner.adjustFireLoss(5 * repetitions)
+ for(var/mob/living/carbon/victim in range(1,carbon_owner))
+ if(IS_HERETIC(victim) || victim == carbon_owner)
+ continue
+ victim.apply_status_effect(type,repetitions-1)
+ break
+ return ..()
+
+/datum/status_effect/eldritch/rust
+ id = "rust_mark"
+ effect_sprite = "emark3"
+
+/datum/status_effect/eldritch/rust/on_effect()
+ if(!iscarbon(owner))
+ return
+ var/mob/living/carbon/carbon_owner = owner
+ for(var/obj/item/I in carbon_owner.get_all_gear()) //Affects roughly 75% of items
+ if(!QDELETED(I) && prob(75)) //Just in case
+ I.take_damage(100)
+ return ..()
+
+/datum/status_effect/corrosion_curse
+ id = "corrosion_curse"
+ status_type = STATUS_EFFECT_REPLACE
+ alert_type = null
+ tick_interval = 1 SECONDS
+
+/datum/status_effect/corrosion_curse/on_creation(mob/living/new_owner, ...)
+ . = ..()
+ to_chat(owner, "Your feel your body starting to break apart...")
+
+/datum/status_effect/corrosion_curse/tick()
+ . = ..()
+ if(!ishuman(owner))
+ return
+ var/mob/living/carbon/human/H = owner
+ var/chance = rand(0,100)
+ switch(chance)
+ if(0 to 19)
+ H.vomit()
+ if(20 to 29)
+ H.Dizzy(10)
+ if(30 to 39)
+ H.adjustOrganLoss(ORGAN_SLOT_LIVER,5)
+ if(40 to 49)
+ H.adjustOrganLoss(ORGAN_SLOT_HEART,5)
+ if(50 to 59)
+ H.adjustOrganLoss(ORGAN_SLOT_STOMACH,5)
+ if(60 to 69)
+ H.adjustOrganLoss(ORGAN_SLOT_EYES,10)
+ if(70 to 79)
+ H.adjustOrganLoss(ORGAN_SLOT_EARS,10)
+ if(80 to 89)
+ H.adjustOrganLoss(ORGAN_SLOT_LUNGS,10)
+ if(90 to 99)
+ H.adjustOrganLoss(ORGAN_SLOT_TONGUE,10)
+ if(100)
+ H.adjustOrganLoss(ORGAN_SLOT_BRAIN,20)
+
+/datum/status_effect/corrosion_curse/lesser
+ id = "corrosion_curse_lesser"
+ duration = 20 SECONDS
+
+/datum/status_effect/corrosion_curse/lesser/tick()
+ . = ..()
+ if(!ishuman(owner))
+ return
+ var/mob/living/carbon/human/H = owner
+ var/chance = rand(0,100)
+ switch(chance)
+ if(0 to 19)
+ H.adjustBruteLoss(6)
+ if(20 to 29)
+ H.Dizzy(10)
+ if(30 to 39)
+ H.adjustOrganLoss(ORGAN_SLOT_LIVER,2)
+ if(40 to 49)
+ H.adjustOrganLoss(ORGAN_SLOT_HEART,2)
+ if(50 to 59)
+ H.adjustOrganLoss(ORGAN_SLOT_STOMACH,2)
+ if(60 to 69)
+ H.adjustOrganLoss(ORGAN_SLOT_EYES,5)
+ if(70 to 79)
+ H.adjustOrganLoss(ORGAN_SLOT_EARS,5)
+ if(80 to 89)
+ H.adjustOrganLoss(ORGAN_SLOT_LUNGS,5)
+ if(90 to 99)
+ H.adjustOrganLoss(ORGAN_SLOT_TONGUE,5)
+ if(100)
+ H.adjustOrganLoss(ORGAN_SLOT_BRAIN,10)
+
+/datum/status_effect/amok
+ id = "amok"
+ status_type = STATUS_EFFECT_REPLACE
+ alert_type = null
+ duration = 10 SECONDS
+ tick_interval = 1 SECONDS
+
+/datum/status_effect/amok/on_apply(mob/living/afflicted)
+ . = ..()
+ to_chat(owner, "Your feel filled with a rage that is not your own!")
+
+/datum/status_effect/amok/tick()
+ . = ..()
+ var/prev_intent = owner.a_intent
+ owner.a_intent = INTENT_HARM
+
+ var/list/mob/living/targets = list()
+ for(var/mob/living/potential_target in oview(owner, 1))
+ if(IS_HERETIC(potential_target) || potential_target.mind?.has_antag_datum(/datum/antagonist/heretic_monster))
+ continue
+ targets += potential_target
+ if(LAZYLEN(targets))
+ owner.log_message(" attacked someone due to the amok debuff.", LOG_ATTACK) //the following attack will log itself
+ owner.ClickOn(pick(targets))
+ owner.a_intent = prev_intent
+
+/datum/status_effect/cloudstruck
+ id = "cloudstruck"
+ status_type = STATUS_EFFECT_REPLACE
+ duration = 3 SECONDS
+ on_remove_on_mob_delete = TRUE
+ ///This overlay is applied to the owner for the duration of the effect.
+ var/mutable_appearance/mob_overlay
+
+/datum/status_effect/cloudstruck/on_creation(mob/living/new_owner, set_duration)
+ if(isnum(set_duration))
+ duration = set_duration
+ . = ..()
+
+/datum/status_effect/cloudstruck/on_apply()
+ . = ..()
+ mob_overlay = mutable_appearance('icons/effects/eldritch.dmi', "cloud_swirl", ABOVE_MOB_LAYER)
+ owner.overlays += mob_overlay
+ owner.update_icon()
+ ADD_TRAIT(owner, TRAIT_BLIND, "cloudstruck")
+ return TRUE
+
+/datum/status_effect/cloudstruck/on_remove()
+ . = ..()
+ if(QDELETED(owner))
+ return
+ REMOVE_TRAIT(owner, TRAIT_BLIND, "cloudstruck")
+ if(owner)
+ owner.overlays -= mob_overlay
+ owner.update_icon()
+
+/datum/status_effect/cloudstruck/Destroy()
+ . = ..()
+ QDEL_NULL(mob_overlay)
+
+
/datum/status_effect/stacking/saw_bleed
id = "saw_bleed"
tick_interval = 6
@@ -433,7 +671,7 @@
var/still_bleeding = FALSE
for(var/thing in throat.wounds)
var/datum/wound/W = thing
- if(W.wound_type == WOUND_LIST_CUT && W.severity > WOUND_SEVERITY_MODERATE)
+ if(W.wound_type == WOUND_SLASH && W.severity > WOUND_SEVERITY_MODERATE)
still_bleeding = TRUE
break
if(!still_bleeding)
@@ -547,8 +785,8 @@
owner.DefaultCombatKnockdown(15, TRUE, FALSE, 15)
if(iscarbon(owner))
var/mob/living/carbon/C = owner
- C.silent = max(2, C.silent)
- C.stuttering = max(5, C.stuttering)
+ C.silent = max(5, C.silent) //Increased, now lasts until five seconds after it ends, instead of 2
+ C.stuttering = max(10, C.stuttering) //Increased, now lasts for five seconds after the mute ends, instead of 3
if(!old_health)
old_health = owner.health
if(!old_oxyloss)
diff --git a/code/datums/status_effects/wound_effects.dm b/code/datums/status_effects/wound_effects.dm
index 91440d36de..045b1b257d 100644
--- a/code/datums/status_effects/wound_effects.dm
+++ b/code/datums/status_effects/wound_effects.dm
@@ -117,8 +117,7 @@
/datum/status_effect/wound/on_creation(mob/living/new_owner, incoming_wound)
. = ..()
- var/datum/wound/W = incoming_wound
- linked_wound = W
+ linked_wound = incoming_wound
linked_limb = linked_wound.limb
/datum/status_effect/wound/on_remove()
@@ -140,9 +139,9 @@
// bones
-/datum/status_effect/wound/bone
+/datum/status_effect/wound/blunt
-/datum/status_effect/wound/bone/interact_speed_modifier()
+/datum/status_effect/wound/blunt/interact_speed_modifier()
var/mob/living/carbon/C = owner
if(C.get_active_hand() == linked_limb)
@@ -151,7 +150,7 @@
return 1
-/datum/status_effect/wound/bone/action_cooldown_mod()
+/datum/status_effect/wound/blunt/action_cooldown_mod()
var/mob/living/carbon/C = owner
if(C.get_active_hand() == linked_limb)
@@ -159,24 +158,34 @@
return 1
-/datum/status_effect/wound/bone/moderate
+/datum/status_effect/wound/blunt/moderate
id = "disjoint"
-/datum/status_effect/wound/bone/severe
+/datum/status_effect/wound/blunt/severe
id = "hairline"
-/datum/status_effect/wound/bone/critical
+/datum/status_effect/wound/blunt/critical
id = "compound"
// cuts
-/datum/status_effect/wound/cut/moderate
+/datum/status_effect/wound/slash/moderate
id = "abrasion"
-/datum/status_effect/wound/cut/severe
+/datum/status_effect/wound/slash/severe
id = "laceration"
-/datum/status_effect/wound/cut/critical
+/datum/status_effect/wound/slash/critical
id = "avulsion"
+// pierce
+/datum/status_effect/wound/pierce/moderate
+ id = "breakage"
+
+/datum/status_effect/wound/pierce/severe
+ id = "puncture"
+
+/datum/status_effect/wound/pierce/critical
+ id = "rupture"
+
// burns
/datum/status_effect/wound/burn/moderate
id = "seconddeg"
diff --git a/code/datums/traits/negative.dm b/code/datums/traits/negative.dm
index 87126ae28f..322fba0a33 100644
--- a/code/datums/traits/negative.dm
+++ b/code/datums/traits/negative.dm
@@ -14,7 +14,7 @@
if(NOBLOOD in H.dna.species.species_traits) //can't lose blood if your species doesn't have any
return
else
- quirk_holder.blood_volume -= 0.275
+ quirk_holder.blood_volume -= 0.2
/datum/quirk/depression
name = "Depression"
@@ -54,7 +54,7 @@ GLOBAL_LIST_EMPTY(family_heirlooms)
if("Botanist")
heirloom_type = pick(/obj/item/cultivator, /obj/item/reagent_containers/glass/bucket, /obj/item/storage/bag/plants, /obj/item/toy/plush/beeplushie)
if("Medical Doctor")
- heirloom_type = /obj/item/healthanalyzer/advanced
+ heirloom_type = /obj/item/healthanalyzer
if("Paramedic")
heirloom_type = /obj/item/lighter
if("Station Engineer")
@@ -211,24 +211,27 @@ GLOBAL_LIST_EMPTY(family_heirlooms)
H.gain_trauma(T, TRAUMA_RESILIENCE_ABSOLUTE)
/datum/quirk/paraplegic/on_spawn()
- if(quirk_holder.buckled) // Handle late joins being buckled to arrival shuttle chairs.
- quirk_holder.buckled.unbuckle_mob(quirk_holder)
+ if(quirk_holder.client)
+ var/modified_limbs = quirk_holder.client.prefs.modified_limbs
+ if(!(modified_limbs[BODY_ZONE_L_LEG] == LOADOUT_LIMB_AMPUTATED && modified_limbs[BODY_ZONE_R_LEG] == LOADOUT_LIMB_AMPUTATED && !isjellyperson(quirk_holder)))
+ if(quirk_holder.buckled) // Handle late joins being buckled to arrival shuttle chairs.
+ quirk_holder.buckled.unbuckle_mob(quirk_holder)
- var/turf/T = get_turf(quirk_holder)
- var/obj/structure/chair/spawn_chair = locate() in T
+ var/turf/T = get_turf(quirk_holder)
+ var/obj/structure/chair/spawn_chair = locate() in T
- var/obj/vehicle/ridden/wheelchair/wheels = new(T)
- if(spawn_chair) // Makes spawning on the arrivals shuttle more consistent looking
- wheels.setDir(spawn_chair.dir)
+ var/obj/vehicle/ridden/wheelchair/wheels = new(T)
+ if(spawn_chair) // Makes spawning on the arrivals shuttle more consistent looking
+ wheels.setDir(spawn_chair.dir)
- wheels.buckle_mob(quirk_holder)
+ wheels.buckle_mob(quirk_holder)
- // During the spawning process, they may have dropped what they were holding, due to the paralysis
- // So put the things back in their hands.
+ // During the spawning process, they may have dropped what they were holding, due to the paralysis
+ // So put the things back in their hands.
- for(var/obj/item/I in T)
- if(I.fingerprintslast == quirk_holder.ckey)
- quirk_holder.put_in_hands(I)
+ for(var/obj/item/I in T)
+ if(I.fingerprintslast == quirk_holder.ckey)
+ quirk_holder.put_in_hands(I)
/datum/quirk/poor_aim
name = "Poor Aim"
@@ -244,42 +247,6 @@ GLOBAL_LIST_EMPTY(family_heirlooms)
mob_trait = TRAIT_PROSOPAGNOSIA
medical_record_text = "Patient suffers from prosopagnosia and cannot recognize faces."
-/datum/quirk/prosthetic_limb
- name = "Prosthetic Limb"
- desc = "An accident caused you to lose one of your limbs. Because of this, you now have a random prosthetic!"
- value = -1
- var/slot_string = "limb"
-
-/datum/quirk/prosthetic_limb/on_spawn()
- var/mob/living/carbon/human/H = quirk_holder
- var/limb_slot
- if(HAS_TRAIT(H, TRAIT_PARA))//Prevent paraplegic legs being replaced
- limb_slot = pick(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM)
- else
- limb_slot = pick(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG)
- var/obj/item/bodypart/old_part = H.get_bodypart(limb_slot)
- var/obj/item/bodypart/prosthetic
- switch(limb_slot)
- if(BODY_ZONE_L_ARM)
- prosthetic = new/obj/item/bodypart/l_arm/robot/surplus(quirk_holder)
- slot_string = "left arm"
- if(BODY_ZONE_R_ARM)
- prosthetic = new/obj/item/bodypart/r_arm/robot/surplus(quirk_holder)
- slot_string = "right arm"
- if(BODY_ZONE_L_LEG)
- prosthetic = new/obj/item/bodypart/l_leg/robot/surplus(quirk_holder)
- slot_string = "left leg"
- if(BODY_ZONE_R_LEG)
- prosthetic = new/obj/item/bodypart/r_leg/robot/surplus(quirk_holder)
- slot_string = "right leg"
- prosthetic.replace_limb(H)
- qdel(old_part)
- H.regenerate_icons()
-
-/datum/quirk/prosthetic_limb/post_add()
- to_chat(quirk_holder, "Your [slot_string] has been replaced with a surplus prosthetic. It is fragile and will easily come apart under duress. Additionally, \
- you need to use a welding tool and cables to repair it, instead of bruise packs and ointment.")
-
/datum/quirk/insanity
name = "Reality Dissociation Syndrome"
desc = "You suffer from a severe disorder that causes very vivid hallucinations. Mindbreaker toxin can suppress its effects, and you are immune to mindbreaker's hallucinogenic properties. This is not a license to grief."
@@ -337,10 +304,8 @@ GLOBAL_LIST_EMPTY(family_heirlooms)
dumb_thing = FALSE //only once per life
if(prob(1))
new/obj/item/reagent_containers/food/snacks/pastatomato(get_turf(H)) //now that's what I call spaghetti code
+
// small chance to make eye contact with inanimate objects/mindless mobs because of nerves
-
-
-
/datum/quirk/social_anxiety/proc/looks_at_floor(datum/source, atom/A)
var/mob/living/mind_check = A
if(prob(85) || (istype(mind_check) && mind_check.mind))
diff --git a/code/datums/wires/_wires.dm b/code/datums/wires/_wires.dm
index 75bd4f5c17..04fbc4a590 100644
--- a/code/datums/wires/_wires.dm
+++ b/code/datums/wires/_wires.dm
@@ -97,6 +97,12 @@
/datum/wires/proc/get_wire(color)
return colors[color]
+/datum/wires/proc/get_color_of_wire(wire_type)
+ for(var/color in colors)
+ var/other_type = colors[color]
+ if(wire_type == other_type)
+ return color
+
/datum/wires/proc/get_attached(color)
if(assemblies[color])
return assemblies[color]
@@ -117,7 +123,7 @@
return TRUE
/datum/wires/proc/is_dud(wire)
- return findtext(wire, WIRE_DUD_PREFIX)
+ return findtext(wire, WIRE_DUD_PREFIX, 1, length(WIRE_DUD_PREFIX) + 1)
/datum/wires/proc/is_dud_color(color)
return is_dud(get_wire(color))
@@ -197,6 +203,7 @@
S.forceMove(holder.drop_location())
return S
+/// Called from [/atom/proc/emp_act]
/datum/wires/proc/emp_pulse()
var/list/possible_wires = shuffle(wires)
var/remaining_pulses = MAXIMUM_EMP_WIRES
@@ -239,11 +246,13 @@
return ..()
return UI_CLOSE
-/datum/wires/ui_interact(mob/user, ui_key = "wires", datum/tgui/ui = null, force_open = FALSE, \
- datum/tgui/master_ui = null, datum/ui_state/state = GLOB.physical_state)
- ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+/datum/wires/ui_state(mob/user)
+ return GLOB.physical_state
+
+/datum/wires/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
if (!ui)
- ui = new(user, src, ui_key, "Wires", "[holder.name] Wires", 350, 150 + wires.len * 30, master_ui, state)
+ ui = new(user, src, "Wires", "[holder.name] Wires")
ui.open()
/datum/wires/ui_data(mob/user)
diff --git a/code/datums/wires/airlock.dm b/code/datums/wires/airlock.dm
index 2b13344cc6..9af78b0090 100644
--- a/code/datums/wires/airlock.dm
+++ b/code/datums/wires/airlock.dm
@@ -53,11 +53,8 @@
/datum/wires/airlock/interactable(mob/user)
var/obj/machinery/door/airlock/A = holder
- if(!A.panel_open)
- return FALSE
- if(!A.hasSiliconAccessInArea(user) && A.isElectrified() && A.shock(user, 100))
- return FALSE
- return TRUE
+ if(A.panel_open)
+ return TRUE
/datum/wires/airlock/get_status()
var/obj/machinery/door/airlock/A = holder
@@ -74,6 +71,8 @@
/datum/wires/airlock/on_pulse(wire)
set waitfor = FALSE
var/obj/machinery/door/airlock/A = holder
+ if(usr && !A.hasSiliconAccessInArea(usr) && A.isElectrified() && A.shock(usr, 100))
+ return FALSE
switch(wire)
if(WIRE_POWER1, WIRE_POWER2) // Pulse to loose power.
A.loseMainPower()
@@ -115,10 +114,7 @@
A.aiControlDisabled = -1
if(WIRE_SHOCK) // Pulse to shock the door for 10 ticks.
if(!A.secondsElectrified)
- A.set_electrified(30)
- if(usr)
- LAZYADD(A.shockedby, text("\[[TIME_STAMP("hh:mm:ss", FALSE)]\] [key_name(usr)]"))
- log_combat(usr, A, "electrified")
+ A.set_electrified(30, usr)
if(WIRE_SAFETY)
A.safe = !A.safe
if(!A.density)
@@ -131,25 +127,23 @@
/datum/wires/airlock/on_cut(wire, mend)
var/obj/machinery/door/airlock/A = holder
+ if(usr && !A.hasSiliconAccessInArea(usr) && A.isElectrified() && A.shock(usr, 100))
+ return FALSE
switch(wire)
if(WIRE_POWER1, WIRE_POWER2) // Cut to loose power, repair all to gain power.
if(mend && !is_cut(WIRE_POWER1) && !is_cut(WIRE_POWER2))
A.regainMainPower()
- if(usr)
- A.shock(usr, 50)
else
A.loseMainPower()
- if(usr)
- A.shock(usr, 50)
+ if(isliving(usr))
+ A.shock(usr, 50)
if(WIRE_BACKUP1, WIRE_BACKUP2) // Cut to loose backup power, repair all to gain backup power.
if(mend && !is_cut(WIRE_BACKUP1) && !is_cut(WIRE_BACKUP2))
A.regainBackupPower()
- if(usr)
- A.shock(usr, 50)
else
A.loseBackupPower()
- if(usr)
- A.shock(usr, 50)
+ if(isliving(usr))
+ A.shock(usr, 50)
if(WIRE_BOLTS) // Cut to drop bolts, mend does nothing.
if(!mend)
A.bolt()
@@ -170,10 +164,7 @@
A.set_electrified(0)
else
if(A.secondsElectrified != -1)
- A.set_electrified(-1)
- if(usr)
- LAZYADD(A.shockedby, text("\[[TIME_STAMP("hh:mm:ss", FALSE)]\] [key_name(usr)]"))
- log_combat(usr, A, "electrified")
+ A.set_electrified(-1, usr)
if(WIRE_SAFETY) // Cut to disable safeties, mend to re-enable.
A.safe = mend
if(WIRE_TIMING) // Cut to disable auto-close, mend to re-enable.
@@ -184,5 +175,5 @@
A.lights = mend
A.update_icon()
if(WIRE_ZAP1, WIRE_ZAP2) // Ouch.
- if(usr)
+ if(isliving(usr))
A.shock(usr, 50)
diff --git a/code/datums/wounds/_scars.dm b/code/datums/wounds/_scars.dm
index bfbaab835e..8cd0d8a047 100644
--- a/code/datums/wounds/_scars.dm
+++ b/code/datums/wounds/_scars.dm
@@ -53,8 +53,12 @@
if(victim)
LAZYADD(victim.all_scars, src)
- description = pick(W.scarring_descriptions)
- precise_location = pick(limb.specific_locations)
+ if(victim && victim.get_biological_state() == BIO_JUST_BONE)
+ description = pick(strings(BONE_SCAR_FILE, W.scar_keyword)) || "general disfigurement"
+ else
+ description = pick(strings(FLESH_SCAR_FILE, W.scar_keyword)) || "general disfigurement"
+
+ precise_location = pick(strings(SCAR_LOC_FILE, limb.body_zone))
switch(W.severity)
if(WOUND_SEVERITY_MODERATE)
visibility = 2
@@ -62,6 +66,9 @@
visibility = 3
if(WOUND_SEVERITY_CRITICAL)
visibility = 5
+ if(WOUND_SEVERITY_LOSS)
+ visibility = 7
+ precise_location = "amputation"
/// Used when we finalize a scar from a healing cut
/datum/scar/proc/lazy_attach(obj/item/bodypart/BP, datum/wound/W)
@@ -71,10 +78,11 @@
LAZYADD(victim.all_scars, src)
/// Used to "load" a persistent scar
-/datum/scar/proc/load(obj/item/bodypart/BP, description, specific_location, severity=WOUND_SEVERITY_SEVERE)
- if(!(BP.body_zone in applicable_zones))
+/datum/scar/proc/load(obj/item/bodypart/BP, version, description, specific_location, severity=WOUND_SEVERITY_SEVERE)
+ if(!(BP.body_zone in applicable_zones) || !BP.is_organic_limb())
qdel(src)
return
+
limb = BP
src.severity = severity
LAZYADD(limb.scars, src)
@@ -90,6 +98,8 @@
visibility = 3
if(WOUND_SEVERITY_CRITICAL)
visibility = 5
+ if(WOUND_SEVERITY_LOSS)
+ visibility = 7
return TRUE
/// What will show up in examine_more() if this scar is visible
@@ -102,9 +112,12 @@
if(WOUND_SEVERITY_MODERATE)
msg = "[msg]"
if(WOUND_SEVERITY_SEVERE)
- msg = "[msg]"
+ msg = "[msg]"
if(WOUND_SEVERITY_CRITICAL)
- msg = "[msg]"
+ msg = "[msg]"
+ if(WOUND_SEVERITY_LOSS)
+ msg = "[victim.p_their(TRUE)] [limb.name] [description]." // different format
+ msg = "[msg]"
return "\t[msg]"
/// Whether a scar can currently be seen by the viewer
@@ -117,12 +130,12 @@
if(!ishuman(victim) || isobserver(viewer) || victim == viewer)
return TRUE
- var/mob/living/carbon/human/H = victim
+ var/mob/living/carbon/human/human_victim = victim
if(istype(limb, /obj/item/bodypart/head))
- if((H.wear_mask && (H.wear_mask.flags_inv & HIDEFACE)) || (H.head && (H.head.flags_inv & HIDEFACE)))
+ if((human_victim.wear_mask && (human_victim.wear_mask.flags_inv & HIDEFACE)) || (human_victim.head && (human_victim.head.flags_inv & HIDEFACE)))
return FALSE
else if(limb.scars_covered_by_clothes)
- var/num_covers = LAZYLEN(H.clothingonpart(limb))
+ var/num_covers = LAZYLEN(human_victim.clothingonpart(limb))
if(num_covers + get_dist(viewer, victim) >= visibility)
return FALSE
@@ -131,4 +144,9 @@
/// Used to format a scar to safe in preferences for persistent scars
/datum/scar/proc/format()
if(!fake)
- return "[limb.body_zone]|[description]|[precise_location]|[severity]"
+ return "[SCAR_CURRENT_VERSION]|[limb.body_zone]|[description]|[precise_location]|[severity]"
+
+/// Used to format a scar to safe in preferences for persistent scars
+/datum/scar/proc/format_amputated(body_zone)
+ description = pick(list("is several skintone shades paler than the rest of the body", "is a gruesome patchwork of artificial flesh", "has a large series of attachment scars at the articulation points"))
+ return "[SCAR_CURRENT_VERSION]|[body_zone]|[description]|amputated|[WOUND_SEVERITY_LOSS]"
\ No newline at end of file
diff --git a/code/datums/wounds/_wounds.dm b/code/datums/wounds/_wounds.dm
index b1ba49b7ab..29c87b32d4 100644
--- a/code/datums/wounds/_wounds.dm
+++ b/code/datums/wounds/_wounds.dm
@@ -33,15 +33,13 @@
/// Either WOUND_SEVERITY_TRIVIAL (meme wounds like stubbed toe), WOUND_SEVERITY_MODERATE, WOUND_SEVERITY_SEVERE, or WOUND_SEVERITY_CRITICAL (or maybe WOUND_SEVERITY_LOSS)
var/severity = WOUND_SEVERITY_MODERATE
- /// The list of wounds it belongs in, WOUND_LIST_BONE, WOUND_LIST_CUT, or WOUND_LIST_BURN
+ /// The list of wounds it belongs in, WOUND_LIST_BLUNT, WOUND_LIST_SLASH, or WOUND_LIST_BURN
var/wound_type
/// What body zones can we affect
var/list/viable_zones = list(BODY_ZONE_HEAD, BODY_ZONE_CHEST, BODY_ZONE_L_ARM, BODY_ZONE_R_ARM, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG)
/// Who owns the body part that we're wounding
var/mob/living/carbon/victim = null
- /// If we only work on organics (everything right now)
- var/organic_only = TRUE
/// The bodypart we're parented to
var/obj/item/bodypart/limb = null
@@ -51,8 +49,6 @@
var/list/treatable_by_grabbed
/// Tools with the specified tool flag will also be able to try directly treating this wound
var/treatable_tool
- /// Set to TRUE if we don't give a shit about the patient's comfort and are allowed to just use any random sharp thing on this wound. Will require an aggressive grab or more to perform
- var/treatable_sharp
/// How long it will take to treat this wound with a standard effective tool, assuming it doesn't need surgery
var/base_treat_time = 5 SECONDS
@@ -65,17 +61,13 @@
/// How much we're contributing to this limb's bleed_rate
var/blood_flow
- /// The minimum we need to roll on [/obj/item/bodypart/proc/check_wounding()] to begin suffering this wound, see check_wounding_mods() for more
+ /// The minimum we need to roll on [/obj/item/bodypart/proc/check_wounding] to begin suffering this wound, see check_wounding_mods() for more
var/threshold_minimum
/// How much having this wound will add to all future check_wounding() rolls on this limb, to allow progression to worse injuries with repeated damage
var/threshold_penalty
/// If we need to process each life tick
var/processes = FALSE
- /// If TRUE and an item that can treat multiple different types of coexisting wounds (gauze can be used to splint broken bones, staunch bleeding, and cover burns), we get first dibs if we come up first for it, then become nonpriority.
- /// Otherwise, if no untreated wound claims the item, we cycle through the non priority wounds and pick a random one who can use that item.
- var/treat_priority = FALSE
-
/// If having this wound makes currently makes the parent bodypart unusable
var/disabling
@@ -89,12 +81,15 @@
var/cryo_progress
/// What kind of scars this wound will create description wise once healed
- var/list/scarring_descriptions = list("general disfigurement")
+ var/scar_keyword = "generic"
/// If we've already tried scarring while removing (since remove_wound calls qdel, and qdel calls remove wound, .....) TODO: make this cleaner
var/already_scarred = FALSE
/// If we forced this wound through badmin smite, we won't count it towards the round totals
var/from_smite
+ /// What flags apply to this wound
+ var/wound_flags = (FLESH_WOUND | BONE_WOUND | ACCEPTS_GAUZE)
+
/datum/wound/Destroy()
if(attached_surgery)
QDEL_NULL(attached_surgery)
@@ -115,13 +110,13 @@
* * smited- If this is a smite, we don't care about this wound for stat tracking purposes (not yet implemented)
*/
/datum/wound/proc/apply_wound(obj/item/bodypart/L, silent = FALSE, datum/wound/old_wound = null, smited = FALSE)
- if(!istype(L) || !L.owner || !(L.body_zone in viable_zones) || isalien(L.owner))
+ if(!istype(L) || !L.owner || !(L.body_zone in viable_zones) || isalien(L.owner) || !L.is_organic_limb())
qdel(src)
return
if(ishuman(L.owner))
var/mob/living/carbon/human/H = L.owner
- if(organic_only && ((NOBLOOD in H.dna.species.species_traits) || !L.is_organic_limb()))
+ if(((wound_flags & BONE_WOUND) && !(HAS_BONE in H.dna.species.species_traits)) || ((wound_flags & FLESH_WOUND) && !(HAS_FLESH in H.dna.species.species_traits)))
qdel(src)
return
@@ -161,7 +156,7 @@
victim.visible_message(msg, "Your [limb.name] [occur_text]!", vision_distance = vis_dist)
if(sound_effect)
- playsound(L.owner, sound_effect, 60 + 20 * severity, TRUE)
+ playsound(L.owner, sound_effect, 70 + 20 * severity, TRUE)
if(!demoted)
wound_injury(old_wound)
@@ -181,7 +176,7 @@
SEND_SIGNAL(victim, COMSIG_CARBON_LOSE_WOUND, src, limb)
if(limb && !ignore_limb)
LAZYREMOVE(limb.wounds, src)
- limb.update_wounds()
+ limb.update_wounds(replaced)
/**
* replace_wound() is used when you want to replace the current wound with a new wound, presumably of the same category, just of a different severity (either up or down counts)
@@ -189,7 +184,7 @@
* This proc actually instantiates the new wound based off the specific type path passed, then returns the new instantiated wound datum.
*
* Arguments:
- * * new_type- The TYPE PATH of the wound you want to replace this, like /datum/wound/brute/cut/severe
+ * * new_type- The TYPE PATH of the wound you want to replace this, like /datum/wound/slash/severe
* * smited- If this is a smite, we don't care about this wound for stat tracking purposes (not yet implemented)
*/
/datum/wound/proc/replace_wound(new_type, smited = FALSE)
@@ -206,7 +201,6 @@
/// Additional beneficial effects when the wound is gained, in case you want to give a temporary boost to allow the victim to try an escape or last stand
/datum/wound/proc/second_wind()
-
switch(severity)
if(WOUND_SEVERITY_MODERATE)
victim.reagents.add_reagent(/datum/reagent/determination, WOUND_DETERMINATION_MODERATE)
@@ -214,11 +208,13 @@
victim.reagents.add_reagent(/datum/reagent/determination, WOUND_DETERMINATION_SEVERE)
if(WOUND_SEVERITY_CRITICAL)
victim.reagents.add_reagent(/datum/reagent/determination, WOUND_DETERMINATION_CRITICAL)
+ if(WOUND_SEVERITY_LOSS)
+ victim.reagents.add_reagent(/datum/reagent/determination, WOUND_DETERMINATION_LOSS)
/**
- * try_treating() is an intercept run from [/mob/living/carbon/attackby()] right after surgeries but before anything else. Return TRUE here if the item is something that is relevant to treatment to take over the interaction.
+ * try_treating() is an intercept run from [/mob/living/carbon/proc/attackby] right after surgeries but before anything else. Return TRUE here if the item is something that is relevant to treatment to take over the interaction.
*
- * This proc leads into [/datum/wound/proc/treat()] and probably shouldn't be added onto in children types. You can specify what items or tools you want to be intercepted
+ * This proc leads into [/datum/wound/proc/treat] and probably shouldn't be added onto in children types. You can specify what items or tools you want to be intercepted
* with var/list/treatable_by and var/treatable_tool, then if an item fulfills one of those requirements and our wound claims it first, it goes over to treat() and treat_self().
*
* Arguments:
@@ -258,7 +254,7 @@
treat(I, user)
return TRUE
-/// Return TRUE if we have an item that can only be used while aggro grabbed (unhanded aggro grab treatments go in [/datum/wound/proc/try_handling()]). Treatment is still is handled in [/datum/wound/proc/treat()]
+/// Return TRUE if we have an item that can only be used while aggro grabbed (unhanded aggro grab treatments go in [/datum/wound/proc/try_handling]). Treatment is still is handled in [/datum/wound/proc/treat]
/datum/wound/proc/check_grab_treatments(obj/item/I, mob/user)
return FALSE
@@ -288,10 +284,22 @@
if(cryo_progress > 33 * severity)
qdel(src)
+/// When synthflesh is applied to the victim, we call this. No sense in setting up an entire chem reaction system for wounds when we only care for a few chems. Probably will change in the future
+/datum/wound/proc/on_synthflesh(power)
+ return
+
+/// Called when the patient is undergoing stasis, so that having fully treated a wound doesn't make you sit there helplessly until you think to unbuckle them
+/datum/wound/proc/on_stasis()
+ return
+
/// Called when we're crushed in an airlock or firedoor, for one of the improvised joint dislocation fixes
/datum/wound/proc/crush()
return
+/// Used when we're being dragged while bleeding, the value we return is how much bloodloss this wound causes from being dragged. Since it's a proc, you can let bandages soak some of the blood
+/datum/wound/proc/drag_bleed_amount()
+ return
+
/**
* get_examine_description() is used in carbon/examine and human/examine to show the status of this wound. Useful if you need to show some status like the wound being splinted or bandaged.
*
@@ -301,7 +309,8 @@
* * mob/user: The user examining the wound's owner, if that matters
*/
/datum/wound/proc/get_examine_description(mob/user)
- return "[victim.p_their(TRUE)] [limb.name] [examine_desc]!"
+ . = "[victim.p_their(TRUE)] [limb.name] [examine_desc]"
+ . = severity <= WOUND_SEVERITY_MODERATE ? "[.]." : "[.]!"
/datum/wound/proc/get_scanner_description(mob/user)
return "Type: [name]\nSeverity: [severity_text()]\nDescription: [desc]\nRecommended Treatment: [treat_text]"
diff --git a/code/datums/wounds/bones.dm b/code/datums/wounds/bones.dm
index 80d922cba3..128c860a6d 100644
--- a/code/datums/wounds/bones.dm
+++ b/code/datums/wounds/bones.dm
@@ -1,4 +1,3 @@
-
/*
Bones
*/
@@ -7,12 +6,10 @@
/*
Base definition
*/
-/datum/wound/brute/bone
- sound_effect = 'sound/effects/crack1.ogg'
- wound_type = WOUND_LIST_BONE
-
- /// The item we're currently splinted with, if there is one
- var/obj/item/stack/splinted
+/datum/wound/blunt
+ sound_effect = 'sound/effects/wounds/crack1.ogg'
+ wound_type = WOUND_BLUNT
+ wound_flags = (BONE_WOUND | ACCEPTS_GAUZE)
/// Have we been taped?
var/taped
@@ -31,12 +28,12 @@
/// How long do we wait +/- 20% for the next trauma?
var/trauma_cycle_cooldown
/// If this is a chest wound and this is set, we have this chance to cough up blood when hit in the chest
- var/chance_internal_bleeding = 0
+ var/internal_bleeding_chance = 0
/*
Overwriting of base procs
*/
-/datum/wound/brute/bone/wound_injury(datum/wound/old_wound = null)
+/datum/wound/blunt/wound_injury(datum/wound/old_wound = null)
if(limb.body_zone == BODY_ZONE_HEAD && brain_trauma_group)
processes = TRUE
active_trauma = victim.gain_trauma_type(brain_trauma_group, TRAUMA_RESILIENCE_WOUND)
@@ -53,14 +50,14 @@
update_inefficiencies()
-/datum/wound/brute/bone/remove_wound(ignore_limb, replaced)
+/datum/wound/blunt/remove_wound(ignore_limb, replaced)
limp_slowdown = 0
QDEL_NULL(active_trauma)
if(victim)
UnregisterSignal(victim, COMSIG_HUMAN_EARLY_UNARMED_ATTACK)
return ..()
-/datum/wound/brute/bone/handle_process()
+/datum/wound/blunt/handle_process()
. = ..()
if(limb.body_zone == BODY_ZONE_HEAD && brain_trauma_group && world.time > next_trauma_cycle)
if(active_trauma)
@@ -86,7 +83,7 @@
remove_wound()
/// If we're a human who's punching something with a broken arm, we might hurt ourselves doing so
-/datum/wound/brute/bone/proc/attack_with_hurt_hand(mob/M, atom/target, proximity)
+/datum/wound/blunt/proc/attack_with_hurt_hand(mob/M, atom/target, proximity)
if(victim.get_active_hand() != limb || victim.a_intent == INTENT_HELP || !ismob(target) || severity <= WOUND_SEVERITY_MODERATE)
return
@@ -104,61 +101,54 @@
limb.receive_damage(brute=rand(3,7))
return COMPONENT_NO_ATTACK_HAND
-/datum/wound/brute/bone/receive_damage(wounding_type, wounding_dmg, wound_bonus)
- if(!victim)
+/datum/wound/blunt/receive_damage(wounding_type, wounding_dmg, wound_bonus)
+ if(!victim || wounding_dmg < WOUND_MINIMUM_DAMAGE)
return
+ if(ishuman(victim))
+ var/mob/living/carbon/human/human_victim = victim
+ if(NOBLOOD in human_victim.dna?.species.species_traits)
+ return
- if(limb.body_zone == BODY_ZONE_CHEST && victim.blood_volume && prob(chance_internal_bleeding + wounding_dmg))
+ if(limb.body_zone == BODY_ZONE_CHEST && victim.blood_volume && prob(internal_bleeding_chance + wounding_dmg))
var/blood_bled = rand(1, wounding_dmg * (severity == WOUND_SEVERITY_CRITICAL ? 2 : 1.5)) // 12 brute toolbox can cause up to 18/24 bleeding with a severe/critical chest wound
switch(blood_bled)
if(1 to 6)
victim.bleed(blood_bled, TRUE)
if(7 to 13)
- victim.visible_message("[victim] coughs up a bit of blood from the blow to [victim.p_their()] chest.", "You cough up a bit of blood from the blow to your chest.")
+ victim.visible_message("[victim] coughs up a bit of blood from the blow to [victim.p_their()] chest.", "You cough up a bit of blood from the blow to your chest.", vision_distance=COMBAT_MESSAGE_RANGE)
victim.bleed(blood_bled, TRUE)
if(14 to 19)
- victim.visible_message("[victim] spits out a string of blood from the blow to [victim.p_their()] chest!", "You spit out a string of blood from the blow to your chest!")
+ victim.visible_message("[victim] spits out a string of blood from the blow to [victim.p_their()] chest!", "You spit out a string of blood from the blow to your chest!", vision_distance=COMBAT_MESSAGE_RANGE)
new /obj/effect/temp_visual/dir_setting/bloodsplatter(victim.loc, victim.dir)
victim.bleed(blood_bled)
if(20 to INFINITY)
- victim.visible_message("[victim] chokes up a spray of blood from the blow to [victim.p_their()] chest!", "You choke up on a spray of blood from the blow to your chest!")
+ victim.visible_message("[victim] chokes up a spray of blood from the blow to [victim.p_their()] chest!", "You choke up on a spray of blood from the blow to your chest!", vision_distance=COMBAT_MESSAGE_RANGE)
victim.bleed(blood_bled)
new /obj/effect/temp_visual/dir_setting/bloodsplatter(victim.loc, victim.dir)
victim.add_splatter_floor(get_step(victim.loc, victim.dir))
- if(!(wounding_type in list(WOUND_SHARP, WOUND_BURN)) || !splinted || wound_bonus == CANT_WOUND)
- return
- splinted.take_damage(wounding_dmg, damage_type = (wounding_type == WOUND_SHARP ? BRUTE : BURN), sound_effect = FALSE)
- if(QDELETED(splinted))
- var/destroyed_verb = (wounding_type == WOUND_SHARP ? "torn" : "burned")
- victim.visible_message("The splint securing [victim]'s [limb.name] is [destroyed_verb] away!", "The splint securing your [limb.name] is [destroyed_verb] away!", vision_distance=COMBAT_MESSAGE_RANGE)
- splinted = null
- treat_priority = TRUE
- update_inefficiencies()
-
-
-/datum/wound/brute/bone/get_examine_description(mob/user)
- if(!splinted && !gelled && !taped)
+/datum/wound/blunt/get_examine_description(mob/user)
+ if(!limb.current_gauze && !gelled && !taped)
return ..()
- var/msg = ""
- if(!splinted)
- msg = "[victim.p_their(TRUE)] [limb.name] [examine_desc]"
+ var/list/msg = list()
+ if(!limb.current_gauze)
+ msg += "[victim.p_their(TRUE)] [limb.name] [examine_desc]"
else
- var/splint_condition = ""
+ var/sling_condition = ""
// how much life we have left in these bandages
- switch(splinted.obj_integrity / splinted.max_integrity * 100)
+ switch(limb.current_gauze.obj_integrity / limb.current_gauze.max_integrity * 100)
if(0 to 25)
- splint_condition = "just barely "
+ sling_condition = "just barely "
if(25 to 50)
- splint_condition = "loosely "
+ sling_condition = "loosely "
if(50 to 75)
- splint_condition = "mostly "
+ sling_condition = "mostly "
if(75 to INFINITY)
- splint_condition = "tightly "
+ sling_condition = "tightly "
- msg = "[victim.p_their(TRUE)] [limb.name] is [splint_condition] fastened in a splint of [splinted.name]"
+ msg += "[victim.p_their(TRUE)] [limb.name] is [sling_condition] fastened in a sling of [limb.current_gauze.name]"
if(taped)
msg += ", and appears to be reforming itself under some surgical tape!"
@@ -166,58 +156,35 @@
msg += ", with fizzing flecks of blue bone gel sparking off the bone!"
else
msg += "!"
- return "[msg]"
+ return "[msg.Join()]"
/*
- New common procs for /datum/wound/brute/bone/
+ New common procs for /datum/wound/blunt/
*/
-/datum/wound/brute/bone/proc/update_inefficiencies()
+/datum/wound/blunt/proc/update_inefficiencies()
if(limb.body_zone in list(BODY_ZONE_L_LEG, BODY_ZONE_R_LEG))
- if(splinted)
- limp_slowdown = initial(limp_slowdown) * splinted.splint_factor
+ if(limb.current_gauze)
+ limp_slowdown = initial(limp_slowdown) * limb.current_gauze.splint_factor
else
limp_slowdown = initial(limp_slowdown)
victim.apply_status_effect(STATUS_EFFECT_LIMP)
else if(limb.body_zone in list(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM))
- if(splinted)
- interaction_efficiency_penalty = 1 + ((interaction_efficiency_penalty - 1) * splinted.splint_factor)
+ if(limb.current_gauze)
+ interaction_efficiency_penalty = 1 + ((interaction_efficiency_penalty - 1) * limb.current_gauze.splint_factor)
else
interaction_efficiency_penalty = interaction_efficiency_penalty
- if(initial(disabling) && splinted)
- disabling = FALSE
- else if(initial(disabling))
- disabling = TRUE
+ if(initial(disabling))
+ disabling = !limb.current_gauze
limb.update_wounds()
-/*
- BEWARE OF REDUNDANCY AHEAD THAT I MUST PARE DOWN
-*/
-
-/datum/wound/brute/bone/proc/splint(obj/item/stack/I, mob/user)
- if(splinted && splinted.splint_factor >= I.splint_factor)
- to_chat(user, "The splint already on [user == victim ? "your" : "[victim]'s"] [limb.name] is better than you can do with [I].")
- return
-
- user.visible_message("[user] begins splinting [victim]'s [limb.name] with [I].", "You begin splinting [user == victim ? "your" : "[victim]'s"] [limb.name] with [I]...")
-
- if(!do_after(user, base_treat_time * (user == victim ? 1.5 : 1), target = victim, extra_checks=CALLBACK(src, .proc/still_exists)))
- return
-
- user.visible_message("[user] finishes splinting [victim]'s [limb.name]!", "You finish splinting [user == victim ? "your" : "[victim]'s"] [limb.name]!")
- treat_priority = FALSE
- splinted = new I.type(limb)
- splinted.amount = 1
- I.use(1)
- update_inefficiencies()
-
/*
Moderate (Joint Dislocation)
*/
-/datum/wound/brute/bone/moderate
+/datum/wound/blunt/moderate
name = "Joint Dislocation"
desc = "Patient's bone has been unset from socket, causing pain and reduced motor function."
treat_text = "Recommended application of bonesetter to affected limb, though manual relocation by applying an aggressive grab to the patient and helpfully interacting with afflicted limb may suffice."
@@ -226,19 +193,20 @@
severity = WOUND_SEVERITY_MODERATE
viable_zones = list(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG)
interaction_efficiency_penalty = 1.5
- limp_slowdown = 3
- threshold_minimum = 35
+ limp_slowdown = 1.5
+ threshold_minimum = 45
threshold_penalty = 15
treatable_tool = TOOL_BONESET
- status_effect_type = /datum/status_effect/wound/bone/moderate
- scarring_descriptions = list("light discoloring", "a slight blue tint")
+ wound_flags = (BONE_WOUND)
+ status_effect_type = /datum/status_effect/wound/blunt/moderate
+ scar_keyword = "bluntmoderate"
-/datum/wound/brute/bone/moderate/crush()
+/datum/wound/blunt/moderate/crush()
if(prob(33))
victim.visible_message("[victim]'s dislocated [limb.name] pops back into place!", "Your dislocated [limb.name] pops back into place! Ow!")
remove_wound()
-/datum/wound/brute/bone/moderate/try_handling(mob/living/carbon/human/user)
+/datum/wound/blunt/moderate/try_handling(mob/living/carbon/human/user)
if(user.pulling != victim || user.zone_selected != limb.body_zone || user.a_intent == INTENT_GRAB)
return FALSE
@@ -256,7 +224,7 @@
return TRUE
/// If someone is snapping our dislocated joint back into place by hand with an aggro grab and help intent
-/datum/wound/brute/bone/moderate/proc/chiropractice(mob/living/carbon/human/user)
+/datum/wound/blunt/moderate/proc/chiropractice(mob/living/carbon/human/user)
var/time = base_treat_time
if(!do_after(user, time, target=victim, extra_checks = CALLBACK(src, .proc/still_exists)))
@@ -275,7 +243,7 @@
chiropractice(user)
/// If someone is snapping our dislocated joint into a fracture by hand with an aggro grab and harm or disarm intent
-/datum/wound/brute/bone/moderate/proc/malpractice(mob/living/carbon/human/user)
+/datum/wound/blunt/moderate/proc/malpractice(mob/living/carbon/human/user)
var/time = base_treat_time
if(!do_after(user, time, target=victim, extra_checks = CALLBACK(src, .proc/still_exists)))
@@ -293,7 +261,7 @@
malpractice(user)
-/datum/wound/brute/bone/moderate/treat(obj/item/I, mob/user)
+/datum/wound/blunt/moderate/treat(obj/item/I, mob/user)
if(victim == user)
victim.visible_message("[user] begins resetting [victim.p_their()] [limb.name] with [I].", "You begin resetting your [limb.name] with [I]...")
else
@@ -317,56 +285,57 @@
Severe (Hairline Fracture)
*/
-/datum/wound/brute/bone/severe
+/datum/wound/blunt/severe
name = "Hairline Fracture"
desc = "Patient's bone has suffered a crack in the foundation, causing serious pain and reduced limb functionality."
- treat_text = "Recommended light surgical application of bone gel, though splinting will prevent worsening situation."
- examine_desc = "appears bruised and grotesquely swollen"
-
+ treat_text = "Recommended light surgical application of bone gel, though a sling of medical gauze will prevent worsening situation."
+ examine_desc = "appears grotesquely swollen, its attachment weakened"
occur_text = "sprays chips of bone and develops a nasty looking bruise"
+
severity = WOUND_SEVERITY_SEVERE
interaction_efficiency_penalty = 2
- limp_slowdown = 6
- threshold_minimum = 60
+ limp_slowdown = 4
+ threshold_minimum = 70
threshold_penalty = 30
- treatable_by = list(/obj/item/stack/sticky_tape/surgical, /obj/item/stack/medical/gauze, /obj/item/stack/medical/bone_gel)
- status_effect_type = /datum/status_effect/wound/bone/severe
- treat_priority = TRUE
- scarring_descriptions = list("a faded, fist-sized bruise", "a vaguely triangular peel scar")
+ treatable_by = list(/obj/item/stack/sticky_tape/surgical, /obj/item/stack/medical/bone_gel)
+ status_effect_type = /datum/status_effect/wound/blunt/severe
+ scar_keyword = "bluntsevere"
brain_trauma_group = BRAIN_TRAUMA_MILD
trauma_cycle_cooldown = 1.5 MINUTES
- chance_internal_bleeding = 40
+ internal_bleeding_chance = 40
+ wound_flags = (BONE_WOUND | ACCEPTS_GAUZE | MANGLES_BONE)
-/datum/wound/brute/bone/critical
+/datum/wound/blunt/critical
name = "Compound Fracture"
desc = "Patient's bones have suffered multiple gruesome fractures, causing significant pain and near uselessness of limb."
treat_text = "Immediate binding of affected limb, followed by surgical intervention ASAP."
- examine_desc = "has a cracked bone sticking out of it"
+ examine_desc = "is mangled and pulped, seemingly held together by tissue alone"
occur_text = "cracks apart, exposing broken bones to open air"
+
severity = WOUND_SEVERITY_CRITICAL
interaction_efficiency_penalty = 4
- limp_slowdown = 9
- sound_effect = 'sound/effects/crack2.ogg'
- threshold_minimum = 115
+ limp_slowdown = 6
+ sound_effect = 'sound/effects/wounds/crack2.ogg'
+ threshold_minimum = 125
threshold_penalty = 50
disabling = TRUE
- treatable_by = list(/obj/item/stack/sticky_tape/surgical, /obj/item/stack/medical/gauze, /obj/item/stack/medical/bone_gel)
- status_effect_type = /datum/status_effect/wound/bone/critical
- treat_priority = TRUE
- scarring_descriptions = list("a section of janky skin lines and badly healed scars", "a large patch of uneven skin tone", "a cluster of calluses")
+ treatable_by = list(/obj/item/stack/sticky_tape/surgical, /obj/item/stack/medical/bone_gel)
+ status_effect_type = /datum/status_effect/wound/blunt/critical
+ scar_keyword = "bluntcritical"
brain_trauma_group = BRAIN_TRAUMA_SEVERE
trauma_cycle_cooldown = 2.5 MINUTES
- chance_internal_bleeding = 60
+ internal_bleeding_chance = 60
+ wound_flags = (BONE_WOUND | ACCEPTS_GAUZE | MANGLES_BONE)
// doesn't make much sense for "a" bone to stick out of your head
-/datum/wound/brute/bone/critical/apply_wound(obj/item/bodypart/L, silent, datum/wound/old_wound, smited)
+/datum/wound/blunt/critical/apply_wound(obj/item/bodypart/L, silent, datum/wound/old_wound, smited)
if(L.body_zone == BODY_ZONE_HEAD)
occur_text = "splits open, exposing a bare, cracked skull through the flesh and blood"
examine_desc = "has an unsettling indent, with bits of skull poking out"
. = ..()
/// if someone is using bone gel on our wound
-/datum/wound/brute/bone/proc/gel(obj/item/stack/medical/bone_gel/I, mob/user)
+/datum/wound/blunt/proc/gel(obj/item/stack/medical/bone_gel/I, mob/user)
if(gelled)
to_chat(user, "[user == victim ? "Your" : "[victim]'s"] [limb.name] is already coated with bone gel!")
return
@@ -385,12 +354,12 @@
var/painkiller_bonus = 0
if(victim.drunkenness)
painkiller_bonus += 5
- if(victim.reagents && victim.reagents.has_reagent(/datum/reagent/medicine/morphine))
+ if(victim.reagents?.has_reagent(/datum/reagent/medicine/morphine))
painkiller_bonus += 10
- if(victim.reagents && victim.reagents.has_reagent(/datum/reagent/determination))
+ if(victim.reagents?.has_reagent(/datum/reagent/determination))
painkiller_bonus += 5
- if(prob(25 + (20 * (severity - 2)) - painkiller_bonus)) // 25%/45% chance to fail self-applying with severe and critical wounds, modded by painkillers
+ if(prob(25 + (20 * severity - 2) - painkiller_bonus)) // 25%/45% chance to fail self-applying with severe and critical wounds, modded by painkillers
victim.visible_message("[victim] fails to finish applying [I] to [victim.p_their()] [limb.name], passing out from the pain!", "You black out from the pain of applying [I] to your [limb.name] before you can finish!")
victim.AdjustUnconscious(5 SECONDS)
return
@@ -401,7 +370,7 @@
gelled = TRUE
/// if someone is using surgical tape on our wound
-/datum/wound/brute/bone/proc/tape(obj/item/stack/sticky_tape/surgical/I, mob/user)
+/datum/wound/blunt/proc/tape(obj/item/stack/sticky_tape/surgical/I, mob/user)
if(!gelled)
to_chat(user, "[user == victim ? "Your" : "[victim]'s"] [limb.name] must be coated with bone gel to perform this emergency operation!")
return
@@ -426,15 +395,13 @@
taped = TRUE
processes = TRUE
-/datum/wound/brute/bone/treat(obj/item/I, mob/user)
+/datum/wound/blunt/treat(obj/item/I, mob/user)
if(istype(I, /obj/item/stack/medical/bone_gel))
gel(I, user)
else if(istype(I, /obj/item/stack/sticky_tape/surgical))
tape(I, user)
- else if(istype(I, /obj/item/stack/medical/gauze))
- splint(I, user)
-/datum/wound/brute/bone/get_scanner_description(mob/user)
+/datum/wound/blunt/get_scanner_description(mob/user)
. = ..()
. += "
"
@@ -444,7 +411,7 @@
else if(!taped)
. += "Continue Alternative Treatment: Apply surgical tape directly to injured limb to begin bone regeneration. Note, this is both excruciatingly painful and slow.\n"
else
- . += "Note: Bone regeneration in effect. Bone is [round((regen_points_current*100)/regen_points_needed,0.1)]% regenerated.\n"
+ . += "Note: Bone regeneration in effect. Bone is [round(regen_points_current*100/regen_points_needed)]% regenerated.\n"
if(limb.body_zone == BODY_ZONE_HEAD)
. += "Cranial Trauma Detected: Patient will suffer random bouts of [severity == WOUND_SEVERITY_SEVERE ? "mild" : "severe"] brain traumas until bone is repaired."
diff --git a/code/datums/wounds/burns.dm b/code/datums/wounds/burns.dm
index 6a6629a0c9..f3e22807cf 100644
--- a/code/datums/wounds/burns.dm
+++ b/code/datums/wounds/burns.dm
@@ -1,14 +1,14 @@
-
// TODO: well, a lot really, but specifically I want to add potential fusing of clothing/equipment on the affected area, and limb infections, though those may go in body part code
/datum/wound/burn
a_or_from = "from"
- wound_type = WOUND_LIST_BURN
+ wound_type = WOUND_BURN
processes = TRUE
- sound_effect = 'sound/effects/sizzle1.ogg'
+ sound_effect = 'sound/effects/wounds/sizzle1.ogg'
+ wound_flags = (FLESH_WOUND | ACCEPTS_GAUZE)
- treatable_by = list(/obj/item/stack/medical/gauze, /obj/item/stack/medical/ointment, /obj/item/stack/medical/mesh) // sterilizer and alcohol will require reagent treatments, coming soon
+ treatable_by = list(/obj/item/stack/medical/ointment, /obj/item/stack/medical/mesh) // sterilizer and alcohol will require reagent treatments, coming soon
// Flesh damage vars
/// How much damage to our flesh we currently have. Once both this and infestation reach 0, the wound is considered healed
@@ -27,8 +27,6 @@
/// Once we reach infestation beyond WOUND_INFESTATION_SEPSIS, we get this many warnings before the limb is completely paralyzed (you'd have to ignore a really bad burn for a really long time for this to happen)
var/strikes_to_lose_limb = 3
- /// The current bandage we have for this wound (maybe move bandages to the limb?)
- var/obj/item/stack/current_bandage
/datum/wound/burn/handle_process()
. = ..()
@@ -47,15 +45,11 @@
sanitization += 0.3
flesh_healing += 0.5
- if(current_bandage)
- current_bandage.absorption_capacity -= WOUND_BURN_SANITIZATION_RATE
- if(current_bandage.absorption_capacity <= 0)
- victim.visible_message("Pus soaks through \the [current_bandage] on [victim]'s [limb.name].", "Pus soaks through \the [current_bandage] on your [limb.name].", vision_distance=COMBAT_MESSAGE_RANGE)
- QDEL_NULL(current_bandage)
- treat_priority = TRUE
+ if(limb.current_gauze)
+ limb.seep_gauze(WOUND_BURN_SANITIZATION_RATE)
if(flesh_healing > 0)
- var/bandage_factor = (current_bandage ? current_bandage.splint_factor : 1)
+ var/bandage_factor = (limb.current_gauze ? limb.current_gauze.splint_factor : 1)
flesh_damage = max(0, flesh_damage - 1)
flesh_healing = max(0, flesh_healing - bandage_factor) // good bandages multiply the length of flesh healing
@@ -67,7 +61,7 @@
// sanitization is checked after the clearing check but before the rest, because we freeze the effects of infection while we have sanitization
if(sanitization > 0)
- var/bandage_factor = (current_bandage ? current_bandage.splint_factor : 1)
+ var/bandage_factor = (limb.current_gauze ? limb.current_gauze.splint_factor : 1)
infestation = max(0, infestation - WOUND_BURN_SANITIZATION_RATE)
sanitization = max(0, sanitization - (WOUND_BURN_SANITIZATION_RATE * bandage_factor))
return
@@ -122,10 +116,10 @@
if(strikes_to_lose_limb <= 0)
return "[victim.p_their(TRUE)] [limb.name] is completely dead and unrecognizable as organic."
- var/condition = ""
- if(current_bandage)
+ var/list/condition = list("[victim.p_their(TRUE)] [limb.name] [examine_desc]")
+ if(limb.current_gauze)
var/bandage_condition
- switch(current_bandage.absorption_capacity)
+ switch(limb.current_gauze.absorption_capacity)
if(0 to 1.25)
bandage_condition = "nearly ruined "
if(1.25 to 2.75)
@@ -135,7 +129,7 @@
if(4 to INFINITY)
bandage_condition = "clean "
- condition += " underneath a dressing of [bandage_condition] [current_bandage.name]"
+ condition += " underneath a dressing of [bandage_condition] [limb.current_gauze.name]"
else
switch(infestation)
if(WOUND_INFECTION_MODERATE to WOUND_INFECTION_SEVERE)
@@ -149,7 +143,7 @@
else
condition += "!"
- return "[victim.p_their(TRUE)] [limb.name] [examine_desc][condition]"
+ return "[condition.Join()]"
/datum/wound/burn/get_scanner_description(mob/user)
if(strikes_to_lose_limb == 0)
@@ -186,7 +180,7 @@
/// if someone is using ointment on our burns
/datum/wound/burn/proc/ointment(obj/item/stack/medical/ointment/I, mob/user)
user.visible_message("[user] begins applying [I] to [victim]'s [limb.name]...", "You begin applying [I] to [user == victim ? "your" : "[victim]'s"] [limb.name]...")
- if(!do_after(user, (user == victim ? I.self_delay : I.other_delay), target = victim))
+ if(!do_after(user, (user == victim ? I.self_delay : I.other_delay), extra_checks = CALLBACK(src, .proc/still_exists)))
return
limb.heal_damage(I.heal_brute, I.heal_burn)
@@ -200,36 +194,6 @@
else
try_treating(I, user)
-/// for use in the burn dressing surgery since we don't want to make them do another do_after obviously
-/datum/wound/burn/proc/force_bandage(obj/item/stack/medical/gauze/I, mob/user)
- QDEL_NULL(current_bandage)
- current_bandage = new I.type(limb)
- current_bandage.amount = 1
- treat_priority = FALSE
- sanitization += I.sanitization
- I.use(1)
-
-/// if someone is wrapping gauze on our burns
-/datum/wound/burn/proc/bandage(obj/item/stack/medical/gauze/I, mob/user)
- if(current_bandage)
- if(current_bandage.absorption_capacity > I.absorption_capacity + 1)
- to_chat(user, "The [current_bandage] on [victim]'s [limb.name] is still in better condition than your [I.name]!")
- return
- user.visible_message("[user] begins to redress the burns on [victim]'s [limb.name] with [I]...", "You begin redressing the burns on [user == victim ? "your" : "[victim]'s"] [limb.name] with [I]...")
- else
- user.visible_message("[user] begins to dress the burns on [victim]'s [limb.name] with [I]...", "You begin dressing the burns on [user == victim ? "your" : "[victim]'s"] [limb.name] with [I]...")
-
- if(!do_after(user, (user == victim ? I.self_delay : I.other_delay), target=victim, extra_checks = CALLBACK(src, .proc/still_exists)))
- return
-
- user.visible_message("[user] applies [I] to [victim].", "You apply [I] to [user == victim ? "your" : "[victim]'s"] [limb.name].")
- QDEL_NULL(current_bandage)
- current_bandage = new I.type(limb)
- current_bandage.amount = 1
- treat_priority = FALSE
- sanitization += I.sanitization
- I.use(1)
-
/// if someone is using mesh on our burns
/datum/wound/burn/proc/mesh(obj/item/stack/medical/mesh/I, mob/user)
user.visible_message("[user] begins wrapping [victim]'s [limb.name] with [I]...", "You begin wrapping [user == victim ? "your" : "[victim]'s"] [limb.name] with [I]...")
@@ -249,7 +213,7 @@
/// Paramedic UV penlights
/datum/wound/burn/proc/uv(obj/item/flashlight/pen/paramedic/I, mob/user)
- if(I.uv_cooldown > world.time)
+ if(!COOLDOWN_FINISHED(I, uv_cooldown))
to_chat(user, "[I] is still recharging!")
return
if(infestation <= 0 || infestation < sanitization)
@@ -258,20 +222,29 @@
user.visible_message("[user] flashes the burns on [victim]'s [limb] with [I].", "You flash the burns on [user == victim ? "your" : "[victim]'s"] [limb.name] with [I].", vision_distance=COMBAT_MESSAGE_RANGE)
sanitization += I.uv_power
- I.uv_cooldown = world.time + I.uv_cooldown_length
+ COOLDOWN_START(I, uv_cooldown, I.uv_cooldown_length)
/datum/wound/burn/treat(obj/item/I, mob/user)
- if(istype(I, /obj/item/stack/medical/gauze))
- bandage(I, user)
- else if(istype(I, /obj/item/stack/medical/ointment))
+ if(istype(I, /obj/item/stack/medical/ointment))
ointment(I, user)
else if(istype(I, /obj/item/stack/medical/mesh))
mesh(I, user)
else if(istype(I, /obj/item/flashlight/pen/paramedic))
uv(I, user)
-/// basic support for instabitaluri/synthflesh healing flesh damage, more chem support in the future
-/datum/wound/burn/proc/regenerate_flesh(amount)
+// people complained about burns not healing on stasis beds, so in addition to checking if it's cured, they also get the special ability to very slowly heal on stasis beds if they have the healing effects stored
+/datum/wound/burn/on_stasis()
+ . = ..()
+ if(flesh_healing > 0)
+ flesh_damage = max(0, flesh_damage - 0.2)
+ if((flesh_damage <= 0) && (infestation <= 1))
+ to_chat(victim, "The burns on your [limb.name] have cleared up!")
+ qdel(src)
+ return
+ if(sanitization > 0)
+ infestation = max(0, infestation - WOUND_BURN_SANITIZATION_RATE * 0.2)
+
+/datum/wound/burn/on_synthflesh(amount)
flesh_healing += amount * 0.5 // 20u patch will heal 10 flesh standard
// we don't even care about first degree burns, straight to second
@@ -282,12 +255,12 @@
examine_desc = "is badly burned and breaking out in blisters"
occur_text = "breaks out with violent red burns"
severity = WOUND_SEVERITY_MODERATE
- damage_mulitplier_penalty = 1.1
- threshold_minimum = 40
+ damage_mulitplier_penalty = 1.05
+ threshold_minimum = 50
threshold_penalty = 30 // burns cause significant decrease in limb integrity compared to other wounds
status_effect_type = /datum/status_effect/wound/burn/moderate
flesh_damage = 5
- scarring_descriptions = list("small amoeba-shaped skinmarks", "a faded streak of depressed skin")
+ scar_keyword = "burnmoderate"
/datum/wound/burn/severe
name = "Third Degree Burns"
@@ -296,14 +269,14 @@
examine_desc = "appears seriously charred, with aggressive red splotches"
occur_text = "chars rapidly, exposing ruined tissue and spreading angry red burns"
severity = WOUND_SEVERITY_SEVERE
- damage_mulitplier_penalty = 1.2
- threshold_minimum = 80
+ damage_mulitplier_penalty = 1.1
+ threshold_minimum = 90
threshold_penalty = 40
status_effect_type = /datum/status_effect/wound/burn/severe
- treatable_by = list(/obj/item/flashlight/pen/paramedic, /obj/item/stack/medical/gauze, /obj/item/stack/medical/ointment, /obj/item/stack/medical/mesh)
+ treatable_by = list(/obj/item/flashlight/pen/paramedic, /obj/item/stack/medical/ointment, /obj/item/stack/medical/mesh)
infestation_rate = 0.05 // appx 13 minutes to reach sepsis without any treatment
flesh_damage = 12.5
- scarring_descriptions = list("a large, jagged patch of faded skin", "random spots of shiny, smooth skin", "spots of taut, leathery skin")
+ scar_keyword = "burnsevere"
/datum/wound/burn/critical
name = "Catastrophic Burns"
@@ -312,12 +285,12 @@
examine_desc = "is a ruined mess of blanched bone, melted fat, and charred tissue"
occur_text = "vaporizes as flesh, bone, and fat melt together in a horrifying mess"
severity = WOUND_SEVERITY_CRITICAL
- damage_mulitplier_penalty = 1.3
- sound_effect = 'sound/effects/sizzle2.ogg'
- threshold_minimum = 140
+ damage_mulitplier_penalty = 1.15
+ sound_effect = 'sound/effects/wounds/sizzle2.ogg'
+ threshold_minimum = 150
threshold_penalty = 80
status_effect_type = /datum/status_effect/wound/burn/critical
- treatable_by = list(/obj/item/flashlight/pen/paramedic, /obj/item/stack/medical/gauze, /obj/item/stack/medical/ointment, /obj/item/stack/medical/mesh)
+ treatable_by = list(/obj/item/flashlight/pen/paramedic, /obj/item/stack/medical/ointment, /obj/item/stack/medical/mesh)
infestation_rate = 0.15 // appx 4.33 minutes to reach sepsis without any treatment
flesh_damage = 20
- scarring_descriptions = list("massive, disfiguring keloid scars", "several long streaks of badly discolored and malformed skin", "unmistakeable splotches of dead tissue from serious burns")
+ scar_keyword = "burncritical"
diff --git a/code/datums/wounds/loss.dm b/code/datums/wounds/loss.dm
new file mode 100644
index 0000000000..6c5e536fb2
--- /dev/null
+++ b/code/datums/wounds/loss.dm
@@ -0,0 +1,41 @@
+/datum/wound/loss
+ name = "Dismembered"
+ desc = "oof ouch!!"
+
+ sound_effect = 'sound/effects/dismember.ogg'
+ severity = WOUND_SEVERITY_LOSS
+ threshold_minimum = 180
+ status_effect_type = null
+ scar_keyword = "dismember"
+ wound_flags = null
+
+/// Our special proc for our special dismembering, the wounding type only matters for what text we have
+/datum/wound/loss/proc/apply_dismember(obj/item/bodypart/dismembered_part, wounding_type=WOUND_SLASH)
+ if(!istype(dismembered_part) || !dismembered_part.owner || !(dismembered_part.body_zone in viable_zones) || isalien(dismembered_part.owner) || !dismembered_part.can_dismember())
+ qdel(src)
+ return
+
+ already_scarred = TRUE // so we don't scar a limb we don't have. If I add different levels of amputation desc, do it here
+
+ switch(wounding_type)
+ if(WOUND_BLUNT)
+ occur_text = "is shattered through the last bone holding it together, severing it completely!"
+ if(WOUND_SLASH)
+ occur_text = "is slashed through the last tissue holding it together, severing it completely!"
+ if(WOUND_PIERCE)
+ occur_text = "is pierced through the last tissue holding it together, severing it completely!"
+ if(WOUND_BURN)
+ occur_text = "is completely incinerated, falling to dust!"
+
+ victim = dismembered_part.owner
+
+ var/msg = "[victim]'s [dismembered_part.name] [occur_text]!"
+
+ victim.visible_message(msg, "Your [dismembered_part.name] [occur_text]!")
+
+ limb = dismembered_part
+ severity = WOUND_SEVERITY_LOSS
+ second_wind()
+ log_wound(victim, src)
+ dismembered_part.dismember(wounding_type == WOUND_BURN ? BURN : BRUTE)
+ qdel(src)
diff --git a/code/datums/wounds/pierce.dm b/code/datums/wounds/pierce.dm
new file mode 100644
index 0000000000..56c60b31d7
--- /dev/null
+++ b/code/datums/wounds/pierce.dm
@@ -0,0 +1,170 @@
+/*
+ Pierce
+*/
+
+/datum/wound/pierce
+ sound_effect = 'sound/weapons/slice.ogg'
+ processes = TRUE
+ wound_type = WOUND_PIERCE
+ treatable_by = list(/obj/item/stack/medical/suture)
+ treatable_tool = TOOL_CAUTERY
+ base_treat_time = 3 SECONDS
+ wound_flags = (FLESH_WOUND | ACCEPTS_GAUZE)
+
+ /// How much blood we start losing when this wound is first applied
+ var/initial_flow
+ /// If gauzed, what percent of the internal bleeding actually clots of the total absorption rate
+ var/gauzed_clot_rate
+
+ /// When hit on this bodypart, we have this chance of losing some blood + the incoming damage
+ var/internal_bleeding_chance
+ /// If we let off blood when hit, the max blood lost is this * the incoming damage
+ var/internal_bleeding_coefficient
+
+/datum/wound/pierce/wound_injury(datum/wound/old_wound)
+ blood_flow = initial_flow
+
+/datum/wound/pierce/receive_damage(wounding_type, wounding_dmg, wound_bonus)
+ if(victim.stat == DEAD || wounding_dmg < 5)
+ return
+ if(victim.blood_volume && prob(internal_bleeding_chance + wounding_dmg))
+ if(limb.current_gauze && limb.current_gauze.splint_factor)
+ wounding_dmg *= (1 - limb.current_gauze.splint_factor)
+ var/blood_bled = rand(1, wounding_dmg * internal_bleeding_coefficient) // 12 brute toolbox can cause up to 15/18/21 bloodloss on mod/sev/crit
+ switch(blood_bled)
+ if(1 to 6)
+ victim.bleed(blood_bled, TRUE)
+ if(7 to 13)
+ victim.visible_message("Blood droplets fly from the hole in [victim]'s [limb.name].", "You cough up a bit of blood from the blow to your [limb.name].", vision_distance=COMBAT_MESSAGE_RANGE)
+ victim.bleed(blood_bled, TRUE)
+ if(14 to 19)
+ victim.visible_message("A small stream of blood spurts from the hole in [victim]'s [limb.name]!", "You spit out a string of blood from the blow to your [limb.name]!", vision_distance=COMBAT_MESSAGE_RANGE)
+ new /obj/effect/temp_visual/dir_setting/bloodsplatter(victim.loc, victim.dir)
+ victim.bleed(blood_bled)
+ if(20 to INFINITY)
+ victim.visible_message("A spray of blood streams from the gash in [victim]'s [limb.name]!", "You choke up on a spray of blood from the blow to your [limb.name]!", vision_distance=COMBAT_MESSAGE_RANGE)
+ victim.bleed(blood_bled)
+ new /obj/effect/temp_visual/dir_setting/bloodsplatter(victim.loc, victim.dir)
+ victim.add_splatter_floor(get_step(victim.loc, victim.dir))
+
+/datum/wound/pierce/handle_process()
+ blood_flow = min(blood_flow, WOUND_SLASH_MAX_BLOODFLOW)
+
+ if(victim.bodytemperature < (BODYTEMP_NORMAL - 10))
+ blood_flow -= 0.2
+ if(prob(5))
+ to_chat(victim, "You feel the [lowertext(name)] in your [limb.name] firming up from the cold!")
+
+ if(victim.reagents?.has_reagent(/datum/reagent/toxin/heparin))
+ blood_flow += 0.5 // old herapin used to just add +2 bleed stacks per tick, this adds 0.5 bleed flow to all open cuts which is probably even stronger as long as you can cut them first
+
+ if(limb.current_gauze)
+ blood_flow -= limb.current_gauze.absorption_rate * gauzed_clot_rate
+ limb.current_gauze.absorption_capacity -= limb.current_gauze.absorption_rate
+
+ if(blood_flow <= 0)
+ qdel(src)
+
+/datum/wound/pierce/on_stasis()
+ . = ..()
+ if(blood_flow <= 0)
+ qdel(src)
+
+/datum/wound/pierce/treat(obj/item/I, mob/user)
+ if(istype(I, /obj/item/stack/medical/suture))
+ suture(I, user)
+ else if(I.tool_behaviour == TOOL_CAUTERY || I.get_temperature() > 300)
+ tool_cauterize(I, user)
+
+/datum/wound/pierce/on_xadone(power)
+ . = ..()
+ blood_flow -= 0.03 * power // i think it's like a minimum of 3 power, so .09 blood_flow reduction per tick is pretty good for 0 effort
+
+/datum/wound/pierce/on_synthflesh(power)
+ . = ..()
+ blood_flow -= 0.05 * power // 20u * 0.05 = -1 blood flow, less than with slashes but still good considering smaller bleed rates
+
+/// If someone is using a suture to close this cut
+/datum/wound/pierce/proc/suture(obj/item/stack/medical/suture/I, mob/user)
+ var/self_penalty_mult = (user == victim ? 1.4 : 1)
+ user.visible_message("[user] begins stitching [victim]'s [limb.name] with [I]...", "You begin stitching [user == victim ? "your" : "[victim]'s"] [limb.name] with [I]...")
+ if(!do_after(user, base_treat_time * self_penalty_mult, target=victim, extra_checks = CALLBACK(src, .proc/still_exists)))
+ return
+ user.visible_message("[user] stitches up some of the bleeding on [victim].", "You stitch up some of the bleeding on [user == victim ? "yourself" : "[victim]"].")
+ var/blood_sutured = I.stop_bleeding / self_penalty_mult * 0.5
+ blood_flow -= blood_sutured
+ limb.heal_damage(I.heal_brute, I.heal_burn)
+
+ if(blood_flow > 0)
+ try_treating(I, user)
+ else
+ to_chat(user, "You successfully close the hole in [user == victim ? "your" : "[victim]'s"] [limb.name].")
+
+/// If someone is using either a cautery tool or something with heat to cauterize this pierce
+/datum/wound/pierce/proc/tool_cauterize(obj/item/I, mob/user)
+ var/self_penalty_mult = (user == victim ? 1.5 : 1)
+ user.visible_message("[user] begins cauterizing [victim]'s [limb.name] with [I]...", "You begin cauterizing [user == victim ? "your" : "[victim]'s"] [limb.name] with [I]...")
+ if(!do_after(user, base_treat_time * self_penalty_mult, target=victim, extra_checks = CALLBACK(src, .proc/still_exists)))
+ return
+
+ user.visible_message("[user] cauterizes some of the bleeding on [victim].", "You cauterize some of the bleeding on [victim].")
+ limb.receive_damage(burn = 2 + severity, wound_bonus = CANT_WOUND)
+ if(prob(30))
+ victim.emote("scream")
+ var/blood_cauterized = (0.6 / self_penalty_mult) * 0.5
+ blood_flow -= blood_cauterized
+
+ if(blood_flow > 0)
+ try_treating(I, user)
+
+/datum/wound/pierce/moderate
+ name = "Minor Breakage"
+ desc = "Patient's skin has been broken open, causing severe bruising and minor internal bleeding in affected area."
+ treat_text = "Treat affected site with bandaging or exposure to extreme cold. In dire cases, brief exposure to vacuum may suffice." // space is cold in ss13, so it's like an ice pack!
+ examine_desc = "has a small, circular hole, gently bleeding"
+ occur_text = "spurts out a thin stream of blood"
+ sound_effect = 'sound/effects/wounds/pierce1.ogg'
+ severity = WOUND_SEVERITY_MODERATE
+ initial_flow = 1.5
+ gauzed_clot_rate = 0.8
+ internal_bleeding_chance = 30
+ internal_bleeding_coefficient = 1.25
+ threshold_minimum = 40
+ threshold_penalty = 15
+ status_effect_type = /datum/status_effect/wound/pierce/moderate
+ scar_keyword = "piercemoderate"
+
+/datum/wound/pierce/severe
+ name = "Open Puncture"
+ desc = "Patient's internal tissue is penetrated, causing sizeable internal bleeding and reduced limb stability."
+ treat_text = "Repair punctures in skin by suture or cautery, extreme cold may also work."
+ examine_desc = "is pierced clear through, with bits of tissue obscuring the open hole"
+ occur_text = "looses a violent spray of blood, revealing a pierced wound"
+ sound_effect = 'sound/effects/wounds/pierce2.ogg'
+ severity = WOUND_SEVERITY_SEVERE
+ initial_flow = 2.25
+ gauzed_clot_rate = 0.6
+ internal_bleeding_chance = 60
+ internal_bleeding_coefficient = 1.5
+ threshold_minimum = 60
+ threshold_penalty = 25
+ status_effect_type = /datum/status_effect/wound/pierce/severe
+ scar_keyword = "piercesevere"
+
+/datum/wound/pierce/critical
+ name = "Ruptured Cavity"
+ desc = "Patient's internal tissue and circulatory system is shredded, causing significant internal bleeding and damage to internal organs."
+ treat_text = "Surgical repair of puncture wound, followed by supervised resanguination."
+ examine_desc = "is ripped clear through, barely held together by exposed bone"
+ occur_text = "blasts apart, sending chunks of viscera flying in all directions"
+ sound_effect = 'sound/effects/wounds/pierce3.ogg'
+ severity = WOUND_SEVERITY_CRITICAL
+ initial_flow = 3
+ gauzed_clot_rate = 0.4
+ internal_bleeding_chance = 80
+ internal_bleeding_coefficient = 1.75
+ threshold_minimum = 110
+ threshold_penalty = 40
+ status_effect_type = /datum/status_effect/wound/pierce/critical
+ scar_keyword = "piercecritical"
+ wound_flags = (FLESH_WOUND | ACCEPTS_GAUZE | MANGLES_FLESH)
diff --git a/code/datums/wounds/cuts.dm b/code/datums/wounds/slash.dm
similarity index 60%
rename from code/datums/wounds/cuts.dm
rename to code/datums/wounds/slash.dm
index 59ce286884..9044835272 100644
--- a/code/datums/wounds/cuts.dm
+++ b/code/datums/wounds/slash.dm
@@ -1,17 +1,16 @@
-
/*
Cuts
*/
-/datum/wound/brute/cut
+/datum/wound/slash
sound_effect = 'sound/weapons/slice.ogg'
processes = TRUE
- wound_type = WOUND_LIST_CUT
- treatable_by = list(/obj/item/stack/medical/suture, /obj/item/stack/medical/gauze)
+ wound_type = WOUND_SLASH
+ treatable_by = list(/obj/item/stack/medical/suture)
treatable_by_grabbed = list(/obj/item/gun/energy/laser)
treatable_tool = TOOL_CAUTERY
- treat_priority = TRUE
base_treat_time = 3 SECONDS
+ wound_flags = (FLESH_WOUND | ACCEPTS_GAUZE)
/// How much blood we start losing when this wound is first applied
var/initial_flow
@@ -27,75 +26,82 @@
var/max_per_type
/// The maximum flow we've had so far
var/highest_flow
- /// How much flow we've already cauterized
- var/cauterized
- /// How much flow we've already sutured
- var/sutured
- /// The current bandage we have for this wound (maybe move bandages to the limb?)
- var/obj/item/stack/current_bandage
/// A bad system I'm using to track the worst scar we earned (since we can demote, we want the biggest our wound has been, not what it was when it was cured (probably moderate))
var/datum/scar/highest_scar
-/datum/wound/brute/cut/wound_injury(datum/wound/brute/cut/old_wound = null)
+/datum/wound/slash/wound_injury(datum/wound/slash/old_wound = null)
blood_flow = initial_flow
if(old_wound)
blood_flow = max(old_wound.blood_flow, initial_flow)
if(old_wound.severity > severity && old_wound.highest_scar)
highest_scar = old_wound.highest_scar
old_wound.highest_scar = null
- if(old_wound.current_bandage)
- current_bandage = old_wound.current_bandage
- old_wound.current_bandage = null
if(!highest_scar)
highest_scar = new
highest_scar.generate(limb, src, add_to_scars=FALSE)
-/datum/wound/brute/cut/remove_wound(ignore_limb, replaced)
+/datum/wound/slash/remove_wound(ignore_limb, replaced)
if(!replaced && highest_scar)
already_scarred = TRUE
highest_scar.lazy_attach(limb)
return ..()
-/datum/wound/brute/cut/get_examine_description(mob/user)
- if(!current_bandage)
+/datum/wound/slash/get_examine_description(mob/user)
+ if(!limb.current_gauze)
return ..()
- var/bandage_condition = ""
+ var/list/msg = list("The cuts on [victim.p_their()] [limb.name] are wrapped with")
// how much life we have left in these bandages
- switch(current_bandage.absorption_capacity)
+ switch(limb.current_gauze.absorption_capacity)
if(0 to 1.25)
- bandage_condition = "nearly ruined "
+ msg += "nearly ruined "
if(1.25 to 2.75)
- bandage_condition = "badly worn "
+ msg += "badly worn "
if(2.75 to 4)
- bandage_condition = "slightly bloodied "
+ msg += "slightly bloodied "
if(4 to INFINITY)
- bandage_condition = "clean "
- return "The cuts on [victim.p_their()] [limb.name] are wrapped with [bandage_condition] [current_bandage.name]!"
+ msg += "clean "
+ msg += "[limb.current_gauze.name]!"
-/datum/wound/brute/cut/receive_damage(wounding_type, wounding_dmg, wound_bonus)
- if(victim.stat != DEAD && wounding_type == WOUND_SHARP) // can't stab dead bodies to make it bleed faster this way
+ return "[msg.Join()]"
+
+/datum/wound/slash/receive_damage(wounding_type, wounding_dmg, wound_bonus)
+ if(victim.stat != DEAD && wounding_type == WOUND_SLASH) // can't stab dead bodies to make it bleed faster this way
blood_flow += 0.05 * wounding_dmg
-/datum/wound/brute/cut/handle_process()
- blood_flow = min(blood_flow, WOUND_CUT_MAX_BLOODFLOW)
+/datum/wound/slash/drag_bleed_amount()
+ // say we have 3 severe cuts with 3 blood flow each, pretty reasonable
+ // compare with being at 100 brute damage before, where you bled (brute/100 * 2), = 2 blood per tile
+ var/bleed_amt = min(blood_flow * 0.1, 1) // 3 * 3 * 0.1 = 0.9 blood total, less than before! the share here is .3 blood of course.
- if(victim.reagents && victim.reagents.has_reagent(/datum/reagent/toxin/heparin))
+ if(limb.current_gauze) // gauze stops all bleeding from dragging on this limb, but wears the gauze out quicker
+ limb.seep_gauze(bleed_amt * 0.33)
+ return
+
+ return bleed_amt
+
+/datum/wound/slash/handle_process()
+ if(victim.stat == DEAD)
+ blood_flow -= max(clot_rate, WOUND_SLASH_DEAD_CLOT_MIN)
+ if(blood_flow < minimum_flow)
+ if(demotes_to)
+ replace_wound(demotes_to)
+ return
+ qdel(src)
+ return
+
+ blood_flow = min(blood_flow, WOUND_SLASH_MAX_BLOODFLOW)
+
+ if(victim.reagents?.has_reagent(/datum/reagent/toxin/heparin))
blood_flow += 0.5 // old herapin used to just add +2 bleed stacks per tick, this adds 0.5 bleed flow to all open cuts which is probably even stronger as long as you can cut them first
- else if(victim.reagents && victim.reagents.has_reagent(/datum/reagent/medicine/coagulant))
- blood_flow -= 0.25
- if(current_bandage)
+ if(limb.current_gauze)
if(clot_rate > 0)
blood_flow -= clot_rate
- blood_flow -= current_bandage.absorption_rate
- current_bandage.absorption_capacity -= current_bandage.absorption_rate
- if(current_bandage.absorption_capacity < 0)
- victim.visible_message("Blood soaks through \the [current_bandage] on [victim]'s [limb.name].", "Blood soaks through \the [current_bandage] on your [limb.name].", vision_distance=COMBAT_MESSAGE_RANGE)
- QDEL_NULL(current_bandage)
- treat_priority = TRUE
+ blood_flow -= limb.current_gauze.absorption_rate
+ limb.seep_gauze(limb.current_gauze.absorption_rate)
else
blood_flow -= clot_rate
@@ -109,41 +115,57 @@
to_chat(victim, "The cut on your [limb.name] has stopped bleeding!")
qdel(src)
+
+/datum/wound/slash/on_stasis()
+ if(blood_flow >= minimum_flow)
+ return
+ if(demotes_to)
+ replace_wound(demotes_to)
+ return
+ qdel(src)
+
/* BEWARE, THE BELOW NONSENSE IS MADNESS. bones.dm looks more like what I have in mind and is sufficiently clean, don't pay attention to this messiness */
-/datum/wound/brute/cut/check_grab_treatments(obj/item/I, mob/user)
+/datum/wound/slash/check_grab_treatments(obj/item/I, mob/user)
if(istype(I, /obj/item/gun/energy/laser))
return TRUE
-/datum/wound/brute/cut/treat(obj/item/I, mob/user)
+/datum/wound/slash/treat(obj/item/I, mob/user)
if(istype(I, /obj/item/gun/energy/laser))
las_cauterize(I, user)
else if(I.tool_behaviour == TOOL_CAUTERY || I.get_temperature() > 300)
tool_cauterize(I, user)
- else if(istype(I, /obj/item/stack/medical/gauze))
- bandage(I, user)
else if(istype(I, /obj/item/stack/medical/suture))
suture(I, user)
-/datum/wound/brute/cut/try_handling(mob/living/carbon/human/user)
+/datum/wound/slash/try_handling(mob/living/carbon/human/user)
if(user.pulling != victim || user.zone_selected != limb.body_zone || user.a_intent == INTENT_GRAB)
return FALSE
if(!iscatperson(user))
return FALSE
- if(!(user.client?.prefs.vore_flags & LICKABLE))
- return FALSE
-
lick_wounds(user)
return TRUE
/// if a felinid is licking this cut to reduce bleeding
-/datum/wound/brute/cut/proc/lick_wounds(mob/living/carbon/human/user)
+/datum/wound/slash/proc/lick_wounds(mob/living/carbon/human/user)
if(INTERACTING_WITH(user, victim))
to_chat(user, "You're already interacting with [victim]!")
return
+ if(user.is_mouth_covered())
+ to_chat(user, "Your mouth is covered, you can't lick [victim]'s wounds!")
+ return
+
+ if(!user.getorganslot(ORGAN_SLOT_TONGUE))
+ to_chat(user, "You can't lick wounds without a tongue!") // f in chat
+ return
+
+ // transmission is one way patient -> felinid since google said cat saliva is antiseptic or whatever, and also because felinids are already risking getting beaten for this even without people suspecting they're spreading a deathvirus
+ for(var/datum/disease/D in victim.diseases)
+ user.ForceContractDisease(D)
+
user.visible_message("[user] begins licking the wounds on [victim]'s [limb.name].", "You begin licking the wounds on [victim]'s [limb.name]...", ignored_mobs=victim)
to_chat(victim, "[user] begins to lick the wounds on your [limb.name].You successfully lower the severity of [victim]'s cuts.")
-/datum/wound/brute/cut/on_xadone(power)
+/datum/wound/slash/on_xadone(power)
. = ..()
blood_flow -= 0.03 * power // i think it's like a minimum of 3 power, so .09 blood_flow reduction per tick is pretty good for 0 effort
+/datum/wound/slash/on_synthflesh(power)
+ . = ..()
+ blood_flow -= 0.075 * power // 20u * 0.075 = -1.5 blood flow, pretty good for how little effort it is
+
/// If someone's putting a laser gun up to our cut to cauterize it
-/datum/wound/brute/cut/proc/las_cauterize(obj/item/gun/energy/laser/lasgun, mob/user)
+/datum/wound/slash/proc/las_cauterize(obj/item/gun/energy/laser/lasgun, mob/user)
var/self_penalty_mult = (user == victim ? 1.25 : 1)
user.visible_message("[user] begins aiming [lasgun] directly at [victim]'s [limb.name]...", "You begin aiming [lasgun] directly at [user == victim ? "your" : "[victim]'s"] [limb.name]...")
if(!do_after(user, base_treat_time * self_penalty_mult, target=victim, extra_checks = CALLBACK(src, .proc/still_exists)))
@@ -180,11 +206,10 @@
return
victim.emote("scream")
blood_flow -= damage / (5 * self_penalty_mult) // 20 / 5 = 4 bloodflow removed, p good
- cauterized += damage / (5 * self_penalty_mult)
victim.visible_message("The cuts on [victim]'s [limb.name] scar over!")
/// If someone is using either a cautery tool or something with heat to cauterize this cut
-/datum/wound/brute/cut/proc/tool_cauterize(obj/item/I, mob/user)
+/datum/wound/slash/proc/tool_cauterize(obj/item/I, mob/user)
var/self_penalty_mult = (user == victim ? 1.5 : 1)
user.visible_message("[user] begins cauterizing [victim]'s [limb.name] with [I]...", "You begin cauterizing [user == victim ? "your" : "[victim]'s"] [limb.name] with [I]...")
if(!do_after(user, base_treat_time * self_penalty_mult, target=victim, extra_checks = CALLBACK(src, .proc/still_exists)))
@@ -196,7 +221,6 @@
victim.emote("scream")
var/blood_cauterized = (0.6 / self_penalty_mult)
blood_flow -= blood_cauterized
- cauterized += blood_cauterized
if(blood_flow > minimum_flow)
try_treating(I, user)
@@ -204,15 +228,15 @@
to_chat(user, "You successfully lower the severity of [user == victim ? "your" : "[victim]'s"] cuts.")
/// If someone is using a suture to close this cut
-/datum/wound/brute/cut/proc/suture(obj/item/stack/medical/suture/I, mob/user)
+/datum/wound/slash/proc/suture(obj/item/stack/medical/suture/I, mob/user)
var/self_penalty_mult = (user == victim ? 1.4 : 1)
user.visible_message("[user] begins stitching [victim]'s [limb.name] with [I]...", "You begin stitching [user == victim ? "your" : "[victim]'s"] [limb.name] with [I]...")
+
if(!do_after(user, base_treat_time * self_penalty_mult, target=victim, extra_checks = CALLBACK(src, .proc/still_exists)))
return
user.visible_message("[user] stitches up some of the bleeding on [victim].", "You stitch up some of the bleeding on [user == victim ? "yourself" : "[victim]"].")
var/blood_sutured = I.stop_bleeding / self_penalty_mult
blood_flow -= blood_sutured
- sutured += blood_sutured
limb.heal_damage(I.heal_brute, I.heal_burn)
if(blood_flow > minimum_flow)
@@ -220,95 +244,57 @@
else if(demotes_to)
to_chat(user, "You successfully lower the severity of [user == victim ? "your" : "[victim]'s"] cuts.")
-/// If someone is using gauze on this cut
-/datum/wound/brute/cut/proc/bandage(obj/item/stack/I, mob/user)
- if(current_bandage)
- if(current_bandage.absorption_capacity > I.absorption_capacity + 1)
- to_chat(user, "The [current_bandage] on [victim]'s [limb.name] is still in better condition than your [I.name]!")
- return
- else
- user.visible_message("[user] begins rewrapping the cuts on [victim]'s [limb.name] with [I]...", "You begin rewrapping the cuts on [user == victim ? "your" : "[victim]'s"] [limb.name] with [I]...")
- else
- user.visible_message("[user] begins wrapping the cuts on [victim]'s [limb.name] with [I]...", "You begin wrapping the cuts on [user == victim ? "your" : "[victim]'s"] [limb.name] with [I]...")
- if(!do_after(user, base_treat_time, target=victim, extra_checks = CALLBACK(src, .proc/still_exists)))
- return
- user.visible_message("[user] applies [I] to [victim]'s [limb.name].", "You bandage some of the bleeding on [user == victim ? "yourself" : "[victim]"].")
- QDEL_NULL(current_bandage)
- current_bandage = new I.type(limb)
- current_bandage.amount = 1
- treat_priority = FALSE
- I.use(1)
-
-
-/datum/wound/brute/cut/moderate
+/datum/wound/slash/moderate
name = "Rough Abrasion"
desc = "Patient's skin has been badly scraped, generating moderate blood loss."
treat_text = "Application of clean bandages or first-aid grade sutures, followed by food and rest."
examine_desc = "has an open cut"
occur_text = "is cut open, slowly leaking blood"
- sound_effect = 'sound/effects/blood1.ogg'
+ sound_effect = 'sound/effects/wounds/blood1.ogg'
severity = WOUND_SEVERITY_MODERATE
- initial_flow = 2
- minimum_flow = 0.5
+ initial_flow = 1.5
+ minimum_flow = 0.375
max_per_type = 3
- clot_rate = 0.15
- threshold_minimum = 20
+ clot_rate = 0.12
+ threshold_minimum = 30
threshold_penalty = 10
- status_effect_type = /datum/status_effect/wound/cut/moderate
- scarring_descriptions = list("light, faded lines", "minor cut marks", "a small faded slit", "a series of small scars")
+ status_effect_type = /datum/status_effect/wound/slash/moderate
+ scar_keyword = "slashmoderate"
-/datum/wound/brute/cut/severe
+/datum/wound/slash/severe
name = "Open Laceration"
desc = "Patient's skin is ripped clean open, allowing significant blood loss."
treat_text = "Speedy application of first-aid grade sutures and clean bandages, followed by vitals monitoring to ensure recovery."
examine_desc = "has a severe cut"
occur_text = "is ripped open, veins spurting blood"
- sound_effect = 'sound/effects/blood2.ogg'
+ sound_effect = 'sound/effects/wounds/blood2.ogg'
severity = WOUND_SEVERITY_SEVERE
- initial_flow = 3.25
- minimum_flow = 2.75
+ initial_flow = 2.4375
+ minimum_flow = 2.0625
clot_rate = 0.07
max_per_type = 4
- threshold_minimum = 50
+ threshold_minimum = 60
threshold_penalty = 25
- demotes_to = /datum/wound/brute/cut/moderate
- status_effect_type = /datum/status_effect/wound/cut/severe
- scarring_descriptions = list("a twisted line of faded gashes", "a gnarled sickle-shaped slice scar", "a long-faded puncture wound")
+ demotes_to = /datum/wound/slash/moderate
+ status_effect_type = /datum/status_effect/wound/slash/severe
+ scar_keyword = "slashsevere"
-/datum/wound/brute/cut/critical
+/datum/wound/slash/critical
name = "Weeping Avulsion"
desc = "Patient's skin is completely torn open, along with significant loss of tissue. Extreme blood loss will lead to quick death without intervention."
treat_text = "Immediate bandaging and either suturing or cauterization, followed by supervised resanguination."
- examine_desc = "is spurting blood at an alarming rate"
+ examine_desc = "is carved down to the bone, spraying blood wildly"
occur_text = "is torn open, spraying blood wildly"
- sound_effect = 'sound/effects/blood3.ogg'
+ sound_effect = 'sound/effects/wounds/blood3.ogg'
severity = WOUND_SEVERITY_CRITICAL
- initial_flow = 4.25
- minimum_flow = 4
+ initial_flow = 3.1875
+ minimum_flow = 3
clot_rate = -0.05 // critical cuts actively get worse instead of better
max_per_type = 5
- threshold_minimum = 80
+ threshold_minimum = 90
threshold_penalty = 40
- demotes_to = /datum/wound/brute/cut/severe
- status_effect_type = /datum/status_effect/wound/cut/critical
- scarring_descriptions = list("a winding path of very badly healed scar tissue", "a series of peaks and valleys along a gruesome line of cut scar tissue", "a grotesque snake of indentations and stitching scars")
-
-// TODO: see about moving dismemberment over to this, i'll have to add judging dismembering power/wound potential wrt item size i guess
-/datum/wound/brute/cut/loss
- name = "Dismembered"
- desc = "oof ouch!!"
- occur_text = "is violently dismembered!"
- sound_effect = 'sound/effects/dismember.ogg'
- viable_zones = list(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG)
- severity = WOUND_SEVERITY_LOSS
- threshold_minimum = 180
- status_effect_type = null
-
-/datum/wound/brute/cut/loss/apply_wound(obj/item/bodypart/L, silent, datum/wound/brute/cut/old_wound, smited = FALSE)
- if(!L.dismemberable)
- qdel(src)
- return
-
- L.dismember()
- qdel(src)
+ demotes_to = /datum/wound/slash/severe
+ status_effect_type = /datum/status_effect/wound/slash/critical
+ scar_keyword = "slashcritical"
+ wound_flags = (FLESH_WOUND | ACCEPTS_GAUZE | MANGLES_FLESH)
diff --git a/code/game/area/Space_Station_13_areas.dm b/code/game/area/Space_Station_13_areas.dm
index cbad2f7ab0..5310b24202 100644
--- a/code/game/area/Space_Station_13_areas.dm
+++ b/code/game/area/Space_Station_13_areas.dm
@@ -43,6 +43,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
outdoors = TRUE
ambientsounds = SPACE
blob_allowed = FALSE //Eating up space doesn't count for victory as a blob.
+ considered_hull_exterior = TRUE
/area/space/nearstation
icon_state = "space_near"
@@ -1592,4 +1593,4 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
valid_territory = FALSE
outdoors = TRUE
ambientsounds = SPACE
- blob_allowed = FALSE //While part of the station, what good will it do you?
\ No newline at end of file
+ blob_allowed = FALSE //While part of the station, what good will it do you?
diff --git a/code/game/area/areas.dm b/code/game/area/areas.dm
index f87f7a757c..b9415f3cf8 100644
--- a/code/game/area/areas.dm
+++ b/code/game/area/areas.dm
@@ -16,6 +16,8 @@
/// If it's valid territory for gangs/cults to summon
var/valid_territory = TRUE
+ /// malf ais can hack this
+ var/valid_malf_hack = TRUE
/// if blobs can spawn there and if it counts towards their score.
var/blob_allowed = TRUE
/// whether servants can warp into this area from Reebe
@@ -32,6 +34,9 @@
/// If megafauna can be spawned by natural random generation
var/megafauna_spawn_allowed = FALSE
+ /// Considered space for hull shielding
+ var/considered_hull_exterior = FALSE
+
var/fire = null
var/atmos = TRUE
var/atmosalm = FALSE
@@ -572,6 +577,7 @@ GLOBAL_LIST_EMPTY(teleportlocs)
power_environ = FALSE
always_unpowered = FALSE
valid_territory = FALSE
+ valid_malf_hack = FALSE
blob_allowed = FALSE
addSorted()
diff --git a/code/game/atoms.dm b/code/game/atoms.dm
index de9df3a23e..8a66394ecc 100644
--- a/code/game/atoms.dm
+++ b/code/game/atoms.dm
@@ -26,7 +26,6 @@
var/list/atom_colours //used to store the different colors on an atom
//its inherent color, the colored paint applied on it, special color effect etc...
- var/list/priority_overlays //overlays that should remain on top and not normally removed when using cut_overlay functions, like c4.
var/list/remove_overlays // a very temporary list of overlays to remove
var/list/add_overlays // a very temporary list of overlays to add
@@ -145,7 +144,6 @@
qdel(reagents)
LAZYCLEARLIST(overlays)
- LAZYCLEARLIST(priority_overlays)
for(var/i in targeted_by)
var/mob/M = i
@@ -962,15 +960,16 @@
if(source != target)
target.log_talk(message, message_type, tag="[tag] from [key_name(source)]", log_globally=FALSE)
-/*
-Proc for attack log creation, because really why not
-1 argument is the actor performing the action
-2 argument is the target of the action
-3 is a verb describing the action (e.g. punched, throwed, kicked, etc.)
-4 is a tool with which the action was made (usually an item)
-5 is any additional text, which will be appended to the rest of the log line
-*/
-
+/**
+ * Log a combat message in the attack log
+ *
+ * Arguments:
+ * * atom/user - argument is the actor performing the action
+ * * atom/target - argument is the target of the action
+ * * what_done - is a verb describing the action (e.g. punched, throwed, kicked, etc.)
+ * * atom/object - is a tool with which the action was made (usually an item)
+ * * addition - is any additional text, which will be appended to the rest of the log line
+ */
/proc/log_combat(atom/user, atom/target, what_done, atom/object=null, addition=null)
var/ssource = key_name(user)
var/starget = key_name(target)
@@ -1120,3 +1119,11 @@ Proc for attack log creation, because really why not
max_grav = max(G.setting,max_grav)
return max_grav
return SSmapping.level_trait(T.z, ZTRAIT_GRAVITY)
+
+/**
+ * Causes effects when the atom gets hit by a rust effect from heretics
+ *
+ * Override this if you want custom behaviour in whatever gets hit by the rust
+ */
+/atom/proc/rust_heretic_act()
+ return
diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm
index 0238529195..0a6c2b9eca 100644
--- a/code/game/atoms_movable.dm
+++ b/code/game/atoms_movable.dm
@@ -646,3 +646,10 @@
animate(I, alpha = 175, pixel_x = to_x, pixel_y = to_y, time = 3, transform = M, easing = CUBIC_EASING)
sleep(1)
animate(I, alpha = 0, transform = matrix(), time = 1)
+
+/atom/movable/proc/set_anchored(anchorvalue) //literally only for plumbing ran
+ SHOULD_CALL_PARENT(TRUE)
+ if(anchored == anchorvalue)
+ return
+ . = anchored
+ anchored = anchorvalue
diff --git a/code/game/gamemodes/clock_cult/clock_cult.dm b/code/game/gamemodes/clock_cult/clock_cult.dm
index 154d2df563..be8dee5bf8 100644
--- a/code/game/gamemodes/clock_cult/clock_cult.dm
+++ b/code/game/gamemodes/clock_cult/clock_cult.dm
@@ -213,6 +213,9 @@ Credit where due:
var/list/slots = list("In your left pocket" = SLOT_L_STORE, "In your right pocket" = SLOT_R_STORE, "In your backpack" = SLOT_IN_BACKPACK)
if(ishuman(L))
var/mob/living/carbon/human/H = L
+ var/obj/item/clockwork/replica_fabricator/F = new
+ if(H.equip_to_slot_or_del(F, SLOT_IN_BACKPACK))
+ to_chat(H, "You have been equipped with a replica fabricator, an advanced tool that can convert objects like doors, tables or even coats into clockwork equivalents.")
slot = H.equip_in_one_of_slots(S, slots)
if(slot == "In your backpack")
slot = "In your [H.back.name]"
@@ -221,8 +224,7 @@ Credit where due:
qdel(S)
if(S && !QDELETED(S))
to_chat(L, "[slot] is a clockwork slab, a multipurpose tool used to construct machines and invoke ancient words of power. If this is your first time \
- as a servant, you can find a concise tutorial in the Recollection category of its interface.")
- to_chat(L, "If you want more information, you can read the wiki page to learn more.")
+ as a servant, you can read the wiki page to learn more.")
return TRUE
return FALSE
diff --git a/code/game/gamemodes/clown_ops/clown_weapons.dm b/code/game/gamemodes/clown_ops/clown_weapons.dm
index afa3dbfeaa..a27cb02ea8 100644
--- a/code/game/gamemodes/clown_ops/clown_weapons.dm
+++ b/code/game/gamemodes/clown_ops/clown_weapons.dm
@@ -62,7 +62,7 @@
hitsound = null
attack_verb_on = list("slipped")
clumsy_check = FALSE
- sharpness = IS_BLUNT
+ sharpness = SHARP_NONE
sword_color = "yellow"
heat = 0
light_color = "#ffff00"
diff --git a/code/game/gamemodes/dynamic/dynamic.dm b/code/game/gamemodes/dynamic/dynamic.dm
index 659d50c077..eac7347479 100644
--- a/code/game/gamemodes/dynamic/dynamic.dm
+++ b/code/game/gamemodes/dynamic/dynamic.dm
@@ -6,10 +6,6 @@ GLOBAL_VAR_INIT(dynamic_latejoin_delay_max, (30 MINUTES))
GLOBAL_VAR_INIT(dynamic_midround_delay_min, (10 MINUTES))
GLOBAL_VAR_INIT(dynamic_midround_delay_max, (30 MINUTES))
-GLOBAL_VAR_INIT(dynamic_event_delay_min, (10 MINUTES))
-GLOBAL_VAR_INIT(dynamic_event_delay_max, (30 MINUTES)) // this is on top of regular events, so can't be quite as often
-
-
// -- Roundstart injection delays
GLOBAL_VAR_INIT(dynamic_first_latejoin_delay_min, (2 MINUTES))
GLOBAL_VAR_INIT(dynamic_first_latejoin_delay_max, (30 MINUTES))
@@ -58,7 +54,7 @@ GLOBAL_VAR_INIT(dynamic_forced_storyteller, null)
// Threat logging vars
/// Starting threat level, for things that increase it but can bring it back down.
var/initial_threat_level = 0
- /// Target threat level right now. Events and antags will try to keep the round at this level.
+ /// Target threat level right now. Antags will try to keep the round at this level.
var/threat_level = 0
/// The current antag threat. Recalculated every time a ruletype starts or ends.
var/threat = 0
@@ -80,8 +76,6 @@ GLOBAL_VAR_INIT(dynamic_forced_storyteller, null)
var/list/latejoin_rules = list()
/// List of midround rules used for selecting the rules.
var/list/midround_rules = list()
- /// List of events used for reducing threat without causing antag injection (necessarily).
- var/list/events = list()
/** # Pop range per requirement.
* If the value is five the range is:
* 0-4, 5-9, 10-14, 15-19, 20-24, 25-29, 30-34, 35-39, 40-54, 45+
@@ -119,8 +113,6 @@ GLOBAL_VAR_INIT(dynamic_forced_storyteller, null)
var/latejoin_injection_cooldown = 0
/// When world.time is over this number the mode tries to inject a midround ruleset.
var/midround_injection_cooldown = 0
- /// When wor.dtime is over this number the mode tries to do an event.
- var/event_injection_cooldown = 0
/// When TRUE GetInjectionChance returns 100.
var/forced_injection = FALSE
/// Forced ruleset to be executed for the next latejoin.
@@ -184,7 +176,6 @@ GLOBAL_VAR_INIT(dynamic_forced_storyteller, null)
dat += " Injection Timers: ([storyteller.get_injection_chance(TRUE)]% chance) "
dat += "Latejoin: [(latejoin_injection_cooldown-world.time)>60*10 ? "[round((latejoin_injection_cooldown-world.time)/60/10,0.1)] minutes" : "[(latejoin_injection_cooldown-world.time)/10] seconds"] \[Now!\] "
dat += "Midround: [(midround_injection_cooldown-world.time)>60*10 ? "[round((midround_injection_cooldown-world.time)/60/10,0.1)] minutes" : "[(midround_injection_cooldown-world.time)/10] seconds"] \[Now!\] "
- dat += "Event: [(event_injection_cooldown-world.time)>60*10 ? "[round((event_injection_cooldown-world.time)/60/10,0.1)] minutes" : "[(event_injection_cooldown-world.time)/10] seconds"] \[Now!\] "
usr << browse(dat.Join(), "window=gamemode_panel;size=500x500")
/datum/game_mode/dynamic/Topic(href, href_list)
@@ -204,10 +195,7 @@ GLOBAL_VAR_INIT(dynamic_forced_storyteller, null)
var/threatadd = input("Specify how much threat to add (negative to subtract). This can inflate the threat level.", "Adjust Threat", 0) as null|num
if(!threatadd)
return
- if(threatadd > 0)
- create_threat(threatadd)
- else
- remove_threat(threatadd)
+ create_threat(threatadd)
else if (href_list["injectlate"])
latejoin_injection_cooldown = 0
forced_injection = TRUE
@@ -216,10 +204,6 @@ GLOBAL_VAR_INIT(dynamic_forced_storyteller, null)
midround_injection_cooldown = 0
forced_injection = TRUE
message_admins("[key_name(usr)] forced a midround injection.", 1)
- else if (href_list["forceevent"])
- event_injection_cooldown = 0
- // events always happen anyway
- message_admins("[key_name(usr)] forced an event.", 1)
else if (href_list["threatlog"])
show_threatlog(usr)
else if (href_list["stacking_limit"])
@@ -377,8 +361,6 @@ GLOBAL_VAR_INIT(dynamic_forced_storyteller, null)
generate_threat()
storyteller.start_injection_cooldowns()
- SSevents.frequency_lower = storyteller.event_frequency_lower // 6 minutes by default
- SSevents.frequency_upper = storyteller.event_frequency_upper // 20 minutes by default
log_game("DYNAMIC: Dynamic Mode initialized with a Threat Level of... [threat_level]!")
initial_threat_level = threat_level
return TRUE
@@ -397,9 +379,6 @@ GLOBAL_VAR_INIT(dynamic_forced_storyteller, null)
if ("Midround")
if (ruleset.weight)
midround_rules += ruleset
- if("Event")
- if(ruleset.weight)
- events += ruleset
for(var/mob/dead/new_player/player in GLOB.player_list)
if(player.ready == PLAYER_READY_TO_PLAY && player.mind)
roundstart_pop_ready++
@@ -596,8 +575,6 @@ GLOBAL_VAR_INIT(dynamic_forced_storyteller, null)
latejoin_rules = remove_from_list(latejoin_rules, rule.type)
else if(rule.ruletype == "Midround")
midround_rules = remove_from_list(midround_rules, rule.type)
- else if(rule.ruletype == "Event")
- events = remove_from_list(events,rule.type)
addtimer(CALLBACK(src, /datum/game_mode/dynamic/.proc/execute_midround_latejoin_rule, rule), rule.delay)
return TRUE
@@ -706,17 +683,6 @@ GLOBAL_VAR_INIT(dynamic_forced_storyteller, null)
picking_midround_latejoin_rule(drafted_rules)
// get_injection_chance can do things on fail
- if(event_injection_cooldown < world.time)
- SSblackbox.record_feedback("tally","dynamic",1,"Attempted event injections")
- event_injection_cooldown = storyteller.get_event_cooldown() + world.time
- message_admins("DYNAMIC: Doing event injection.")
- log_game("DYNAMIC: Doing event injection.")
- update_playercounts()
- var/list/drafted_rules = storyteller.event_draft()
- if(drafted_rules.len > 0)
- SSblackbox.record_feedback("tally","dynamic",1,"Successful event injections")
- picking_midround_latejoin_rule(drafted_rules)
-
/// Updates current_players.
/datum/game_mode/dynamic/proc/update_playercounts()
current_players[CURRENT_LIVING_PLAYERS] = list()
diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_events.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_events.dm
deleted file mode 100644
index 1ee226875b..0000000000
--- a/code/game/gamemodes/dynamic/dynamic_rulesets_events.dm
+++ /dev/null
@@ -1,454 +0,0 @@
-/datum/dynamic_ruleset/event
- ruletype = "Event"
- var/typepath // typepath of the event
- var/triggering
- var/earliest_start = 20 MINUTES
-
-/datum/dynamic_ruleset/event/get_blackbox_info()
- var/list/ruleset_data = list()
- ruleset_data["name"] = name
- ruleset_data["rule_type"] = ruletype
- ruleset_data["cost"] = total_cost
- ruleset_data["weight"] = weight
- ruleset_data["scaled_times"] = scaled_times
- ruleset_data["event_type"] = typepath
- ruleset_data["population_tier"] = indice_pop
- return ruleset_data
-
-/datum/dynamic_ruleset/event/execute()
- var/datum/round_event/E = new typepath()
- E.current_players = get_active_player_count(alive_check = 1, afk_check = 1, human_check = 1)
- // E.control = src // can't be done! we just don't use events that require these, those can be from_ghost almost always
-
- testing("[time2text(world.time, "hh:mm:ss")] [E.type]")
- deadchat_broadcast("[name] has just been triggered by dynamic!")
- log_game("Random Event triggering: [name] ([typepath])")
-
- return E
-
-/datum/dynamic_ruleset/event/ready(forced = FALSE)
- if (!forced)
- if(earliest_start >= world.time-SSticker.round_start_time)
- return FALSE
- var/job_check = 0
- if (enemy_roles.len > 0)
- for (var/mob/M in mode.current_players[CURRENT_LIVING_PLAYERS])
- if (M.stat == DEAD)
- continue // Dead players cannot count as opponents
- if (M.mind && M.mind.assigned_role && (M.mind.assigned_role in enemy_roles))
- job_check++ // Checking for "enemies" (such as sec officers). To be counters, they must either not be candidates to that rule, or have a job that restricts them from it
-
- var/threat = round(mode.threat_level/10)
- if (job_check < required_enemies[threat])
- SSblackbox.record_feedback("tally","dynamic",1,"Times rulesets rejected due to not enough enemy roles")
- return FALSE
- return TRUE
-
-//////////////////////////////////////////////
-// //
-// PIRATES //
-// //
-//////////////////////////////////////////////
-
-/datum/dynamic_ruleset/event/pirates
- name = "Space Pirates"
- config_tag = "pirates"
- typepath = /datum/round_event/pirates
- antag_flag = ROLE_TRAITOR
- enemy_roles = list("AI","Security Officer","Head of Security","Captain")
- required_enemies = list(2,2,1,1,0,0,0,0,0,0)
- weight = 5
- cost = 10
- earliest_start = 30 MINUTES
- blocking_rules = list(/datum/dynamic_ruleset/roundstart/nuclear,/datum/dynamic_ruleset/midround/from_ghosts/nuclear)
- requirements = list(70,60,50,50,40,40,40,30,20,15)
- property_weights = list("story_potential" = 1, "trust" = 1, "chaos" = 1)
- high_population_requirement = 15
-
-/datum/dynamic_ruleset/event/pirates/ready(forced = FALSE)
- if (!SSmapping.empty_space)
- return FALSE
- return ..()
-
-//////////////////////////////////////////////
-// //
-// SPIDERS //
-// //
-//////////////////////////////////////////////
-
-/datum/dynamic_ruleset/event/spiders
- name = "Spider Infestation"
- config_tag = "spiders"
- typepath = /datum/round_event/spider_infestation
- enemy_roles = list("AI","Security Officer","Head of Security","Captain")
- required_enemies = list(2,2,1,1,0,0,0,0,0,0)
- weight = 5
- cost = 10
- requirements = list(70,60,50,50,40,40,40,30,20,15)
- high_population_requirement = 15
- property_weights = list("chaos" = 1, "valid" = 1)
-
-//////////////////////////////////////////////
-// //
-// CLOGGED VENTS //
-// //
-//////////////////////////////////////////////
-
-/datum/dynamic_ruleset/event/ventclog
- name = "Clogged Vents"
- config_tag = "ventclog"
- typepath = /datum/round_event/vent_clog
- enemy_roles = list("Chemist","Medical Doctor","Chief Medical Officer")
- required_enemies = list(1,1,1,0,0,0,0,0,0,0)
- cost = 2
- weight = 4
- repeatable_weight_decrease = 3
- requirements = list(5,5,5,5,5,5,5,5,5,5) // yes, can happen on fake-extended
- high_population_requirement = 5
- repeatable = TRUE
- property_weights = list("chaos" = 1, "extended" = 2)
-
-/datum/dynamic_ruleset/event/ventclog/ready()
- if(mode.threat_level > 30 && mode.threat >= 5 && prob(20))
- name = "Clogged Vents: Threatening"
- cost = 5
- required_enemies = list(3,3,3,2,2,2,1,1,1,1)
- typepath = /datum/round_event/vent_clog/threatening
- else if(mode.threat_level > 15 && mode.threat > 15 && prob(30))
- name = "Clogged Vents: Catastrophic"
- cost = 15
- required_enemies = list(2,2,1,1,1,1,0,0,0,0)
- typepath = /datum/round_event/vent_clog/catastrophic
- else
- cost = 2
- name = "Clogged Vents: Normal"
- required_enemies = list(1,1,1,0,0,0,0,0,0,0)
- typepath = /datum/round_event/vent_clog
- return ..()
-
-//////////////////////////////////////////////
-// //
-// ION STORM //
-// //
-//////////////////////////////////////////////
-
-/datum/dynamic_ruleset/event/ion_storm
- name = "Ion Storm"
- config_tag = "ion_storm"
- typepath = /datum/round_event/ion_storm
- enemy_roles = list("Research Director","Captain","Chief Engineer")
- required_enemies = list(1,1,0,0,0,0,0,0,0,0)
- weight = 4
- // no repeatable weight decrease. too variable to be unfun multiple times in one round
- cost = 1
- requirements = list(5,5,5,5,5,5,5,5,5,5)
- high_population_requirement = 5
- repeatable = TRUE
- property_weights = list("story_potential" = 1, "extended" = 1)
- always_max_weight = TRUE
-
-//////////////////////////////////////////////
-// //
-// METEORS //
-// //
-//////////////////////////////////////////////
-
-/datum/dynamic_ruleset/event/meteor_wave
- name = "Meteor Wave"
- config_tag = "meteor_wave"
- typepath = /datum/round_event/meteor_wave
- enemy_roles = list("Chief Engineer","Station Engineer","Atmospheric Technician","Captain","Cyborg")
- required_enemies = list(3,3,3,3,3,3,3,3,3,3)
- cost = 15
- weight = 3
- earliest_start = 25 MINUTES
- repeatable_weight_decrease = 2
- requirements = list(60,50,40,30,30,30,30,30,30,30)
- high_population_requirement = 30
- property_weights = list("extended" = -2)
-
-/datum/dynamic_ruleset/event/meteor_wave/ready()
- if(world.time-SSticker.round_start_time > 35 MINUTES && mode.threat_level > 40 && mode.threat >= 25 && prob(30))
- name = "Meteor Wave: Threatening"
- cost = 25
- typepath = /datum/round_event/meteor_wave/threatening
- else if(world.time-SSticker.round_start_time > 45 MINUTES && mode.threat_level > 50 && mode.threat >= 40 && prob(30))
- name = "Meteor Wave: Catastrophic"
- cost = 40
- typepath = /datum/round_event/meteor_wave/catastrophic
- else
- name = "Meteor Wave: Normal"
- cost = 15
- typepath = /datum/round_event/meteor_wave
- return ..()
-
-//////////////////////////////////////////////
-// //
-// ANOMALIES //
-// //
-//////////////////////////////////////////////
-
-/datum/dynamic_ruleset/event/anomaly_bluespace
- name = "Anomaly: Bluespace"
- config_tag = "anomaly_bluespace"
- typepath = /datum/round_event/anomaly/anomaly_bluespace
- enemy_roles = list("Chief Engineer","Station Engineer","Atmospheric Technician","Research Director","Scientist","Captain")
- required_enemies = list(1,1,1,0,0,0,0,0,0,0)
- weight = 2
- repeatable_weight_decrease = 1
- cost = 3
- requirements = list(5,5,5,5,5,5,5,5,5,5)
- high_population_requirement = 5
- repeatable = TRUE
- property_weights = list("extended" = 1)
-
-/datum/dynamic_ruleset/event/anomaly_flux
- name = "Anomaly: Hyper-Energetic Flux"
- config_tag = "anomaly_flux"
- typepath = /datum/round_event/anomaly/anomaly_flux
- enemy_roles = list("Chief Engineer","Station Engineer","Atmospheric Technician","Research Director","Scientist","Captain")
- required_enemies = list(1,1,1,0,0,0,0,0,0,0)
- weight = 2
- repeatable_weight_decrease = 1
- cost = 5
- requirements = list(5,5,5,5,5,5,5,5,5,5)
- high_population_requirement = 10
- repeatable = TRUE
- property_weights = list("extended" = 1)
-
-/datum/dynamic_ruleset/event/anomaly_gravitational
- name = "Anomaly: Gravitational"
- config_tag = "anomaly_gravitational"
- typepath = /datum/round_event/anomaly/anomaly_grav
- weight = 2
- repeatable_weight_decrease = 1
- cost = 3
- requirements = list(5,5,5,5,5,5,5,5,5,5)
- high_population_requirement = 5
- repeatable = TRUE
- property_weights = list("extended" = 1)
-
-/datum/dynamic_ruleset/event/anomaly_pyroclastic
- name = "Anomaly: Pyroclastic"
- config_tag = "anomaly_pyroclastic"
- typepath = /datum/round_event/anomaly/anomaly_pyro
- weight = 2
- repeatable_weight_decrease = 1
- cost = 5
- enemy_roles = list("Chief Engineer","Station Engineer","Atmospheric Technician","Research Director","Scientist","Captain","Cyborg")
- required_enemies = list(1,1,1,1,1,1,1,1,1,1)
- requirements = list(10,10,10,10,10,10,10,10,10,10)
- high_population_requirement = 10
- repeatable = TRUE
- property_weights = list("extended" = 1)
-
-/datum/dynamic_ruleset/event/anomaly_vortex
- name = "Anomaly: Vortex"
- config_tag = "anomaly_vortex"
- typepath = /datum/round_event/anomaly/anomaly_vortex
- weight = 2
- repeatable_weight_decrease = 1
- cost = 5
- enemy_roles = list("Chief Engineer","Station Engineer","Atmospheric Technician","Research Director","Scientist","Captain","Cyborg")
- required_enemies = list(1,1,1,1,1,1,1,1,1,1)
- requirements = list(10,10,10,10,10,10,10,10,10,10)
- high_population_requirement = 10
- repeatable = TRUE
- property_weights = list("extended" = 1)
-
-//////////////////////////////////////////////
-// //
-// WOW THAT'S A LOT OF EVENTS //
-// //
-//////////////////////////////////////////////
-
-/datum/dynamic_ruleset/event/brand_intelligence
- name = "Brand Intelligence"
- config_tag = "brand_intelligence"
- typepath = /datum/round_event/brand_intelligence
- weight = 1
- repeatable_weight_decrease = 1
- cost = 2
- enemy_roles = list("Chief Engineer","Station Engineer","Atmospheric Technician","Research Director","Scientist","Captain","Cyborg")
- required_enemies = list(1,1,1,1,0,0,0,0,0,0)
- requirements = list(10,10,10,10,10,10,10,10,10,10)
- high_population_requirement = 10
- repeatable = TRUE
- property_weights = list("extended" = -1, "chaos" = 1)
-
-/datum/dynamic_ruleset/event/carp_migration
- name = "Carp Migration"
- config_tag = "carp_migration"
- typepath = /datum/round_event/carp_migration
- weight = 7
- repeatable_weight_decrease = 3
- cost = 4
- requirements = list(10,10,10,10,10,10,10,10,10,10)
- high_population_requirement = 10
- earliest_start = 10 MINUTES
- repeatable = TRUE
- property_weights = list("extended" = 1)
-
-/datum/dynamic_ruleset/event/communications_blackout
- name = "Communications Blackout"
- config_tag = "communications_blackout"
- typepath = /datum/round_event/communications_blackout
- cost = 4
- weight = 2
- repeatable_weight_decrease = 3
- enemy_roles = list("Chief Engineer","Station Engineer")
- required_enemies = list(1,1,1,0,0,0,0,0,0,0)
- requirements = list(5,5,5,5,5,5,5,5,5,5)
- high_population_requirement = 5
- repeatable = TRUE
- property_weights = list("extended" = 1, "chaos" = 1)
-
-/datum/dynamic_ruleset/event/processor_overload
- name = "Processor Overload"
- config_tag = "processor_overload"
- typepath = /datum/round_event/processor_overload
- cost = 4
- weight = 2
- repeatable_weight_decrease = 3
- enemy_roles = list("Chief Engineer","Station Engineer")
- required_enemies = list(1,1,1,0,0,0,0,0,0,0)
- requirements = list(5,5,5,5,5,5,5,5,5,5)
- high_population_requirement = 5
- repeatable = TRUE
- property_weights = list("extended" = 1, "chaos" = 1)
- always_max_weight = TRUE
-
-/datum/dynamic_ruleset/event/space_dust
- name = "Minor Space Dust"
- config_tag = "space_dust"
- typepath = /datum/round_event/space_dust
- cost = 2
- weight = 2
- repeatable_weight_decrease = 1
- enemy_roles = list("Chief Engineer","Station Engineer")
- required_enemies = list(1,1,1,0,0,0,0,0,0,0)
- requirements = list(5,5,5,5,5,5,5,5,5,5)
- high_population_requirement = 5
- repeatable = TRUE
- earliest_start = 0 MINUTES
- property_weights = list("extended" = 1)
- always_max_weight = TRUE
-
-/datum/dynamic_ruleset/event/major_dust
- name = "Major Space Dust"
- config_tag = "major_dust"
- typepath = /datum/round_event/meteor_wave/major_dust
- cost = 4
- weight = 2
- repeatable_weight_decrease = 1
- enemy_roles = list("Chief Engineer","Station Engineer")
- required_enemies = list(2,2,2,2,2,2,2,2,2,2)
- requirements = list(10,10,10,10,10,10,10,10,10,10)
- high_population_requirement = 10
- repeatable = TRUE
- property_weights = list("extended" = 1)
-
-/datum/dynamic_ruleset/event/electrical_storm
- name = "Electrical Storm"
- config_tag = "electrical_storm"
- typepath = /datum/round_event/electrical_storm
- cost = 1
- weight = 2
- repeatable_weight_decrease = 1
- enemy_roles = list("Chief Engineer","Station Engineer")
- required_enemies = list(1,1,1,0,0,0,0,0,0,0)
- requirements = list(5,5,5,5,5,5,5,5,5,5)
- high_population_requirement = 5
- repeatable = TRUE
- property_weights = list("extended" = 1)
-
-/datum/dynamic_ruleset/event/heart_attack
- name = "Random Heart Attack"
- config_tag = "heart_attack"
- typepath = /datum/round_event/heart_attack
- cost = 3
- weight = 2
- repeatable_weight_decrease = 1
- enemy_roles = list("Medical Doctor","Chief Medical Officer")
- required_enemies = list(2,2,2,2,2,2,2,2,2,2)
- requirements = list(101,101,101,5,5,5,5,5,5,5)
- high_population_requirement = 5
- repeatable = TRUE
- property_weights = list("extended" = 1)
- always_max_weight = TRUE
-
-/datum/dynamic_ruleset/event/radiation_storm
- name = "Radiation Storm"
- config_tag = "radiation_storm"
- typepath = /datum/round_event/radiation_storm
- cost = 3
- weight = 1
- enemy_roles = list("Chemist","Chief Medical Officer","Geneticist","Medical Doctor","AI","Captain")
- required_enemies = list(1,1,1,1,1,1,1,1,1,1)
- requirements = list(5,5,5,5,5,5,5,5,5,5)
- high_population_requirement = 5
- property_weights = list("extended" = 1,"chaos" = 1)
-
-/datum/dynamic_ruleset/event/portal_storm_syndicate
- name = "Portal Storm"
- config_tag = "portal_storm"
- typepath = /datum/round_event/portal_storm/syndicate_shocktroop
- cost = 10
- weight = 1
- enemy_roles = list("Head of Security","Security Officer","AI","Captain","Shaft Miner")
- required_enemies = list(2,2,2,2,2,2,2,2,2,2)
- requirements = list(101,101,101,30,30,30,30,30,30,30)
- high_population_requirement = 30
- earliest_start = 30 MINUTES
- property_weights = list("teamwork" = 1,"chaos" = 1, "extended" = -1)
-
-/datum/dynamic_ruleset/event/wormholes
- name = "Wormholes"
- config_tag = "wormhole"
- typepath = /datum/round_event/wormholes
- cost = 3
- weight = 4
- enemy_roles = list("AI","Medical Doctor","Station Engineer","Head of Personnel","Captain")
- required_enemies = list(2,2,2,2,2,2,2,2,2,2)
- requirements = list(5,5,5,5,5,5,5,5,5,5)
- high_population_requirement = 5
- property_weights = list("extended" = 1)
-
-/datum/dynamic_ruleset/event/swarmers
- name = "Swarmers"
- config_tag = "swarmer"
- typepath = /datum/round_event/spawn_swarmer
- cost = 10
- weight = 1
- earliest_start = 30 MINUTES
- enemy_roles = list("AI","Security Officer","Head of Security","Captain","Station Engineer","Atmos Technician","Chief Engineer")
- required_enemies = list(4,4,4,4,3,3,2,2,1,1)
- requirements = list(101,101,101,101,101,101,101,101,101,101)
- high_population_requirement = 5
- property_weights = list("extended" = -2)
-
-/datum/dynamic_ruleset/event/sentient_disease
- name = "Sentient Disease"
- config_tag = "sentient_disease"
- typepath = /datum/round_event/ghost_role/sentient_disease
- enemy_roles = list("Virologist","Chief Medical Officer","Captain","Chemist")
- required_enemies = list(2,1,1,1,0,0,0,0,0,0)
- required_candidates = 1
- weight = 4
- cost = 5
- requirements = list(30,30,20,20,15,10,10,10,10,5) // yes, it can even happen in "extended"!
- property_weights = list("story_potential" = 1, "extended" = 1, "valid" = -2)
- high_population_requirement = 5
-
-/datum/dynamic_ruleset/event/revenant
- name = "Revenant"
- config_tag = "revenant"
- typepath = /datum/round_event/ghost_role/revenant
- enemy_roles = list("Chief Engineer","Station Engineer","Captain","Chaplain","AI")
- required_enemies = list(2,1,1,1,0,0,0,0,0,0)
- required_candidates = 1
- weight = 4
- cost = 5
- requirements = list(30,30,30,30,20,15,15,15,15,15)
- high_population_requirement = 15
- property_weights = list("story_potential" = -2, "extended" = -1)
diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm
index da33da5f43..d2fa8bcc70 100644
--- a/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm
+++ b/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm
@@ -191,6 +191,26 @@
SSticker.mode_result = "loss - rev heads killed"
SSticker.news_report = REVS_LOSE
+//////////////////////////////////////////////
+// //
+// HERETIC SMUGGLER //
+// //
+//////////////////////////////////////////////
+
+/datum/dynamic_ruleset/latejoin/heretic_smuggler
+ name = "Heretic Smuggler"
+ antag_datum = /datum/antagonist/heretic
+ antag_flag = "latejoin_heretic"
+ protected_roles = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster")
+ restricted_roles = list("AI","Cyborg")
+ required_candidates = 1
+ weight = 4
+ cost = 25
+ requirements = list(60,60,60,55,50,50,50,50,50,50)
+ high_population_requirement = 50
+ property_weights = list("story_potential" = 1, "trust" = -1, "chaos" = 2, "extended" = -1, "valid" = 2)
+ repeatable = TRUE
+
//////////////////////////////////////////////
// //
// BLOODSUCKERS //
diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm
index 6a7671cd5b..b29584aa58 100644
--- a/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm
+++ b/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm
@@ -538,7 +538,7 @@
name = "Slaughter Demon"
config_tag = "slaughter_demon"
antag_flag = ROLE_ALIEN
- enemy_roles = list("Security Officer","Shaft Miner","Head of Security","Captain","Janitor","AI","Cyborg")
+ enemy_roles = list("Security Officer","Shaft Miner","Head of Security","Captain","Janitor","AI","Cyborg","Bartender")
required_enemies = list(3,2,2,2,2,1,1,1,1,0)
required_candidates = 1
weight = 4
diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm
index edaadeae1c..db4ec99558 100644
--- a/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm
+++ b/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm
@@ -143,6 +143,48 @@
changeling.add_antag_datum(new_antag)
return TRUE
+//////////////////////////////////////////////
+// //
+// ELDRITCH CULT //
+// //
+//////////////////////////////////////////////
+
+/datum/dynamic_ruleset/roundstart/heretics
+ name = "Heretics"
+ antag_flag = "heretic"
+ antag_datum = /datum/antagonist/heretic
+ protected_roles = list("Prisoner","Security Officer", "Warden", "Detective", "Head of Security", "Captain")
+ restricted_roles = list("AI", "Cyborg")
+ required_candidates = 1
+ weight = 3
+ cost = 25
+ scaling_cost = 15
+ requirements = list(60,60,60,55,50,50,50,50,50,50)
+ property_weights = list("story_potential" = 1, "trust" = -1, "chaos" = 2, "extended" = -1, "valid" = 2)
+ antag_cap = list(1,1,1,1,2,2,2,2,3,3)
+ high_population_requirement = 50
+
+
+/datum/dynamic_ruleset/roundstart/heretics/pre_execute()
+ . = ..()
+ var/num_ecult = antag_cap[indice_pop] * (scaled_times + 1)
+
+ for (var/i = 1 to num_ecult)
+ var/mob/picked_candidate = pick_n_take(candidates)
+ assigned += picked_candidate.mind
+ picked_candidate.mind.restricted_roles = restricted_roles
+ picked_candidate.mind.special_role = ROLE_HERETIC
+ return TRUE
+
+/datum/dynamic_ruleset/roundstart/heretics/execute()
+
+ for(var/c in assigned)
+ var/datum/mind/cultie = c
+ var/datum/antagonist/heretic/new_antag = new antag_datum()
+ cultie.add_antag_datum(new_antag)
+
+ return TRUE
+
//////////////////////////////////////////////
// //
// WIZARDS //
diff --git a/code/game/gamemodes/dynamic/dynamic_storytellers.dm b/code/game/gamemodes/dynamic/dynamic_storytellers.dm
index 561b38fde1..ed6cb0ba88 100644
--- a/code/game/gamemodes/dynamic/dynamic_storytellers.dm
+++ b/code/game/gamemodes/dynamic/dynamic_storytellers.dm
@@ -22,14 +22,14 @@
var/datum/game_mode/dynamic/mode = null // Cached as soon as it's made, by dynamic.
/**
-Property weights are:
+Property weights are added to the config weight of the ruleset. They are:
"story_potential" -- essentially how many different ways the antag can be played.
"trust" -- How much it makes the crew trust each other. Negative values means they're suspicious. Team antags are like this.
"chaos" -- How chaotic it makes the round. Has some overlap with "valid" and somewhat contradicts "extended".
"valid" -- How likely the non-antag-enemy crew are to get involved, e.g. nukies encouraging the warden to
let everyone into the armory, wizard moving around and being a nuisance, nightmare busting lights.
"extended" -- How much the antag is conducive to a long round. Nukies and cults are bad for this; Wizard is less bad; and so on.
-"conversion" -- Basically a bool. Conversion antags, well, convert. It's its own class for a good reason.
+"conversion" -- Basically a bool. Conversion antags, well, convert. It's in its own class 'cause people kinda hate conversion.
*/
/datum/dynamic_storyteller/proc/start_injection_cooldowns()
@@ -39,9 +39,6 @@ Property weights are:
var/midround_injection_cooldown_middle = 0.5*(GLOB.dynamic_first_midround_delay_min + GLOB.dynamic_first_midround_delay_max)
mode.midround_injection_cooldown = round(clamp(EXP_DISTRIBUTION(midround_injection_cooldown_middle), GLOB.dynamic_first_midround_delay_min, GLOB.dynamic_first_midround_delay_max)) + world.time
- var/event_injection_cooldown_middle = 0.5*(GLOB.dynamic_event_delay_max + GLOB.dynamic_event_delay_min)
- mode.event_injection_cooldown = (round(clamp(EXP_DISTRIBUTION(event_injection_cooldown_middle), GLOB.dynamic_event_delay_min, GLOB.dynamic_event_delay_max)) + world.time)
-
/datum/dynamic_storyteller/proc/calculate_threat()
var/threat = 0
for(var/datum/antagonist/A in GLOB.antagonists)
@@ -50,10 +47,6 @@ Property weights are:
for(var/r in SSevents.running)
var/datum/round_event/R = r
threat += R.threat()
- for(var/mob/living/simple_animal/hostile/H in GLOB.mob_living_list)
- var/turf/T = get_turf(H)
- if(H.stat != DEAD && is_station_level(T.z) && !("Station" in H.faction))
- threat += H.threat()
for(var/obj/item/phylactery/P in GLOB.poi_list)
threat += 25 // can't be giving them too much of a break
for (var/mob/M in mode.current_players[CURRENT_LIVING_PLAYERS])
@@ -91,6 +84,8 @@ Property weights are:
mean += 2.5
if(CHAOS_MAX)
mean += 5
+ else
+ voters += 0.5
if(voters)
GLOB.dynamic_curve_centre += (mean/voters)
if(flags & USE_PREV_ROUND_WEIGHTS)
@@ -101,10 +96,6 @@ Property weights are:
var/midround_injection_cooldown_middle = 0.5*(GLOB.dynamic_midround_delay_max + GLOB.dynamic_midround_delay_min)
return round(clamp(EXP_DISTRIBUTION(midround_injection_cooldown_middle), GLOB.dynamic_midround_delay_min, GLOB.dynamic_midround_delay_max))
-/datum/dynamic_storyteller/proc/get_event_cooldown()
- var/event_injection_cooldown_middle = 0.5*(GLOB.dynamic_event_delay_max + GLOB.dynamic_event_delay_min)
- return round(clamp(EXP_DISTRIBUTION(event_injection_cooldown_middle), GLOB.dynamic_event_delay_min, GLOB.dynamic_event_delay_max))
-
/datum/dynamic_storyteller/proc/get_latejoin_cooldown()
var/latejoin_injection_cooldown_middle = 0.5*(GLOB.dynamic_latejoin_delay_max + GLOB.dynamic_latejoin_delay_min)
return round(clamp(EXP_DISTRIBUTION(latejoin_injection_cooldown_middle), GLOB.dynamic_latejoin_delay_min, GLOB.dynamic_latejoin_delay_max))
@@ -128,7 +119,9 @@ Property weights are:
for(var/property in property_weights)
if(property in rule.property_weights) // just treat it as 0 if it's not in there
property_weight += rule.property_weights[property] * property_weights[property]
- drafted_rules[rule] = (rule.get_weight() * property_weight)*rule.weight_mult
+ var/calced_weight = (rule.get_weight() + property_weight) * rule.weight_mult
+ if(calced_weight > 0) // negatives in the list might cause problems
+ drafted_rules[rule] = calced_weight
return drafted_rules
/datum/dynamic_storyteller/proc/midround_draft()
@@ -140,25 +133,29 @@ Property weights are:
if (GLOB.dynamic_classic_secret && !((rule.flags & TRAITOR_RULESET) || (rule.flags & MINOR_RULESET)))
continue
rule.trim_candidates()
- var/threat_weight = 1
- if(!(rule.flags & MINOR_RULESET)) // makes the traitor rulesets always possible anyway
- var/cost_difference = abs(rule.cost-(mode.threat_level-mode.threat))
- /* Basically, the closer the cost is to the current threat-level-away-from-threat, the more likely it is to
- pick this particular ruleset.
- Let's use a toy example: there's 60 threat level and 10 threat spent.
- We want to pick a ruleset that's close to that, so we run the below equation, on two rulesets.
- Ruleset 1 has 30 cost, ruleset 2 has 5 cost.
- When we do the math, ruleset 1's threat_weight is 0.538, and ruleset 2's is 0.238, meaning ruleset 1
- is 2.26 times as likely to be picked, all other things considered.
- Of course, we don't want it to GUARANTEE the closest, that's no fun, so it's just a weight.
- */
- threat_weight = abs(1-abs(1-LOGISTIC_FUNCTION(2,0.05,cost_difference,0)))
if (rule.ready())
var/property_weight = 0
for(var/property in property_weights)
- if(property in rule.property_weights)
+ if(property in rule.property_weights) // just treat it as 0 if it's not in there
property_weight += rule.property_weights[property] * property_weights[property]
- drafted_rules[rule] = round(((rule.get_weight() * property_weight)*rule.weight_mult*threat_weight)*1000,1)
+ var/threat_weight = 1
+ if(!(rule.flags & TRAITOR_RULESET) || (rule.flags & MINOR_RULESET)) // makes the traitor rulesets always possible anyway
+ var/cost_difference = rule.cost-(mode.threat_level-mode.threat)
+ /* Basically, the closer the cost is to the current threat-level-away-from-threat, the more likely it is to
+ pick this particular ruleset.
+ Let's use a toy example: there's 60 threat level and 10 threat spent.
+ We want to pick a ruleset that's close to that, so we run the below equation, on two rulesets.
+ Ruleset 1 has 30 cost, ruleset 2 has 5 cost.
+ When we do the math, ruleset 1's threat_weight is 0.538, and ruleset 2's is 0.238, meaning ruleset 1
+ is 2.26 times as likely to be picked, all other things considered.
+ Of course, we don't want it to GUARANTEE the closest, that's no fun, so it's just a weight.
+ */
+ threat_weight = abs(1-abs(1-LOGISTIC_FUNCTION(2,0.05,abs(cost_difference),0)))
+ if(cost_difference > 0)
+ threat_weight /= (1+(cost_difference*0.1))
+ var/calced_weight = (rule.get_weight() + property_weight) * rule.weight_mult * threat_weight
+ if(calced_weight > 0)
+ drafted_rules[rule] = calced_weight
return drafted_rules
/datum/dynamic_storyteller/proc/latejoin_draft(mob/living/carbon/human/newPlayer)
@@ -175,31 +172,22 @@ Property weights are:
rule.candidates = list(newPlayer)
rule.trim_candidates()
- var/threat_weight = 1
- if(!(rule.flags & MINOR_RULESET))
- var/cost_difference = abs(rule.cost-(mode.threat_level-mode.threat))
- threat_weight = 1-abs(1-(LOGISTIC_FUNCTION(2,0.05,cost_difference,0)))
if (rule.ready())
var/property_weight = 0
for(var/property in property_weights)
if(property in rule.property_weights)
property_weight += rule.property_weights[property] * property_weights[property]
- drafted_rules[rule] = round(((rule.get_weight() * property_weight)*rule.weight_mult*threat_weight)*1000,1)
+ var/threat_weight = 1
+ if(!(rule.flags & TRAITOR_RULESET) || (rule.flags & MINOR_RULESET))
+ var/cost_difference = rule.cost-(mode.threat_level-mode.threat)
+ threat_weight = 1-abs(1-(LOGISTIC_FUNCTION(2,0.05,abs(cost_difference),0)))
+ if(cost_difference > 0)
+ threat_weight /= (1+(cost_difference*0.1))
+ var/calced_weight = (rule.get_weight() + property_weight) * rule.weight_mult * threat_weight
+ if(calced_weight > 0)
+ drafted_rules[rule] = calced_weight
return drafted_rules
-/datum/dynamic_storyteller/proc/event_draft()
- var/list/drafted_rules = list()
- for(var/datum/dynamic_ruleset/event/rule in mode.events)
- if(rule.acceptable(mode.current_players[CURRENT_LIVING_PLAYERS].len, mode.threat_level) && (mode.threat_level - mode.threat) >= rule.cost)
- if(rule.ready())
- var/property_weight = 0
- for(var/property in property_weights)
- if(property in rule.property_weights)
- property_weight += rule.property_weights[property] * property_weights[property]
- drafted_rules[rule] = (rule.get_weight() + property_weight)*rule.weight_mult
- return drafted_rules
-
-
/datum/dynamic_storyteller/chaotic
name = "Chaotic"
config_tag = "chaotic"
@@ -262,9 +250,6 @@ Property weights are:
/datum/dynamic_storyteller/random/get_midround_cooldown()
return rand(GLOB.dynamic_midround_delay_min/2, GLOB.dynamic_midround_delay_max*2)
-/datum/dynamic_storyteller/random/get_event_cooldown()
- return rand(GLOB.dynamic_event_delay_min/2, GLOB.dynamic_event_delay_max*2)
-
/datum/dynamic_storyteller/random/get_latejoin_cooldown()
return rand(GLOB.dynamic_latejoin_delay_min/2, GLOB.dynamic_latejoin_delay_max*2)
@@ -310,14 +295,6 @@ Property weights are:
drafted_rules[rule] = 1
return drafted_rules
-/datum/dynamic_storyteller/random/event_draft()
- var/list/drafted_rules = list()
- for(var/datum/dynamic_ruleset/event/rule in mode.events)
- if(rule.acceptable(mode.current_players[CURRENT_LIVING_PLAYERS].len, mode.threat_level))
- if(rule.ready())
- drafted_rules[rule] = 1
- return drafted_rules
-
/datum/dynamic_storyteller/story
name = "Story"
config_tag = "story"
@@ -327,12 +304,6 @@ Property weights are:
flags = USE_PREV_ROUND_WEIGHTS
property_weights = list("story_potential" = 2)
-
-/datum/dynamic_storyteller/story/calculate_threat()
- var/current_time = (world.time / SSautotransfer.targettime)*180
- mode.threat_level = round(mode.initial_threat_level*(sin(current_time)+0.25),0.1)
- return ..()
-
/datum/dynamic_storyteller/classic
name = "Classic"
config_tag = "classic"
@@ -363,7 +334,7 @@ Property weights are:
/datum/dynamic_storyteller/no_antag
name = "Extended"
config_tag = "semiextended"
- desc = "No standard antags. Threatening events may still spawn."
+ desc = "No standard antags."
curve_centre = -5
curve_width = 0.5
flags = NO_ASSASSIN | FORCE_IF_WON
@@ -375,15 +346,3 @@ Property weights are:
/datum/dynamic_storyteller/no_antag/get_injection_chance(dry_run)
return 0
-
-/datum/dynamic_storyteller/extended
- name = "Super Extended"
- config_tag = "extended"
- desc = "No antags. No dangerous events."
- curve_centre = -20
- weight = 0
- curve_width = 0.5
-
-/datum/dynamic_storyteller/extended/on_start()
- ..()
- GLOB.dynamic_forced_extended = TRUE
diff --git a/code/game/gamemodes/eldritch_cult/eldritch_cult.dm b/code/game/gamemodes/eldritch_cult/eldritch_cult.dm
new file mode 100644
index 0000000000..20a129a5a3
--- /dev/null
+++ b/code/game/gamemodes/eldritch_cult/eldritch_cult.dm
@@ -0,0 +1,67 @@
+/datum/game_mode/heretics
+ name = "heresy"
+ config_tag = "heresy"
+ antag_flag = ROLE_HERETIC
+ false_report_weight = 5
+ restricted_jobs = list("AI", "Cyborg")
+ protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster") //citadel change - adds HoP, CE, CMO, and RD to heretic role blacklist
+ required_players = 15
+ required_enemies = 1
+ recommended_enemies = 4
+ reroll_friendly = 1
+ enemy_minimum_age = 0
+ round_ends_with_antag_death = 0
+
+ announce_span = "danger"
+ announce_text = "Heretics have been spotted on the station!\n\
+ Heretics: Accomplish your objectives.\n\
+ Crew: Do not let the madman succeed!"
+
+ var/ecult_possible = 4 //hard limit on culties if scaling is turned off
+ var/num_ecult = 1
+ var/list/culties = list()
+
+/datum/game_mode/heretics/pre_setup()
+
+ if(CONFIG_GET(flag/protect_roles_from_antagonist))
+ restricted_jobs += protected_jobs
+
+ if(CONFIG_GET(flag/protect_assistant_from_antagonist))
+ restricted_jobs += "Assistant"
+
+
+ var/esc = CONFIG_GET(number/ecult_scaling_coeff)
+ if(esc)
+ num_ecult = min(max(1, min(round(num_players() / (esc * 2)) + 2, round(num_players() / esc))),4)
+ else
+ num_ecult = max(1, min(num_players(), ecult_possible))
+
+ for(var/i in 1 to num_ecult)
+ if(!antag_candidates.len)
+ break
+ var/datum/mind/cultie = antag_pick(antag_candidates)
+ antag_candidates -= cultie
+ cultie.special_role = ROLE_HERETIC
+ cultie.restricted_roles = restricted_jobs
+ culties += cultie
+
+ var/enough_heretics = culties.len > 0
+
+ if(!enough_heretics)
+ setup_error = "Not enough heretic candidates"
+ return FALSE
+ else
+ for(var/antag in culties)
+ return TRUE
+
+/datum/game_mode/heretics/post_setup()
+ for(var/c in culties)
+ var/datum/mind/cultie = c
+ log_game("[key_name(cultie)] has been selected as a heretic!")
+ var/datum/antagonist/heretic/new_antag = new()
+ cultie.add_antag_datum(new_antag)
+ return ..()
+
+/datum/game_mode/heretics/generate_report()
+ return "Cybersun Industries has announced that they have successfully raided a high-security library. The library contained a very dangerous book that was \
+ shown to posses anomalous properties. We suspect that the book has been copied over, Stay vigilant!"
diff --git a/code/game/gamemodes/meteor/meteors.dm b/code/game/gamemodes/meteor/meteors.dm
index cfb4c9a120..795508320b 100644
--- a/code/game/gamemodes/meteor/meteors.dm
+++ b/code/game/gamemodes/meteor/meteors.dm
@@ -112,7 +112,7 @@ GLOBAL_LIST_INIT(meteorsC, list(/obj/effect/meteor/dust)) //for space dust event
var/turf/T = get_turf(loc)
ram_turf(T)
- if(prob(10) && !isspaceturf(T))//randomly takes a 'hit' from ramming
+ if(prob(10) && !isspaceturf(T) && !istype(T, /turf/closed/mineral) && !istype(T, /turf/open/floor/plating/asteroid))//randomly takes a 'hit' from ramming
get_hit()
/obj/effect/meteor/Destroy()
@@ -136,7 +136,8 @@ GLOBAL_LIST_INIT(meteorsC, list(/obj/effect/meteor/dust)) //for space dust event
if(A)
ram_turf(get_turf(A))
playsound(src.loc, meteorsound, 40, 1)
- get_hit()
+ if(!istype(A, /turf/closed/mineral) && !istype(A, /turf/open/floor/plating/asteroid))
+ get_hit()
/obj/effect/meteor/proc/ram_turf(turf/T)
//first bust whatever is in the turf
diff --git a/code/game/machinery/Sleeper.dm b/code/game/machinery/Sleeper.dm
index 55976d69c2..047e6c955f 100644
--- a/code/game/machinery/Sleeper.dm
+++ b/code/game/machinery/Sleeper.dm
@@ -12,9 +12,9 @@
density = FALSE
state_open = TRUE
circuit = /obj/item/circuitboard/machine/sleeper
- req_access = list(ACCESS_CMO) //Used for reagent deletion and addition of non medicines
+
var/efficiency = 1
- var/min_health = 30
+ var/min_health = -25
var/list/available_chems
var/controls_inside = FALSE
var/list/possible_chems = list(
@@ -31,32 +31,13 @@
/obj/machinery/sleeper/Initialize()
. = ..()
- create_reagents(500, NO_REACT)
+ // if(mapload)
+ // component_parts -= circuit
+ // QDEL_NULL(circuit)
occupant_typecache = GLOB.typecache_living
update_icon()
reset_chem_buttons()
RefreshParts()
- add_inital_chems()
- new_occupant_dir = dir
-
-/obj/machinery/sleeper/setDir(newdir)
- . = ..()
- new_occupant_dir = dir
-
-/obj/machinery/sleeper/on_deconstruction()
- var/obj/item/reagent_containers/sleeper_buffer/buffer = new (loc)
- buffer.volume = reagents.maximum_volume
- buffer.reagents.maximum_volume = reagents.maximum_volume
- reagents.trans_to(buffer.reagents, reagents.total_volume)
-
-/obj/machinery/sleeper/proc/add_inital_chems()
- for(var/i in available_chems)
- var/datum/reagent/R = reagents.has_reagent(i)
- if(!R)
- reagents.add_reagent(i, (20))
- continue
- if(R.volume < 20)
- reagents.add_reagent(i, (20 - R.volume))
/obj/machinery/sleeper/RefreshParts()
var/E
@@ -73,15 +54,11 @@
available_chems |= possible_chems[i]
reset_chem_buttons()
- //Total container size 500 - 2000u
- if(reagents)
- reagents.maximum_volume = (500*E)
-
-
/obj/machinery/sleeper/update_icon_state()
- icon_state = initial(icon_state)
if(state_open)
- icon_state += "-open"
+ icon_state = "[initial(icon_state)]-open"
+ else
+ icon_state = initial(icon_state)
/obj/machinery/sleeper/container_resist(mob/living/user)
visible_message("[occupant] emerges from [src]!",
@@ -98,10 +75,12 @@
/obj/machinery/sleeper/open_machine()
if(!state_open && !panel_open)
+ // flick("[initial(icon_state)]-anim", src)
..()
/obj/machinery/sleeper/close_machine(mob/user)
if((isnull(user) || istype(user)) && state_open && !panel_open)
+ // flick("[initial(icon_state)]-anim", src)
..(user)
var/mob/living/mob_occupant = occupant
if(mob_occupant && mob_occupant.stat != DEAD)
@@ -112,46 +91,23 @@
if (. & EMP_PROTECT_SELF)
return
if(is_operational() && occupant)
- var/datum/reagent/R = pick(reagents.reagent_list)
+ var/datum/reagent/R = pick(reagents.reagent_list) //cit specific
inject_chem(R.type, occupant)
open_machine()
- //Is this too much?
+ //Is this too much? Cit specific
if(severity == EMP_HEAVY)
var/chem = pick(available_chems)
available_chems -= chem
available_chems += get_random_reagent_id()
reset_chem_buttons()
-/obj/machinery/sleeper/attackby(obj/item/I, mob/user, params)
- if(istype(I, /obj/item/reagent_containers/sleeper_buffer))
- var/obj/item/reagent_containers/sleeper_buffer/SB = I
- if((SB.reagents.total_volume + reagents.total_volume) < reagents.maximum_volume)
- SB.reagents.trans_to(reagents, SB.reagents.total_volume)
- visible_message("[user] places the [SB] into the [src].")
- qdel(SB)
- return
- else
- SB.reagents.trans_to(reagents, SB.reagents.total_volume)
- visible_message("[user] adds as much as they can to the [src] from the [SB].")
- return
- if(istype(I, /obj/item/reagent_containers))
- var/obj/item/reagent_containers/RC = I
- if(RC.reagents.total_volume == 0)
- to_chat(user, "The [I] is empty!")
- for(var/datum/reagent/R in RC.reagents.reagent_list)
- if((obj_flags & EMAGGED) || (allowed(usr)))
- break
- if(!istype(R, /datum/reagent/medicine))
- visible_message("The [src] gives out a hearty boop and rejects the [I]. The Sleeper's screen flashes with a pompous \"Medicines only, please.\"")
- return
- RC.reagents.trans_to(reagents, 1000)
- visible_message("[user] adds as much as they can to the [src] from the [I].")
- return
-
-
/obj/machinery/sleeper/MouseDrop_T(mob/target, mob/user)
- if(user.stat || user.lying || !Adjacent(user) || !user.Adjacent(target) || !iscarbon(target) || !user.IsAdvancedToolUser())
+ if(user.stat || !Adjacent(user) || !user.Adjacent(target) || !iscarbon(target) || !user.IsAdvancedToolUser())
return
+ if(isliving(user))
+ var/mob/living/L = user
+ if(!(L.mobility_flags & MOBILITY_STAND))
+ return
close_machine(target)
/obj/machinery/sleeper/screwdriver_act(mob/living/user, obj/item/I)
@@ -187,31 +143,29 @@
visible_message("[usr] pries open [src].", "You pry open [src].")
open_machine()
+/obj/machinery/sleeper/ui_state(mob/user)
+ if(controls_inside)
+ return GLOB.contained_state
+ return GLOB.default_state
+
+/obj/machinery/sleeper/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "Sleeper", name)
+ ui.open()
+
/obj/machinery/sleeper/AltClick(mob/user)
- . = ..()
- if(!user.canUseTopic(src, !hasSiliconAccessInArea(user)))
+ if(!user.canUseTopic(src, !issilicon(user)))
return
if(state_open)
close_machine()
else
open_machine()
- return TRUE
/obj/machinery/sleeper/examine(mob/user)
. = ..()
. += "Alt-click [src] to [state_open ? "close" : "open"] it."
-/obj/machinery/sleeper/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \
- datum/tgui/master_ui = null, datum/ui_state/state = GLOB.notcontained_state)
-
- if(controls_inside && state == GLOB.notcontained_state)
- state = GLOB.default_state // If it has a set of controls on the inside, make it actually controllable by the mob in it.
-
- ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
- if(!ui)
- ui = new(user, src, ui_key, "Sleeper", name, 550, 700, master_ui, state)
- ui.open()
-
/obj/machinery/sleeper/process()
..()
check_nap_violations()
@@ -221,22 +175,13 @@
/obj/machinery/sleeper/ui_data()
var/list/data = list()
- var/chemical_list = list()
- var/blood_percent = 0
-
data["occupied"] = occupant ? 1 : 0
data["open"] = state_open
- data["blood_levels"] = blood_percent
- data["blood_status"] = "Patient either has no blood, or does not require it to function."
- data["chemical_list"] = chemical_list
data["chems"] = list()
for(var/chem in available_chems)
- var/datum/reagent/R = reagents.has_reagent(chem)
- R = GLOB.chemical_reagents_list[chem]
- data["synthchems"] += list(list("name" = R.name, "id" = R.type, "synth_allowed" = synth_allowed(chem)))
- for(var/datum/reagent/R in reagents.reagent_list)
- data["chems"] += list(list("name" = R.name, "id" = R.type, "vol" = R.volume, "purity" = R.purity, "allowed" = chem_allowed(R.type)))
+ var/datum/reagent/R = GLOB.chemical_reagents_list[chem]
+ data["chems"] += list(list("name" = R.name, "id" = R.type, "allowed" = chem_allowed(chem)))
data["occupant"] = list()
var/mob/living/mob_occupant = occupant
@@ -264,40 +209,10 @@
data["occupant"]["fireLoss"] = mob_occupant.getFireLoss()
data["occupant"]["cloneLoss"] = mob_occupant.getCloneLoss()
data["occupant"]["brainLoss"] = mob_occupant.getOrganLoss(ORGAN_SLOT_BRAIN)
-
- if(mob_occupant.reagents.reagent_list.len)
+ data["occupant"]["reagents"] = list()
+ if(mob_occupant.reagents && mob_occupant.reagents.reagent_list.len)
for(var/datum/reagent/R in mob_occupant.reagents.reagent_list)
- chemical_list += list(list("name" = R.name, "volume" = R.volume))
- else
- chemical_list = "Patient has no reagents."
-
- data["occupant"]["failing_organs"] = list()
- var/mob/living/carbon/C = mob_occupant
- if(C)
- for(var/obj/item/organ/Or in C.getFailingOrgans())
- if(istype(Or, /obj/item/organ/brain))
- continue
- data["occupant"]["failing_organs"] += list(list("name" = Or.name))
-
- if(istype(C)) //Non-carbons shouldn't be able to enter sleepers, but this is to prevent runtimes if something ever breaks
- if(mob_occupant.has_dna()) // Blood-stuff is mostly a copy-paste from the healthscanner.
- blood_percent = round((C.blood_volume / BLOOD_VOLUME_NORMAL)*100)
- var/blood_id = C.get_blood_id()
- var/blood_warning = ""
- if(blood_percent < 80)
- blood_warning = "Patient has low blood levels."
- if(blood_percent < 60)
- blood_warning = "Patient has DANGEROUSLY low blood levels."
- if(blood_id)
- var/blood_type = C.dna.blood_type
- if(!(blood_id in GLOB.blood_reagent_types)) // special blood substance
- var/datum/reagent/R = GLOB.chemical_reagents_list[blood_id]
- if(R)
- blood_type = R.name
- else
- blood_type = blood_id
- data["blood_status"] = "Patient has [blood_type] type blood. [blood_warning]"
- data["blood_levels"] = blood_percent
+ data["occupant"]["reagents"] += list(list("name" = R.name, "volume" = R.volume))
return data
/obj/machinery/sleeper/ui_act(action, params)
@@ -314,37 +229,14 @@
. = TRUE
if("inject")
var/chem = text2path(params["chem"])
- var/amount = text2num(params["volume"])
if(!is_operational() || !mob_occupant || isnull(chem))
return
if(mob_occupant.health < min_health && chem != /datum/reagent/medicine/epinephrine)
return
- if(inject_chem(chem, usr, amount))
+ if(inject_chem(chem, usr))
. = TRUE
if(scrambled_chems && prob(5))
to_chat(usr, "Chemical system re-route detected, results may not be as expected!")
- if("synth")
- var/chem = text2path(params["chem"])
- if(!is_operational())
- return
- reagents.add_reagent(chem_buttons[chem], 10) //other_purity = 0.75 for when the mechanics are in
- if("purge")
- var/chem = text2path(params["chem"])
- if(allowed(usr))
- if(!is_operational())
- return
- reagents.remove_reagent(chem, 1000)
- return
- if(chem in available_chems)
- if(!is_operational())
- return
- /*var/datum/reagent/R = reagents.has_reagent(chem) //For when purity effects are in
- if(R.purity < 0.8)*/
- reagents.remove_reagent(chem, 1000)
- else
- visible_message("Access Denied.")
- playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0)
-
/obj/machinery/sleeper/emag_act(mob/user)
. = ..()
@@ -353,10 +245,9 @@
to_chat(user, "You scramble the sleeper's user interface!")
return TRUE
-//trans to
-/obj/machinery/sleeper/proc/inject_chem(chem, mob/user, volume = 10)
- if(chem_allowed(chem))
- reagents.trans_id_to(occupant, chem, volume)//emag effect kicks in here so that the "intended" chem is used for all checks, for extra FUUU
+/obj/machinery/sleeper/proc/inject_chem(chem, mob/user)
+ if((chem in available_chems) && chem_allowed(chem))
+ occupant.reagents.add_reagent(chem_buttons[chem], 10) //emag effect kicks in here so that the "intended" chem is used for all checks, for extra FUUU
if(user)
log_combat(user, occupant, "injected [chem] into", addition = "via [src]")
return TRUE
@@ -369,14 +260,6 @@
var/occ_health = mob_occupant.health > min_health || chem == /datum/reagent/medicine/epinephrine
return amount && occ_health
-/obj/machinery/sleeper/proc/synth_allowed(chem)
- var/datum/reagent/R = reagents.has_reagent(chem)
- if(!R)
- return TRUE
- if(R.volume < 50)
- return TRUE
- return FALSE
-
/obj/machinery/sleeper/proc/reset_chem_buttons()
scrambled_chems = FALSE
LAZYINITLIST(chem_buttons)
@@ -416,23 +299,6 @@
component_parts += new /obj/item/stack/cable_coil(null)
RefreshParts()
-/obj/machinery/sleeper/clockwork
- name = "soothing sleeper"
- desc = "A large cryogenics unit built from brass. Its surface is pleasantly cool the touch."
- icon_state = "sleeper_clockwork"
- enter_message = "You hear the gentle hum and click of machinery, and are lulled into a sense of peace."
- possible_chems = list(list(/datum/reagent/medicine/epinephrine, /datum/reagent/medicine/salbutamol, /datum/reagent/medicine/bicaridine,
- /datum/reagent/medicine/kelotane, /datum/reagent/medicine/oculine, /datum/reagent/medicine/inacusiate, /datum/reagent/medicine/mannitol))
-
-/obj/machinery/sleeper/clockwork/process()
- if(occupant && isliving(occupant))
- var/mob/living/L = occupant
- if(GLOB.clockwork_vitality) //If there's Vitality, the sleeper has passive healing
- GLOB.clockwork_vitality = max(0, GLOB.clockwork_vitality - 1)
- L.adjustBruteLoss(-1)
- L.adjustFireLoss(-1)
- L.adjustOxyLoss(-5)
-
/obj/machinery/sleeper/old
icon_state = "oldpod"
@@ -443,8 +309,6 @@
idle_power_usage = 3000
circuit = /obj/item/circuitboard/machine/sleeper/party
var/leddit = FALSE //Get it like reddit and lead alright fine
- ui_x = 310
- ui_y = 400
controls_inside = TRUE
possible_chems = list(
@@ -477,3 +341,23 @@
/obj/machinery/sleeper/party/emag_act(mob/user)
..()
leddit = TRUE
+
+/obj/machinery/sleeper/clockwork
+ name = "soothing sleeper"
+ desc = "A large cryogenics unit built from brass. Its surface is pleasantly cool the touch."
+ icon_state = "sleeper_clockwork"
+ enter_message = "You hear the gentle hum and click of machinery, and are lulled into a sense of peace."
+ possible_chems = list(
+ list(/datum/reagent/medicine/epinephrine, /datum/reagent/medicine/salbutamol, /datum/reagent/medicine/bicaridine, /datum/reagent/medicine/kelotane, /datum/reagent/medicine/oculine, /datum/reagent/medicine/inacusiate, /datum/reagent/medicine/mannitol)
+ ) //everything is available at start
+ fair_market_price = 0 //it's free
+
+/obj/machinery/sleeper/clockwork/process()
+ ..()
+ if(occupant && isliving(occupant))
+ var/mob/living/L = occupant
+ if(GLOB.clockwork_vitality) //If there's Vitality, the sleeper has passive healing
+ GLOB.clockwork_vitality = max(0, GLOB.clockwork_vitality - 1)
+ L.adjustBruteLoss(-1)
+ L.adjustFireLoss(-1)
+ L.adjustOxyLoss(-5)
diff --git a/code/game/machinery/_machinery.dm b/code/game/machinery/_machinery.dm
index 3faf6d5727..b82d932e6d 100644
--- a/code/game/machinery/_machinery.dm
+++ b/code/game/machinery/_machinery.dm
@@ -92,6 +92,7 @@ Class Procs:
pressure_resistance = 15
max_integrity = 200
layer = BELOW_OBJ_LAYER //keeps shit coming out of the machine from ending up underneath it.
+ flags_1 = DEFAULT_RICOCHET_1
flags_ricochet = RICOCHET_HARD
ricochet_chance_mod = 0.3
@@ -116,6 +117,7 @@ Class Procs:
var/new_occupant_dir = SOUTH //The direction the occupant will be set to look at when entering the machine.
var/speed_process = FALSE // Process as fast as possible?
var/obj/item/circuitboard/circuit // Circuit to be created and inserted when the machinery is created
+ var/wire_compatible = FALSE
// For storing and overriding ui id and dimensions
var/tgui_id // ID of TGUI interface
var/ui_style // ID of custom TGUI style (optional)
@@ -432,6 +434,7 @@ Class Procs:
to_chat(user, "You [anchored ? "un" : ""]secure [src].")
setAnchored(!anchored)
playsound(src, 'sound/items/deconstruct.ogg', 50, 1)
+ SEND_SIGNAL(src, COMSIG_OBJ_DEFAULT_UNFASTEN_WRENCH, anchored)
return SUCCESSFUL_UNFASTEN
return FAILED_UNFASTEN
return CANT_UNFASTEN
@@ -546,3 +549,6 @@ Class Procs:
. = . % 9
AM.pixel_x = -8 + ((.%3)*8)
AM.pixel_y = -8 + (round( . / 3)*8)
+
+/obj/machinery/rust_heretic_act()
+ take_damage(500, BRUTE, "melee", 1)
diff --git a/code/game/machinery/announcement_system.dm b/code/game/machinery/announcement_system.dm
index 8db5ca1d17..b004746051 100644
--- a/code/game/machinery/announcement_system.dm
+++ b/code/game/machinery/announcement_system.dm
@@ -93,13 +93,10 @@ GLOBAL_LIST_EMPTY(announcement_systems)
for(var/channel in channels)
radio.talk_into(src, message, channel)
-//config stuff
-
-/obj/machinery/announcement_system/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state)
- . = ..()
- ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+/obj/machinery/announcement_system/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
- ui = new(user, src, ui_key, "AutomatedAnnouncement", "Automated Announcement System", 500, 225, master_ui, state)
+ ui = new(user, src, "AutomatedAnnouncement")
ui.open()
/obj/machinery/announcement_system/ui_data()
diff --git a/code/game/machinery/bank_machine.dm b/code/game/machinery/bank_machine.dm
index 8033f538ba..882d0b989c 100644
--- a/code/game/machinery/bank_machine.dm
+++ b/code/game/machinery/bank_machine.dm
@@ -3,6 +3,7 @@
desc = "A machine used to deposit and withdraw station funds."
icon = 'goon/icons/obj/goon_terminals.dmi'
idle_power_usage = 100
+
var/siphoning = FALSE
var/next_warning = 0
var/obj/item/radio/radio
@@ -38,7 +39,6 @@
return
return ..()
-
/obj/machinery/computer/bank_machine/process()
..()
if(siphoning)
@@ -51,7 +51,7 @@
end_syphon()
return
- playsound(src.loc, 'sound/items/poster_being_created.ogg', 100, 1)
+ playsound(src, 'sound/items/poster_being_created.ogg', 100, TRUE)
syphoning_credits += 200
D.adjust_money(-200)
if(next_warning < world.time && prob(15))
@@ -60,17 +60,20 @@
radio.talk_into(src, message, radio_channel)
next_warning = world.time + minimum_time_between_warnings
-/obj/machinery/computer/bank_machine/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \
- datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state)
- ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+/obj/machinery/computer/bank_machine/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
- ui = new(user, src, ui_key, "BankMachine", name, 320, 165, master_ui, state)
+ ui = new(user, src, "BankMachine", name)
ui.open()
/obj/machinery/computer/bank_machine/ui_data(mob/user)
var/list/data = list()
var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_CAR)
- data["current_balance"] = D.account_balance
+
+ if(D)
+ data["current_balance"] = D.account_balance
+ else
+ data["current_balance"] = 0
data["siphoning"] = siphoning
data["station_name"] = station_name()
diff --git a/code/game/machinery/computer/Operating.dm b/code/game/machinery/computer/Operating.dm
index c8efe319db..a2fdc2336f 100644
--- a/code/game/machinery/computer/Operating.dm
+++ b/code/game/machinery/computer/Operating.dm
@@ -43,10 +43,13 @@
table.computer = src
break
-/obj/machinery/computer/operating/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.not_incapacitated_state)
- ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+/obj/machinery/computer/operating/ui_state(mob/user)
+ return GLOB.not_incapacitated_state
+
+/obj/machinery/computer/operating/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
- ui = new(user, src, ui_key, "OperatingComputer", name, 350, 470, master_ui, state)
+ ui = new(user, src, "OperatingComputer", name)
ui.open()
/obj/machinery/computer/operating/ui_data(mob/user)
@@ -153,6 +156,8 @@
))
return data
+
+
/obj/machinery/computer/operating/ui_act(action, params)
if(..())
return
diff --git a/code/game/machinery/computer/aifixer.dm b/code/game/machinery/computer/aifixer.dm
index d8553854b2..8ebb64097d 100644
--- a/code/game/machinery/computer/aifixer.dm
+++ b/code/game/machinery/computer/aifixer.dm
@@ -2,14 +2,13 @@
name = "\improper AI system integrity restorer"
desc = "Used with intelliCards containing nonfunctional AIs to restore them to working order."
req_access = list(ACCESS_CAPTAIN, ACCESS_ROBOTICS, ACCESS_HEADS)
- var/mob/living/silicon/ai/occupier = null
- var/active = 0
- circuit = /obj/item/circuitboard/computer/aifixer
icon_keyboard = "tech_key"
icon_screen = "ai-fixer"
light_color = LIGHT_COLOR_PINK
- ui_x = 370
- ui_y = 360
+ circuit = /obj/item/circuitboard/computer/aifixer
+
+ var/mob/living/silicon/ai/occupier = null
+ var/active = FALSE
/obj/machinery/computer/aifixer/attackby(obj/I, mob/user, params)
if(occupier && istype(I, /obj/item/screwdriver))
@@ -20,64 +19,45 @@
else
return ..()
-/obj/machinery/computer/aifixer/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \
- datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state)
- ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+
+/obj/machinery/computer/aifixer/ui_interact(mob/user, datum/tgui/ui) //artur didn't port this correctly
+ ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
- ui = new(user, src, ui_key, "AiRestorer", name, ui_x, ui_y, master_ui, state)
+ ui = new(user, src, "AiRestorer", name)
ui.open()
- var/dat = ""
+/obj/machinery/computer/aifixer/ui_data(mob/user)
+ var/list/data = list()
- if (src.occupier)
- var/laws
- dat += "
Stored AI: [src.occupier.name]
"
- dat += "System integrity: [(src.occupier.health+100)/2]% "
+ data["ejectable"] = FALSE
+ data["AI_present"] = FALSE
+ data["error"] = null
+ if(!occupier)
+ data["error"] = "Please transfer an AI unit."
+ else
+ data["AI_present"] = TRUE
+ data["name"] = occupier.name
+ data["restoring"] = active
+ data["health"] = (occupier.health + 100) / 2
+ data["isDead"] = occupier.stat == DEAD
+ data["laws"] = occupier.laws.get_law_list(include_zeroth = TRUE, render_html = FALSE)
- if (src.occupier.laws.zeroth)
- laws += "0: [src.occupier.laws.zeroth] "
+ return data
- for (var/index = 1, index <= src.occupier.laws.hacked.len, index++)
- var/law = src.occupier.laws.hacked[index]
- if (length(law) > 0)
- var/num = ionnum()
- laws += "[num]: [law] "
+/obj/machinery/computer/aifixer/ui_act(action, params)
+ if(..())
+ return
+ if(!occupier)
+ active = FALSE
- for (var/index = 1, index <= src.occupier.laws.ion.len, index++)
- var/law = src.occupier.laws.ion[index]
- if (length(law) > 0)
- var/num = ionnum()
- laws += "[num]: [law] "
-
- var/number = 1
- for (var/index = 1, index <= src.occupier.laws.inherent.len, index++)
- var/law = src.occupier.laws.inherent[index]
- if (length(law) > 0)
- laws += "[number]: [law] "
- number++
-
- for (var/index = 1, index <= src.occupier.laws.supplied.len, index++)
- var/law = src.occupier.laws.supplied[index]
- if (length(law) > 0)
- laws += "[number]: [law] "
- number++
-
- dat += "Laws: [laws] "
-
- if (src.occupier.stat == DEAD)
- dat += "AI non-functional"
- else
- dat += "AI functional"
- if (!src.active)
- dat += {"
"
- return output
-
-/obj/machinery/mecha_part_fabricator/proc/check_clearance(datum/design/D)
- if(!(obj_flags & EMAGGED) && (offstation_security_levels || is_station_level(z)) && !ISINRANGE(GLOB.security_level, D.min_security_level, D.max_security_level))
- return FALSE
- return TRUE
-
-/obj/machinery/mecha_part_fabricator/proc/output_part_info(datum/design/D)
- var/clearance = !(obj_flags & EMAGGED) && (offstation_security_levels || is_station_level(z))
- var/sec_text = ""
- if(clearance && (D.min_security_level > SEC_LEVEL_GREEN || D.max_security_level < SEC_LEVEL_DELTA))
- sec_text = " (Allowed security levels: "
- for(var/n in D.min_security_level to D.max_security_level)
- sec_text += NUM2SECLEVEL(n)
- if(n + 1 <= D.max_security_level)
- sec_text += ", "
- sec_text += ") "
- var/output = "[initial(D.name)] (Cost: [output_part_cost(D)]) [sec_text][get_construction_time_w_coeff(D)/10]sec"
- return output
-
-/obj/machinery/mecha_part_fabricator/proc/output_part_cost(datum/design/D)
- var/i = 0
- var/output
+/**
+ * Generates an info list for a given part.
+ *
+ * Returns a list of part information.
+ * * D - Design datum to get information on.
+ * * categories - Boolean, whether or not to parse snowflake categories into the part information list.
+ */
+/obj/machinery/mecha_part_fabricator/proc/output_part_info(datum/design/D, categories = FALSE)
+ var/cost = list()
for(var/c in D.materials)
var/datum/material/M = c
- output += "[i?" | ":null][get_resource_cost_w_coeff(D, M)] [M.name]"
- i++
- return output
+ cost[M.name] = get_resource_cost_w_coeff(D, M)
+ var/obj/built_item = D.build_path
+
+ var/list/category_override = null
+ var/list/sub_category = null
+
+ if(categories)
+ // Handle some special cases to build up sub-categories for the fab interface.
+ // Start with checking if this design builds a cyborg module.
+ if(built_item in typesof(/obj/item/borg/upgrade))
+ var/obj/item/borg/upgrade/U = built_item
+ var/module_types = initial(U.module_flags)
+ sub_category = list()
+ if(module_types)
+ if(module_types & BORG_MODULE_SECURITY)
+ sub_category += "Security"
+ if(module_types & BORG_MODULE_MINER)
+ sub_category += "Mining"
+ if(module_types & BORG_MODULE_JANITOR)
+ sub_category += "Janitor"
+ if(module_types & BORG_MODULE_MEDICAL)
+ sub_category += "Medical"
+ if(module_types & BORG_MODULE_ENGINEERING)
+ sub_category += "Engineering"
+ else
+ sub_category += "All Cyborgs"
+ // Else check if this design builds a piece of exosuit equipment.
+ else if(built_item in typesof(/obj/item/mecha_parts/mecha_equipment))
+ var/obj/item/mecha_parts/mecha_equipment/E = built_item
+ var/mech_types = initial(E.mech_flags)
+ sub_category = "Equipment"
+ if(mech_types)
+ category_override = list()
+ if(mech_types & EXOSUIT_MODULE_RIPLEY)
+ category_override += "Ripley"
+ if(mech_types & EXOSUIT_MODULE_FIREFIGHTER)
+ category_override += "Firefighter"
+ if(mech_types & EXOSUIT_MODULE_ODYSSEUS)
+ category_override += "Odysseus"
+ // if(mech_types & EXOSUIT_MODULE_CLARKE)
+ // category_override += "Clarke"
+ if(mech_types & EXOSUIT_MODULE_GYGAX_MED)
+ category_override += "Medical-Spec Gygax"
+ if(mech_types & EXOSUIT_MODULE_GYGAX)
+ category_override += "Gygax"
+ if(mech_types & EXOSUIT_MODULE_DURAND)
+ category_override += "Durand"
+ if(mech_types & EXOSUIT_MODULE_HONK)
+ category_override += "H.O.N.K"
+ if(mech_types & EXOSUIT_MODULE_PHAZON)
+ category_override += "Phazon"
+
+
+ var/list/part = list(
+ "name" = D.name,
+ "desc" = initial(built_item.desc),
+ "printTime" = get_construction_time_w_coeff(initial(D.construction_time))/10,
+ "cost" = cost,
+ "id" = D.id,
+ "subCategory" = sub_category,
+ "categoryOverride" = category_override,
+ "searchMeta" = "UNKNOWN"//D.search_metadata
+ )
+
+ return part
+
+/**
+ * Generates a list of resources / materials available to this Exosuit Fab
+ *
+ * Returns null if there is no material container available.
+ * List format is list(material_name = list(amount = ..., ref = ..., etc.))
+ */
/obj/machinery/mecha_part_fabricator/proc/output_available_resources()
- var/output
- var/datum/component/material_container/materials = GetComponent(/datum/component/material_container)
- for(var/mat_id in materials.materials)
- var/datum/material/M = mat_id
- var/amount = materials.materials[mat_id]
- output += "[M.name]: [amount] cm³"
- if(amount >= MINERAL_MATERIAL_AMOUNT)
- output += "- Remove \[1\]"
- if(amount >= (MINERAL_MATERIAL_AMOUNT * 10))
- output += " | \[10\]"
- output += " | \[All\]"
- output += " "
- return output
+ var/datum/component/material_container/materials = rmat.mat_container
+ var/list/material_data = list()
+
+ if(materials)
+ for(var/mat_id in materials.materials)
+ var/datum/material/M = mat_id
+ var/list/material_info = list()
+ var/amount = materials.materials[mat_id]
+
+ material_info = list(
+ "name" = M.name,
+ "ref" = REF(M),
+ "amount" = amount,
+ "sheets" = round(amount / MINERAL_MATERIAL_AMOUNT),
+ "removable" = amount >= MINERAL_MATERIAL_AMOUNT
+ )
+
+ material_data += list(material_info)
+
+ return material_data
+
+ return null
+
+/**
+ * Intended to be called when an item starts printing.
+ *
+ * Adds the overlay to show the fab working and sets active power usage settings.
+ */
+/obj/machinery/mecha_part_fabricator/proc/on_start_printing()
+ add_overlay("fab-active")
+ use_power = ACTIVE_POWER_USE
+
+/**
+ * Intended to be called when the exofab has stopped working and is no longer printing items.
+ *
+ * Removes the overlay to show the fab working and sets idle power usage settings. Additionally resets the description and turns off queue processing.
+ */
+/obj/machinery/mecha_part_fabricator/proc/on_finish_printing()
+ cut_overlay("fab-active")
+ use_power = IDLE_POWER_USE
+ desc = initial(desc)
+ process_queue = FALSE
+
+/**
+ * Calculates resource/material costs for printing an item based on the machine's resource coefficient.
+ *
+ * Returns a list of k,v resources with their amounts.
+ * * D - Design datum to calculate the modified resource cost of.
+ */
/obj/machinery/mecha_part_fabricator/proc/get_resources_w_coeff(datum/design/D)
var/list/resources = list()
for(var/R in D.materials)
@@ -164,294 +247,419 @@
resources[M] = get_resource_cost_w_coeff(D, M)
return resources
+/**
+ * Checks if the Exofab has enough resources to print a given item.
+ *
+ * Returns FALSE if the design has no reagents used in its construction (?) or if there are insufficient resources.
+ * Returns TRUE if there are sufficient resources to print the item.
+ * * D - Design datum to calculate the modified resource cost of.
+ */
/obj/machinery/mecha_part_fabricator/proc/check_resources(datum/design/D)
- if(D.reagents_list.len) // No reagents storage - no reagent designs.
+ if(length(D.reagents_list)) // No reagents storage - no reagent designs.
return FALSE
- var/datum/component/material_container/materials = GetComponent(/datum/component/material_container)
+ var/datum/component/material_container/materials = rmat.mat_container
if(materials.has_materials(get_resources_w_coeff(D)))
return TRUE
return FALSE
-/obj/machinery/mecha_part_fabricator/proc/build_part(datum/design/D)
+/**
+ * Attempts to build the next item in the build queue.
+ *
+ * Returns FALSE if either there are no more parts to build or the next part is not buildable.
+ * Returns TRUE if the next part has started building.
+ * * verbose - Whether the machine should use say() procs. Set to FALSE to disable the machine saying reasons for failure to build.
+ */
+/obj/machinery/mecha_part_fabricator/proc/build_next_in_queue(verbose = TRUE)
+ if(!length(queue))
+ return FALSE
+
+ var/datum/design/D = queue[1]
+ if(build_part(D, verbose))
+ remove_from_queue(1)
+ return TRUE
+
+ return FALSE
+
+/**
+ * Starts the build process for a given design datum.
+ *
+ * Returns FALSE if the procedure fails. Returns TRUE when being_built is set.
+ * Uses materials.
+ * * D - Design datum to attempt to print.
+ * * verbose - Whether the machine should use say() procs. Set to FALSE to disable the machine saying reasons for failure to build.
+ */
+/obj/machinery/mecha_part_fabricator/proc/build_part(datum/design/D, verbose = TRUE)
+ if(!D)
+ return FALSE
+
+ var/datum/component/material_container/materials = rmat.mat_container
+ if (!materials)
+ if(verbose)
+ say("No access to material storage, please contact the quartermaster.")
+ return FALSE
+ if (rmat.on_hold())
+ if(verbose)
+ say("Mineral access is on hold, please contact the quartermaster.")
+ return FALSE
+ if(!check_resources(D))
+ if(verbose)
+ say("Not enough resources. Processing stopped.")
+ return FALSE
+
+ build_materials = get_resources_w_coeff(D)
+
+ materials.use_materials(build_materials)
being_built = D
- desc = "It's building \a [initial(D.name)]."
- var/list/res_coef = get_resources_w_coeff(D)
+ build_finish = world.time + get_construction_time_w_coeff(initial(D.construction_time))
+ build_start = world.time
+ desc = "It's building \a [D.name]."
- var/datum/component/material_container/materials = GetComponent(/datum/component/material_container)
- materials.use_materials(res_coef)
- add_overlay("fab-active")
- use_power = ACTIVE_POWER_USE
- updateUsrDialog()
- sleep(get_construction_time_w_coeff(D))
- use_power = IDLE_POWER_USE
- cut_overlay("fab-active")
- desc = initial(desc)
+ rmat.silo_log(src, "built", -1, "[D.name]", build_materials)
- var/location = get_step(src,(dir))
- var/obj/item/I = new D.build_path(location)
- I.set_custom_materials(res_coef)
- say("\The [I] is complete.")
- being_built = null
-
- updateUsrDialog()
return TRUE
-/obj/machinery/mecha_part_fabricator/proc/update_queue_on_page()
- send_byjax(usr,"mecha_fabricator.browser","queue",list_queue())
- return
+/obj/machinery/mecha_part_fabricator/process()
+ // If there's a stored part to dispense due to an obstruction, try to dispense it.
+ if(stored_part)
+ var/turf/exit = get_step(src,(dir))
+ if(exit.density)
+ return TRUE
-/obj/machinery/mecha_part_fabricator/proc/add_part_set_to_queue(set_name)
- if(set_name in part_sets)
- for(var/v in stored_research.researched_designs)
- var/datum/design/D = SSresearch.techweb_design_by_id(v)
- if(D.build_type & MECHFAB)
- if(set_name in D.category)
- add_to_queue(D)
+ say("Obstruction cleared. \The [stored_part] is complete.")
+ stored_part.forceMove(exit)
+ stored_part = null
-/obj/machinery/mecha_part_fabricator/proc/add_to_queue(D)
+ // If there's nothing being built, try to build something
+ if(!being_built)
+ // If we're not processing the queue anymore or there's nothing to build, end processing.
+ if(!process_queue || !build_next_in_queue())
+ on_finish_printing()
+ STOP_PROCESSING(SSfastprocess, src)
+ //end_processing()
+ return TRUE
+ on_start_printing()
+
+ // If there's an item being built, check if it is complete.
+ if(being_built && (build_finish < world.time))
+ // Then attempt to dispense it and if appropriate build the next item.
+ dispense_built_part(being_built)
+ if(process_queue)
+ build_next_in_queue(FALSE)
+ return TRUE
+
+/**
+ * Dispenses a part to the tile infront of the Exosuit Fab.
+ *
+ * Returns FALSE is the machine cannot dispense the part on the appropriate turf.
+ * Return TRUE if the part was successfully dispensed.
+ * * D - Design datum to attempt to dispense.
+ */
+/obj/machinery/mecha_part_fabricator/proc/dispense_built_part(datum/design/D)
+ var/obj/item/I = new D.build_path(src)
+ // I.material_flags |= MATERIAL_NO_EFFECTS //Find a better way to do this.
+ I.set_custom_materials(build_materials)
+
+ being_built = null
+
+ var/turf/exit = get_step(src,(dir))
+ if(exit.density)
+ say("Error! Part outlet is obstructed.")
+ desc = "It's trying to dispense \a [D.name], but the part outlet is obstructed."
+ stored_part = I
+ return FALSE
+
+ say("\The [I] is complete.")
+ I.forceMove(exit)
+ return TRUE
+
+/**
+ * Adds a list of datum designs to the build queue.
+ *
+ * Will only add designs that are in this machine's stored techweb.
+ * Does final checks for datum IDs and makes sure this machine can build the designs.
+ * * part_list - List of datum design ids for designs to add to the queue.
+ */
+/obj/machinery/mecha_part_fabricator/proc/add_part_set_to_queue(list/part_list)
+ for(var/v in stored_research.researched_designs)
+ var/datum/design/D = SSresearch.techweb_design_by_id(v)
+ if((D.build_type & MECHFAB) && (D.id in part_list))
+ add_to_queue(D)
+
+/**
+ * Adds a datum design to the build queue.
+ *
+ * Returns TRUE if successful and FALSE if the design was not added to the queue.
+ * * D - Datum design to add to the queue.
+ */
+/obj/machinery/mecha_part_fabricator/proc/add_to_queue(datum/design/D)
if(!istype(queue))
queue = list()
if(D)
queue[++queue.len] = D
- return queue.len
+ return TRUE
+ return FALSE
+/**
+ * Removes datum design from the build queue based on index.
+ *
+ * Returns TRUE if successful and FALSE if a design was not removed from the queue.
+ * * index - Index in the build queue of the element to remove.
+ */
/obj/machinery/mecha_part_fabricator/proc/remove_from_queue(index)
- if(!isnum(index) || !ISINTEGER(index) || !istype(queue) || (index<1 || index>queue.len))
+ if(!isnum(index) || !ISINTEGER(index) || !istype(queue) || (index<1 || index>length(queue)))
return FALSE
queue.Cut(index,++index)
return TRUE
-/obj/machinery/mecha_part_fabricator/proc/process_queue()
- var/datum/design/D = queue[1]
- if(!D)
- remove_from_queue(1)
- if(queue.len)
- return process_queue()
- else
- return
- temp = null
- while(D)
- if(stat&(NOPOWER|BROKEN))
- return FALSE
- if(!check_clearance(D))
- say("Security level not met. Queue processing stopped.")
- temp = {"Security level not met to build next part.
- Try again | Return"}
- return FALSE
- if(!check_resources(D))
- say("Not enough resources. Queue processing stopped.")
- temp = {"Not enough resources to build next part.
- Try again | Return"}
- return FALSE
- remove_from_queue(1)
- build_part(D)
- D = listgetindex(queue, 1)
- say("Queue processing finished successfully.")
-
+/**
+ * Generates a list of parts formatted for tgui based on the current build queue.
+ *
+ * Returns a formatted list of lists containing formatted part information for every part in the build queue.
+ */
/obj/machinery/mecha_part_fabricator/proc/list_queue()
- var/output = "Queue contains:"
- if(!istype(queue) || !queue.len)
- output += " Nothing"
- else
- output += ""
- var/i = 0
- for(var/datum/design/D in queue)
- i++
- var/obj/part = D.build_path
- output += "
")
@@ -242,7 +244,7 @@
dat+=" The newscaster recognises you as: [src.admin_signature]"
if(1)
dat+= "Station Feed Channels"
- if( isemptylist(GLOB.news_network.network_channels) )
+ if( !length(GLOB.news_network.network_channels) )
dat+="No active channels found..."
else
for(var/datum/news/feed_channel/CHANNEL in GLOB.news_network.network_channels)
@@ -295,7 +297,7 @@
dat+="ATTENTION: This channel has been deemed as threatening to the welfare of the station, and marked with a Nanotrasen D-Notice. "
dat+="No further feed story additions are allowed while the D-Notice is in effect.
"
else
- if( isemptylist(src.admincaster_feed_channel.messages) )
+ if( !length(src.admincaster_feed_channel.messages) )
dat+="No feed messages found in channel... "
else
var/i = 0
@@ -317,7 +319,7 @@
dat+="NOTE: Due to the nature of news Feeds, total deletion of a Feed Story is not possible. "
dat+="Keep in mind that users attempting to view a censored feed will instead see the \[REDACTED\] tag above it."
dat+="Select Feed channel to get Stories from: "
- if(isemptylist(GLOB.news_network.network_channels))
+ if(!length(GLOB.news_network.network_channels))
dat+="No feed channels found active... "
else
for(var/datum/news/feed_channel/CHANNEL in GLOB.news_network.network_channels)
@@ -328,7 +330,7 @@
dat+="A D-Notice is to be bestowed upon the channel if the handling Authority deems it as harmful for the station's"
dat+="morale, integrity or disciplinary behaviour. A D-Notice will render a channel unable to be updated by anyone, without deleting any feed"
dat+="stories it might contain at the time. You can lift a D-Notice if you have the required access at any time."
- if(isemptylist(GLOB.news_network.network_channels))
+ if(!length(GLOB.news_network.network_channels))
dat+="No feed channels found active... "
else
for(var/datum/news/feed_channel/CHANNEL in GLOB.news_network.network_channels)
@@ -339,7 +341,7 @@
dat+="[src.admincaster_feed_channel.channel_name]: \[ created by: [src.admincaster_feed_channel.returnAuthor(-1)] \] "
dat+="[(src.admincaster_feed_channel.authorCensor) ? ("Undo Author censorship") : ("Censor channel Author")]"
- if( isemptylist(src.admincaster_feed_channel.messages) )
+ if( !length(src.admincaster_feed_channel.messages) )
dat+="No feed messages found in channel... "
else
for(var/datum/news/feed_message/MESSAGE in src.admincaster_feed_channel.messages)
@@ -356,7 +358,7 @@
dat+="ATTENTION: This channel has been deemed as threatening to the welfare of the station, and marked with a Nanotrasen D-Notice. "
dat+="No further feed story additions are allowed while the D-Notice is in effect.
"
else
- if( isemptylist(src.admincaster_feed_channel.messages) )
+ if( !length(src.admincaster_feed_channel.messages) )
dat+="No feed messages found in channel... "
else
for(var/datum/news/feed_message/MESSAGE in src.admincaster_feed_channel.messages)
@@ -426,7 +428,6 @@
"}
if(GLOB.master_mode == "secret")
dat += "(Force Secret Mode) "
-
if(GLOB.master_mode == "dynamic")
if(SSticker.current_state <= GAME_STATE_PREGAME)
dat += "(Force Roundstart Rulesets) "
@@ -449,7 +450,6 @@
dat += ""
if(SSticker.IsRoundInProgress())
dat += "(Game Mode Panel) "
-
dat += {"
Create Object
@@ -461,7 +461,7 @@
if(marked_datum && istype(marked_datum, /atom))
dat += "Duplicate Marked Datum "
- usr << browse(dat, "window=admin2;size=210x200")
+ usr << browse(dat, "window=admin2;size=240x280")
return
/////////////////////////////////////////////////////////////////////////////////////////////////admins2.dm merge
@@ -475,33 +475,42 @@
if (!usr.client.holder)
return
- var/list/options = list("Regular Restart", "Hard Restart (No Delay/Feeback Reason)", "Hardest Restart (No actions, just reboot)")
+ var/localhost_addresses = list("127.0.0.1", "::1")
+ var/list/options = list("Regular Restart", "Regular Restart (with delay)", "Hard Restart (No Delay/Feeback Reason)", "Hardest Restart (No actions, just reboot)")
if(world.TgsAvailable())
options += "Server Restart (Kill and restart DD)";
- var/rebootconfirm
if(SSticker.admin_delay_notice)
- if(alert(usr, "Are you sure? An admin has already delayed the round end for the following reason: [SSticker.admin_delay_notice]", "Confirmation", "Yes", "No") == "Yes")
- rebootconfirm = TRUE
- else
- rebootconfirm = TRUE
- if(rebootconfirm)
- var/result = input(usr, "Select reboot method", "World Reboot", options[1]) as null|anything in options
- if(result)
- SSblackbox.record_feedback("tally", "admin_verb", 1, "Reboot World") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
- var/init_by = "Initiated by [usr.client.holder.fakekey ? "Admin" : usr.key]."
- switch(result)
- if("Regular Restart")
- SSticker.Reboot(init_by, "admin reboot - by [usr.key] [usr.client.holder.fakekey ? "(stealth)" : ""]", 10)
- if("Hard Restart (No Delay, No Feeback Reason)")
- to_chat(world, "World reboot - [init_by]")
- world.Reboot()
- if("Hardest Restart (No actions, just reboot)")
- to_chat(world, "Hard world reboot - [init_by]")
- world.Reboot(fast_track = TRUE)
- if("Server Restart (Kill and restart DD)")
- to_chat(world, "Server restart - [init_by]")
- world.TgsEndProcess()
+ if(alert(usr, "Are you sure? An admin has already delayed the round end for the following reason: [SSticker.admin_delay_notice]", "Confirmation", "Yes", "No") != "Yes")
+ return FALSE
+
+ var/result = input(usr, "Select reboot method", "World Reboot", options[1]) as null|anything in options
+ if(result)
+ SSblackbox.record_feedback("tally", "admin_verb", 1, "Reboot World") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
+ var/init_by = "Initiated by [usr.client.holder.fakekey ? "Admin" : usr.key]."
+ switch(result)
+ if("Regular Restart")
+ if(!(isnull(usr.client.address) || (usr.client.address in localhost_addresses)))
+ if(alert("Are you sure you want to restart the server?","This server is live","Restart","Cancel") != "Restart")
+ return FALSE
+ SSticker.Reboot(init_by, "admin reboot - by [usr.key] [usr.client.holder.fakekey ? "(stealth)" : ""]", 10)
+ if("Regular Restart (with delay)")
+ var/delay = input("What delay should the restart have (in seconds)?", "Restart Delay", 5) as num|null
+ if(!delay)
+ return FALSE
+ if(!(isnull(usr.client.address) || (usr.client.address in localhost_addresses)))
+ if(alert("Are you sure you want to restart the server?","This server is live","Restart","Cancel") != "Restart")
+ return FALSE
+ SSticker.Reboot(init_by, "admin reboot - by [usr.key] [usr.client.holder.fakekey ? "(stealth)" : ""]", delay * 10)
+ if("Hard Restart (No Delay, No Feeback Reason)")
+ to_chat(world, "World reboot - [init_by]")
+ world.Reboot()
+ if("Hardest Restart (No actions, just reboot)")
+ to_chat(world, "Hard world reboot - [init_by]")
+ world.Reboot(fast_track = TRUE)
+ if("Server Restart (Kill and restart DD)")
+ to_chat(world, "Server restart - [init_by]")
+ world.TgsEndProcess()
/datum/admins/proc/end_round()
set category = "Server"
@@ -529,7 +538,7 @@
if(message)
if(!check_rights(R_SERVER,0))
message = adminscrub(message,500)
- to_chat(world, "[usr.client.holder.fakekey ? "Administrator" : usr.key] Announces:\n \t [message]")
+ to_chat(world, "[usr.client.holder.fakekey ? "Administrator" : usr.key] Announces:\n \t [message]", confidential = TRUE)
log_admin("Announce: [key_name(usr)] : [message]")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Announce") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
@@ -551,7 +560,7 @@
else
message_admins("[key_name(usr)] set the admin notice.")
log_admin("[key_name(usr)] set the admin notice:\n[new_admin_notice]")
- to_chat(world, "Admin Notice:\n \t [new_admin_notice]")
+ to_chat(world, "Admin Notice:\n \t [new_admin_notice]", confidential = TRUE)
SSblackbox.record_feedback("tally", "admin_verb", 1, "Set Admin Notice") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
GLOB.admin_notice = new_admin_notice
return
@@ -598,20 +607,29 @@
set desc="Start the round RIGHT NOW"
set name="Start Now"
if(SSticker.current_state == GAME_STATE_PREGAME || SSticker.current_state == GAME_STATE_STARTUP)
- SSticker.start_immediately = TRUE
- log_admin("[usr.key] has started the game.")
- var/msg = ""
- if(SSticker.current_state == GAME_STATE_STARTUP)
- msg = " (The server is still setting up, but the round will be \
- started as soon as possible.)"
- message_admins("\
- [usr.key] has started the game.[msg]")
- SSblackbox.record_feedback("tally", "admin_verb", 1, "Start Now") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
- return 1
+ if(!SSticker.start_immediately)
+ var/localhost_addresses = list("127.0.0.1", "::1")
+ if(!(isnull(usr.client.address) || (usr.client.address in localhost_addresses)))
+ if(alert("Are you sure you want to start the round?","Start Now","Start Now","Cancel") != "Start Now")
+ return FALSE
+ SSticker.start_immediately = TRUE
+ log_admin("[usr.key] has started the game.")
+ var/msg = ""
+ if(SSticker.current_state == GAME_STATE_STARTUP)
+ msg = " (The server is still setting up, but the round will be \
+ started as soon as possible.)"
+ message_admins("[usr.key] has started the game.[msg]")
+ SSblackbox.record_feedback("tally", "admin_verb", 1, "Start Now") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
+ return TRUE
+ SSticker.start_immediately = FALSE
+ SSticker.SetTimeLeft(1800)
+ to_chat(world, "The game will start in 180 seconds.")
+ SEND_SOUND(world, sound(get_announcer_sound("attention")))
+ message_admins("[usr.key] has cancelled immediate game start. Game will start in 180 seconds.")
+ log_admin("[usr.key] has cancelled immediate game start.")
else
to_chat(usr, "Error: Start Now: Game has already started.")
-
- return 0
+ return FALSE
/datum/admins/proc/toggleenter()
set category = "Server"
@@ -619,9 +637,9 @@
set name="Toggle Entering"
GLOB.enter_allowed = !( GLOB.enter_allowed )
if (!( GLOB.enter_allowed ))
- to_chat(world, "New players may no longer enter the game.")
+ to_chat(world, "New players may no longer enter the game.", confidential = TRUE)
else
- to_chat(world, "New players may now enter the game.")
+ to_chat(world, "New players may now enter the game.", confidential = TRUE)
log_admin("[key_name(usr)] toggled new player game entering.")
message_admins("[key_name_admin(usr)] toggled new player game entering.")
world.update_status()
@@ -634,9 +652,9 @@
var/alai = CONFIG_GET(flag/allow_ai)
CONFIG_SET(flag/allow_ai, !alai)
if (alai)
- to_chat(world, "The AI job is no longer chooseable.")
+ to_chat(world, "The AI job is no longer chooseable.", confidential = TRUE)
else
- to_chat(world, "The AI job is chooseable now.")
+ to_chat(world, "The AI job is chooseable now.", confidential = TRUE)
log_admin("[key_name(usr)] toggled AI allowed.")
world.update_status()
SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle AI", "[!alai ? "Disabled" : "Enabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
@@ -654,7 +672,7 @@
aiPlayer.end_multicam()
log_admin("[key_name(usr)] toggled AI multicam.")
world.update_status()
- to_chat(GLOB.ai_list | GLOB.admins, "The AI [almcam ? "no longer" : "now"] has multicam.")
+ to_chat(GLOB.ai_list | GLOB.admins, "The AI [almcam ? "no longer" : "now"] has multicam.", confidential = TRUE)
SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Multicam", "[!almcam ? "Disabled" : "Enabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/datum/admins/proc/toggleaban()
@@ -664,9 +682,9 @@
var/new_nores = !CONFIG_GET(flag/norespawn)
CONFIG_SET(flag/norespawn, new_nores)
if (!new_nores)
- to_chat(world, "You may now respawn.")
+ to_chat(world, "You may now respawn.", confidential = TRUE)
else
- to_chat(world, "You may no longer respawn :(")
+ to_chat(world, "You may no longer respawn :(", confidential = TRUE)
message_admins("[key_name_admin(usr)] toggled respawn to [!new_nores ? "On" : "Off"].")
log_admin("[key_name(usr)] toggled respawn to [!new_nores ? "On" : "Off"].")
world.update_status()
@@ -675,7 +693,7 @@
/datum/admins/proc/delay()
set category = "Server"
set desc="Delay the game start"
- set name="Delay pre-game"
+ set name="Delay Pre-Game"
var/newtime = input("Set a new time in seconds. Set -1 for indefinite delay.","Set Delay",round(SSticker.GetTimeLeft()/10)) as num|null
if(SSticker.current_state > GAME_STATE_PREGAME)
@@ -683,11 +701,12 @@
if(newtime)
newtime = newtime*10
SSticker.SetTimeLeft(newtime)
+ SSticker.start_immediately = FALSE
if(newtime < 0)
- to_chat(world, "The game start has been delayed.")
+ to_chat(world, "The game start has been delayed.", confidential = TRUE)
log_admin("[key_name(usr)] delayed the round start.")
else
- to_chat(world, "The game will start in [DisplayTimeText(newtime)].")
+ to_chat(world, "The game will start in [DisplayTimeText(newtime)].", confidential = TRUE)
SEND_SOUND(world, sound(get_announcer_sound("attention")))
log_admin("[key_name(usr)] set the pre-game delay to [DisplayTimeText(newtime)].")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Delay Game Start") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
@@ -724,20 +743,28 @@
set desc = "(atom path) Spawn an atom"
set name = "Spawn"
- if(!check_rights(R_SPAWN))
+ if(!check_rights(R_SPAWN) || !object)
+ return
+
+ var/list/preparsed = splittext(object,":")
+ var/path = preparsed[1]
+ var/amount = 1
+ if(preparsed.len > 1)
+ amount = clamp(text2num(preparsed[2]),1, 50) //50 at a time!
+
+ var/chosen = pick_closest_path(path)
+ if(!chosen)
return
var/turf/T = get_turf(usr)
- var/chosen = pick_closest_path(object)
- if(!chosen)
- return
if(ispath(chosen, /turf))
T.ChangeTurf(chosen)
else
- var/atom/A = new chosen(T)
- A.flags_1 |= ADMIN_SPAWNED_1
+ for(var/i in 1 to amount)
+ var/atom/A = new chosen(T)
+ A.flags_1 |= ADMIN_SPAWNED_1
- log_admin("[key_name(usr)] spawned [chosen] at [AREACOORD(usr)]")
+ log_admin("[key_name(usr)] spawned [amount] x [chosen] at [AREACOORD(usr)]")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Spawn Atom") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/datum/admins/proc/podspawn_atom(object as text)
@@ -782,20 +809,18 @@
log_admin("[key_name(usr)] spawned cargo pack [chosen] at [AREACOORD(usr)]")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Spawn Cargo") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
-
-/datum/admins/proc/show_traitor_panel(mob/M in GLOB.mob_list)
+/datum/admins/proc/show_traitor_panel(mob/target_mob in GLOB.mob_list)
set category = "Admin"
set desc = "Edit mobs's memory and role"
set name = "Show Traitor Panel"
-
- if(!istype(M))
- to_chat(usr, "This can only be used on instances of type /mob")
+ var/datum/mind/target_mind = target_mob.mind
+ if(!target_mind)
+ to_chat(usr, "This mob has no mind!", confidential = TRUE)
return
- if(!M.mind)
- to_chat(usr, "This mob has no mind!")
+ if(!istype(target_mob) && !istype(target_mind))
+ to_chat(usr, "This can only be used on instances of type /mob and /mind", confidential = TRUE)
return
-
- M.mind.traitor_panel()
+ target_mind.traitor_panel()
SSblackbox.record_feedback("tally", "admin_verb", 1, "Traitor Panel") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
@@ -805,9 +830,9 @@
set name="Toggle tinted welding helmes"
GLOB.tinted_weldhelh = !( GLOB.tinted_weldhelh )
if (GLOB.tinted_weldhelh)
- to_chat(world, "The tinted_weldhelh has been enabled!")
+ to_chat(world, "The tinted_weldhelh has been enabled!", confidential = TRUE)
else
- to_chat(world, "The tinted_weldhelh has been disabled!")
+ to_chat(world, "The tinted_weldhelh has been disabled!", confidential = TRUE)
log_admin("[key_name(usr)] toggled tinted_weldhelh.")
message_admins("[key_name_admin(usr)] toggled tinted_weldhelh.")
SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Tinted Welding Helmets", "[GLOB.tinted_weldhelh ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
@@ -819,9 +844,9 @@
var/new_guest_ban = !CONFIG_GET(flag/guest_ban)
CONFIG_SET(flag/guest_ban, new_guest_ban)
if (new_guest_ban)
- to_chat(world, "Guests may no longer enter the game.")
+ to_chat(world, "Guests may no longer enter the game.", confidential = TRUE)
else
- to_chat(world, "Guests may now enter the game.")
+ to_chat(world, "Guests may now enter the game.", confidential = TRUE)
log_admin("[key_name(usr)] toggled guests game entering [!new_guest_ban ? "" : "dis"]allowed.")
message_admins("[key_name_admin(usr)] toggled guests game entering [!new_guest_ban ? "" : "dis"]allowed.")
SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Guests", "[!new_guest_ban ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
@@ -832,37 +857,37 @@
var/mob/living/silicon/S = i
ai_number++
if(isAI(S))
- to_chat(usr, "AI [key_name(S, usr)]'s laws:")
+ to_chat(usr, "AI [key_name(S, usr)]'s laws:", confidential = TRUE)
else if(iscyborg(S))
var/mob/living/silicon/robot/R = S
- to_chat(usr, "CYBORG [key_name(S, usr)] [R.connected_ai?"(Slaved to: [key_name(R.connected_ai)])":"(Independent)"]: laws:")
+ to_chat(usr, "CYBORG [key_name(S, usr)] [R.connected_ai?"(Slaved to: [key_name(R.connected_ai)])":"(Independent)"]: laws:", confidential = TRUE)
else if (ispAI(S))
- to_chat(usr, "pAI [key_name(S, usr)]'s laws:")
+ to_chat(usr, "pAI [key_name(S, usr)]'s laws:", confidential = TRUE)
else
- to_chat(usr, "SOMETHING SILICON [key_name(S, usr)]'s laws:")
+ to_chat(usr, "SOMETHING SILICON [key_name(S, usr)]'s laws:", confidential = TRUE)
if (S.laws == null)
- to_chat(usr, "[key_name(S, usr)]'s laws are null?? Contact a coder.")
+ to_chat(usr, "[key_name(S, usr)]'s laws are null?? Contact a coder.", confidential = TRUE)
else
S.laws.show_laws(usr)
if(!ai_number)
- to_chat(usr, "No AIs located" )
+ to_chat(usr, "No AIs located" , confidential = TRUE)
/datum/admins/proc/output_all_devil_info()
var/devil_number = 0
for(var/datum/mind/D in SSticker.mode.devils)
devil_number++
var/datum/antagonist/devil/devil = D.has_antag_datum(/datum/antagonist/devil)
- to_chat(usr, "Devil #[devil_number]:
"
- dat = dat.Join()
- var/datum/browser/popup = new(user, "bounties", "Nanotrasen Bounties", 700, 600)
- popup.set_content(dat)
- popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state))
- popup.open()
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "CargoBountyConsole", name)
+ ui.open()
-/obj/machinery/computer/bounty/Topic(href, href_list)
+/obj/machinery/computer/bounty/ui_data(mob/user)
+ var/list/data = list()
+ var/list/bountyinfo = list()
+ for(var/datum/bounty/B in GLOB.bounties_list)
+ bountyinfo += list(list("name" = B.name, "description" = B.description, "reward_string" = B.reward_string(), "completion_string" = B.completion_string() , "claimed" = B.claimed, "can_claim" = B.can_claim(), "priority" = B.high_priority, "bounty_ref" = REF(B)))
+ data["stored_cash"] = cargocash.account_balance
+ data["bountydata"] = bountyinfo
+ return data
+
+/obj/machinery/computer/bounty/ui_act(action,params)
if(..())
return
-
- switch(href_list["choice"])
+ switch(action)
+ if("ClaimBounty")
+ var/datum/bounty/cashmoney = locate(params["bounty"]) in GLOB.bounties_list
+ if(cashmoney)
+ cashmoney.claim()
+ return TRUE
if("Print")
if(printer_ready < world.time)
printer_ready = world.time + PRINTER_TIMEOUT
print_paper()
-
- if("Claim")
- var/datum/bounty/B = locate(href_list["d_rec"])
- if(B in GLOB.bounties_list)
- B.claim()
-
- if(href_list["refresh"])
- playsound(src, "terminal_type", 25, 0)
-
- updateUsrDialog()
+ return
diff --git a/code/modules/cargo/centcom_podlauncher.dm b/code/modules/cargo/centcom_podlauncher.dm
index f33ea7059b..b7eac1e591 100644
--- a/code/modules/cargo/centcom_podlauncher.dm
+++ b/code/modules/cargo/centcom_podlauncher.dm
@@ -11,7 +11,7 @@
/client/proc/centcom_podlauncher() //Creates a verb for admins to open up the ui
set name = "Config/Launch Supplypod"
- set desc = "Configure and launch a Centcom supplypod full of whatever your heart desires!"
+ set desc = "Configure and launch a CentCom supplypod full of whatever your heart desires!"
set category = "Admin"
var/datum/centcom_podlauncher/plaunch = new(usr)//create the datum
plaunch.ui_interact(usr)//datum has a tgui component, here we open the window
@@ -23,7 +23,10 @@
var/turf/oldTurf //Keeps track of where the user was at if they use the "teleport to centcom" button, so they can go back
var/client/holder //client of whoever is using this datum
var/area/bay //What bay we're using to launch shit from.
+ var/turf/dropoff_turf //If we're reversing, where the reverse pods go
+ var/picking_dropoff_turf
var/launchClone = FALSE //If true, then we don't actually launch the thing in the bay. Instead we call duplicateObject() and send the result
+ var/launchRandomItem = FALSE //If true, lauches a single random item instead of everything on a turf.
var/launchChoice = 1 //Determines if we launch all at once (0) , in order (1), or at random(2)
var/explosionChoice = 0 //Determines if there is no explosion (0), custom explosion (1), or just do a maxcap (2)
var/damageChoice = 0 //Determines if we do no damage (0), custom amnt of damage (1), or gib + 5000dmg (2)
@@ -50,20 +53,25 @@
temp_pod = new(locate(/area/centcom/supplypod/podStorage) in GLOB.sortedAreas) //Create a new temp_pod in the podStorage area on centcom (so users are free to look at it and change other variables if needed)
orderedArea = createOrderedArea(bay) //Order all the turfs in the selected bay (top left to bottom right) to a single list. Used for the "ordered" mode (launchChoice = 1)
-/datum/centcom_podlauncher/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, \
-force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.admin_state)//ui_interact is called when the client verb is called.
+/datum/centcom_podlauncher/ui_state(mob/user)
+ return GLOB.admin_state
- ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+/datum/centcom_podlauncher/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
- ui = new(user, src, ui_key, "CentcomPodLauncher", "Config/Launch Supplypod", 700, 700, master_ui, state)
+ ui = new(user, src, "CentcomPodLauncher")
ui.open()
/datum/centcom_podlauncher/ui_data(mob/user) //Sends info about the pod to the UI.
var/list/data = list() //*****NOTE*****: Many of these comments are similarly described in supplypod.dm. If you change them here, please consider doing so in the supplypod code as well!
- var/B = (istype(bay, /area/centcom/supplypod/loading/one)) ? 1 : (istype(bay, /area/centcom/supplypod/loading/two)) ? 2 : (istype(bay, /area/centcom/supplypod/loading/three)) ? 3 : (istype(bay, /area/centcom/supplypod/loading/four)) ? 4 : 0 //top ten THICCEST FUCKING TERNARY CONDITIONALS OF 2036
- data["bay"] = B //Holds the current bay the user is launching objects from. Bays are specific rooms on the centcom map.
+ var/B = (istype(bay, /area/centcom/supplypod/loading/one)) ? 1 : (istype(bay, /area/centcom/supplypod/loading/two)) ? 2 : (istype(bay, /area/centcom/supplypod/loading/three)) ? 3 : (istype(bay, /area/centcom/supplypod/loading/four)) ? 4 : 0 //(istype(bay, /area/centcom/supplypod/loading/ert)) ? 5 : 0 //top ten THICCEST FUCKING TERNARY CONDITIONALS OF 2036
+ data["bay"] = bay //Holds the current bay the user is launching objects from. Bays are specific rooms on the centcom map.
+ data["bayNumber"] = B //Holds the bay as a number. Useful for comparisons in centcom_podlauncher.ract
data["oldArea"] = (oldTurf ? get_area(oldTurf) : null) //Holds the name of the area that the user was in before using the teleportCentcom action
+ data["picking_dropoff_turf"] = picking_dropoff_turf //If we're picking or have picked a dropoff turf. Only works when pod is in reverse mode
+ data["dropoff_turf"] = dropoff_turf //The turf that reverse pods will drop their newly acquired cargo off at
data["launchClone"] = launchClone //Do we launch the actual items in the bay or just launch clones of them?
+ data["launchRandomItem"] = launchRandomItem //Do we launch a single random item instead of everything on the turf?
data["launchChoice"] = launchChoice //Launch turfs all at once (0), ordered (1), or randomly(1)
data["explosionChoice"] = explosionChoice //An explosion that occurs when landing. Can be no explosion (0), custom explosion (1), or maxcap (2)
data["damageChoice"] = damageChoice //Damage that occurs to any mob under the pod when it lands. Can be no damage (0), custom damage (1), or gib+5000dmg (2)
@@ -72,11 +80,12 @@ force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.adm
data["openingDelay"] = temp_pod.openingDelay //How long the pod takes to open after landing
data["departureDelay"] = temp_pod.departureDelay //How long the pod takes to leave after opening (if bluespace=true, it deletes. if reversing=true, it flies back to centcom)
data["styleChoice"] = temp_pod.style //Style is a variable that keeps track of what the pod is supposed to look like. It acts as an index to the POD_STYLES list in cargo.dm defines to get the proper icon/name/desc for the pod.
+ data["effectShrapnel"] = FALSE //temp_pod.effectShrapnel //If true, creates a cloud of shrapnel of a decided type and magnitude on landing
data["effectStun"] = temp_pod.effectStun //If true, stuns anyone under the pod when it launches until it lands, forcing them to get hit by the pod. Devilish!
data["effectLimb"] = temp_pod.effectLimb //If true, pops off a limb (if applicable) from anyone caught under the pod when it lands
data["effectOrgans"] = temp_pod.effectOrgans //If true, yeets the organs out of any bodies caught under the pod when it lands
data["effectBluespace"] = temp_pod.bluespace //If true, the pod deletes (in a shower of sparks) after landing
- data["effectStealth"] = temp_pod.effectStealth //If true, a target icon isnt displayed on the turf where the pod will land
+ data["effectStealth"] = temp_pod.effectStealth //If true, a target icon isn't displayed on the turf where the pod will land
data["effectQuiet"] = temp_pod.effectQuiet //The female sniper. If true, the pod makes no noise (including related explosions, opening sounds, etc)
data["effectMissile"] = temp_pod.effectMissile //If true, the pod deletes the second it lands. If you give it an explosion, it will act like a missile exploding as it hits the ground
data["effectCircle"] = temp_pod.effectCircle //If true, allows the pod to come in at any angle. Bit of a weird feature but whatever its here
@@ -115,20 +124,41 @@ force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.adm
bay = locate(/area/centcom/supplypod/loading/four) in GLOB.sortedAreas
refreshBay()
. = TRUE
+ if("bay5")
+ to_chat(usr, "LetterN is lazy and didin't bother porting this new cc area!")
+ return
+ // bay = locate(/area/centcom/supplypod/loading/ert) in GLOB.sortedAreas
+ // refreshBay()
+ // . = TRUE
+ if("pickDropoffTurf") //Enters a mode that lets you pick the dropoff location for reverse pods
+ if (picking_dropoff_turf)
+ picking_dropoff_turf = FALSE
+ updateCursor(FALSE, FALSE) //Update the cursor of the user to a cool looking target icon
+ return
+ if (launcherActivated)
+ launcherActivated = FALSE //We don't want to have launch mode enabled while we're picking a turf
+ picking_dropoff_turf = TRUE
+ updateCursor(FALSE, TRUE) //Update the cursor of the user to a cool looking target icon
+ . = TRUE
+ if("clearDropoffTurf")
+ picking_dropoff_turf = FALSE
+ dropoff_turf = null
+ updateCursor(FALSE, FALSE)
+ . = TRUE
if("teleportCentcom") //Teleports the user to the centcom supply loading facility.
var/mob/M = holder.mob //We teleport whatever mob the client is attached to at the point of clicking
oldTurf = get_turf(M) //Used for the "teleportBack" action
- var/area/A = locate(/area/centcom/supplypod/loading) in GLOB.sortedAreas
+ var/area/A = locate(bay) in GLOB.sortedAreas
var/list/turfs = list()
for(var/turf/T in A)
turfs.Add(T) //Fill a list with turfs in the area
- var/turf/T = safepick(turfs) //Only teleport if the list isn't empty
- if(!T) //If the list is empty, error and cancel
+ if (!length(turfs)) //If the list is empty, error and cancel
to_chat(M, "Nowhere to jump to!")
- return
+ return //Only teleport if the list isn't empty
+ var/turf/T = pick(turfs)
M.forceMove(T) //Perform the actual teleport
- log_admin("[key_name(usr)] jumped to [AREACOORD(A)]")
- message_admins("[key_name_admin(usr)] jumped to [AREACOORD(A)]")
+ log_admin("[key_name(usr)] jumped to [AREACOORD(T)]")
+ message_admins("[key_name_admin(usr)] jumped to [AREACOORD(T)]")
. = TRUE
if("teleportBack") //After teleporting to centcom, this button allows the user to teleport to the last spot they were at.
var/mob/M = holder.mob
@@ -144,6 +174,9 @@ force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.adm
if("launchClone") //Toggles the launchClone var. See variable declarations above for what this specifically means
launchClone = !launchClone
. = TRUE
+ if("launchRandomItem") //Pick random turfs from the supplypod bay at centcom to launch
+ launchRandomItem = !launchRandomItem
+ . = TRUE
if("launchOrdered") //Launch turfs (from the orderedArea list) one at a time in order, from the supplypod bay at centcom
if (launchChoice == 1) //launchChoice 1 represents ordered. If we push "ordered" and it already is, then we go to default value
launchChoice = 0
@@ -152,7 +185,7 @@ force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.adm
launchChoice = 1
updateSelector()
. = TRUE
- if("launchRandom") //Pick random turfs from the supplypod bay at centcom to launch
+ if("launchRandomTurf") //Pick random turfs from the supplypod bay at centcom to launch
if (launchChoice == 2)
launchChoice = 0
updateSelector()
@@ -170,11 +203,11 @@ force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.adm
var/list/expNames = list("Devastation", "Heavy Damage", "Light Damage", "Flame") //Explosions have a range of different types of damage
var/list/boomInput = list()
for (var/i=1 to expNames.len) //Gather input from the user for the value of each type of damage
- boomInput.Add(input("[expNames[i]] Range", "Enter the [expNames[i]] range of the explosion. WARNING: This ignores the bomb cap!", 0) as null|num)
+ boomInput.Add(input("Enter the [expNames[i]] range of the explosion. WARNING: This ignores the bomb cap!", "[expNames[i]] Range", 0) as null|num)
if (isnull(boomInput[i]))
return
if (!isnum(boomInput[i])) //If the user doesn't input a number, set that specific explosion value to zero
- alert(usr, "That wasnt a number! Value set to default (zero) instead.")
+ alert(usr, "That wasn't a number! Value set to default (zero) instead.")
boomInput = 0
explosionChoice = 1
temp_pod.explosionSize = boomInput
@@ -192,11 +225,11 @@ force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.adm
damageChoice = 0
temp_pod.damage = 0
return
- var/damageInput = input("How much damage to deal", "Enter the amount of brute damage dealt by getting hit", 0) as null|num
+ var/damageInput = input("Enter the amount of brute damage dealt by getting hit","How much damage to deal", 0) as null|num
if (isnull(damageInput))
return
if (!isnum(damageInput)) //Sanitize the input for damage to deal.s
- alert(usr, "That wasnt a number! Value set to default (zero) instead.")
+ alert(usr, "That wasn't a number! Value set to default (zero) instead.")
damageInput = 0
damageChoice = 1
temp_pod.damage = damageInput
@@ -226,13 +259,32 @@ force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.adm
temp_pod.desc = descInput
temp_pod.adminNamed = TRUE //This variable is checked in the supplypod/setStyle() proc
. = TRUE
+ /*
+ if("effectShrapnel") //Creates a cloud of shrapnel on landing
+ if (temp_pod.effectShrapnel == TRUE) //If already doing custom damage, set back to default (no shrapnel)
+ temp_pod.effectShrapnel = FALSE
+ return
+ var/shrapnelInput = input("Please enter the type of pellet cloud you'd like to create on landing (Can be any projectile!)", "Projectile Typepath", 0) in sortList(subtypesof(/obj/item/projectile), /proc/cmp_typepaths_asc)
+ if (isnull(shrapnelInput))
+ return
+ var/shrapnelMagnitude = input("Enter the magnitude of the pellet cloud. This is usually a value around 1-5. Please note that Ryll-Ryll has asked me to tell you that if you go too crazy with the projectiles you might crash the server. So uh, be gentle!", "Shrapnel Magnitude", 0) as null|num
+ if (isnull(shrapnelMagnitude))
+ return
+ if (!isnum(shrapnelMagnitude))
+ alert(usr, "That wasn't a number! Value set to 3 instead.")
+ shrapnelMagnitude = 3
+ temp_pod.shrapnel_type = shrapnelInput
+ temp_pod.shrapnel_magnitude = shrapnelMagnitude
+ temp_pod.effectShrapnel = TRUE
+ . = TRUE
+ */
if("effectStun") //Toggle: Any mob under the pod is stunned (cant move) until the pod lands, hitting them!
temp_pod.effectStun = !temp_pod.effectStun
. = TRUE
if("effectLimb") //Toggle: Anyone carbon mob under the pod loses a limb when it lands
temp_pod.effectLimb = !temp_pod.effectLimb
. = TRUE
- if("effectOrgans") //Toggle: Any carbon mob under the pod loses every limb and organ
+ if("effectOrgans") //Toggle: Anyone carbon mob under the pod loses a limb when it lands
temp_pod.effectOrgans = !temp_pod.effectOrgans
. = TRUE
if("effectBluespace") //Toggle: Deletes the pod after landing
@@ -253,7 +305,7 @@ force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.adm
if("effectBurst") //Toggle: Launch 5 pods (with a very slight delay between) in a 3x3 area centered around the target
effectBurst = !effectBurst
. = TRUE
- if("effectAnnounce") //Toggle: Sends a ghost announcement.
+ if("effectAnnounce") //Toggle: Launch 5 pods (with a very slight delay between) in a 3x3 area centered around the target
effectAnnounce = !effectAnnounce
. = TRUE
if("effectReverse") //Toggle: Don't send any items. Instead, after landing, close (taking any objects inside) and go back to the centcom bay it came from
@@ -272,15 +324,15 @@ force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.adm
. = TRUE
////////////////////////////TIMER DELAYS//////////////////
- if("fallDuration") //Change the falling animation duration
- if (temp_pod.fallDuration != initial(temp_pod.fallDuration)) //If the fall duration has already been changed when we push the "change value" button, then set it to default
+ if("fallDuration") //Change the time it takes the pod to land, after firing
+ if (temp_pod.fallDuration != initial(temp_pod.fallDuration)) //If the landing delay has already been changed when we push the "change value" button, then set it to default
temp_pod.fallDuration = initial(temp_pod.fallDuration)
return
- var/timeInput = input("Enter the duration of the pod's falling animation, in seconds", "Delay Time", initial(temp_pod.fallDuration) * 0.1) as null|num
+ var/timeInput = input("Enter the duration of the pod's falling animation, in seconds", "Delay Time", initial(temp_pod.fallDuration) * 0.1) as null|num
if (isnull(timeInput))
return
if (!isnum(timeInput)) //Sanitize input, if it doesnt check out, error and set to default
- alert(usr, "That wasnt a number! Value set to default ([initial(temp_pod.fallDuration)*0.1]) instead.")
+ alert(usr, "That wasn't a number! Value set to default ([initial(temp_pod.fallDuration)*0.1]) instead.")
timeInput = initial(temp_pod.fallDuration)
temp_pod.fallDuration = 10 * timeInput
. = TRUE
@@ -292,7 +344,7 @@ force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.adm
if (isnull(timeInput))
return
if (!isnum(timeInput)) //Sanitize input, if it doesnt check out, error and set to default
- alert(usr, "That wasnt a number! Value set to default ([initial(temp_pod.landingDelay)*0.1]) instead.")
+ alert(usr, "That wasn't a number! Value set to default ([initial(temp_pod.landingDelay)*0.1]) instead.")
timeInput = initial(temp_pod.landingDelay)
temp_pod.landingDelay = 10 * timeInput
. = TRUE
@@ -304,7 +356,7 @@ force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.adm
if (isnull(timeInput))
return
if (!isnum(timeInput)) //Sanitize input
- alert(usr, "That wasnt a number! Value set to default ([initial(temp_pod.openingDelay)*0.1]) instead.")
+ alert(usr, "That wasn't a number! Value set to default ([initial(temp_pod.openingDelay)*0.1]) instead.")
timeInput = initial(temp_pod.openingDelay)
temp_pod.openingDelay = 10 * timeInput
. = TRUE
@@ -316,13 +368,13 @@ force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.adm
if (isnull(timeInput))
return
if (!isnum(timeInput))
- alert(usr, "That wasnt a number! Value set to default ([initial(temp_pod.departureDelay)*0.1]) instead.")
+ alert(usr, "That wasn't a number! Value set to default ([initial(temp_pod.departureDelay)*0.1]) instead.")
timeInput = initial(temp_pod.departureDelay)
temp_pod.departureDelay = 10 * timeInput
. = TRUE
////////////////////////////ADMIN SOUNDS//////////////////
- if("fallingSound") //Admin sound from a local file that plays when the pod falls
+ if("fallSound") //Admin sound from a local file that plays when the pod lands
if ((temp_pod.fallingSound) != initial(temp_pod.fallingSound))
temp_pod.fallingSound = initial(temp_pod.fallingSound)
temp_pod.fallingSoundLength = initial(temp_pod.fallingSoundLength)
@@ -334,7 +386,7 @@ force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.adm
if (isnull(timeInput))
return
if (!isnum(timeInput))
- alert(usr, "That wasnt a number! Value set to default ([initial(temp_pod.fallingSoundLength)*0.1]) instead.")
+ alert(usr, "That wasn't a number! Value set to default ([initial(temp_pod.fallingSoundLength)*0.1]) instead.")
temp_pod.fallingSound = soundInput
temp_pod.fallingSoundLength = 10 * timeInput
. = TRUE
@@ -369,7 +421,7 @@ force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.adm
if (temp_pod.soundVolume != initial(temp_pod.soundVolume))
temp_pod.soundVolume = initial(temp_pod.soundVolume)
return
- var/soundInput = input(holder, "Please pick a volume. Default is between 1 and 100 with 80 being average, but pick whatever. I'm a notification, not a cop. If you still cant hear your sound, consider turning on the Quiet effect. It will silence all pod sounds except for the custom admin ones set by the previous three buttons.", "Pick Admin Sound Volume") as null|num
+ var/soundInput = input(holder, "Please pick a volume. Default is between 1 and 100 with 50 being average, but pick whatever. I'm a notification, not a cop. If you still cant hear your sound, consider turning on the Quiet effect. It will silence all pod sounds except for the custom admin ones set by the previous three buttons.", "Pick Admin Sound Volume") as null|num
if (isnull(soundInput))
return
temp_pod.soundVolume = soundInput
@@ -421,26 +473,36 @@ force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.adm
. = TRUE
if("giveLauncher") //Enters the "Launch Mode". When the launcher is activated, temp_pod is cloned, and the result it filled and launched anywhere the user clicks (unless specificTarget is true)
launcherActivated = !launcherActivated
- updateCursor(launcherActivated) //Update the cursor of the user to a cool looking target icon
+ updateCursor(launcherActivated, FALSE) //Update the cursor of the user to a cool looking target icon
+ . = TRUE
+ if("clearBay") //Delete all mobs and objs in the selected bay
+ if(alert(usr, "This will delete all objs and mobs in [bay]. Are you sure?", "Confirmation", "Delete that shit", "No") == "Delete that shit")
+ clearBay()
+ refreshBay()
. = TRUE
/datum/centcom_podlauncher/ui_close() //Uses the destroy() proc. When the user closes the UI, we clean up the temp_pod and supplypod_selector variables.
qdel(src)
-/datum/centcom_podlauncher/proc/updateCursor(var/launching) //Update the moues of the user
- if (holder) //Check to see if we have a client
- if (launching) //If the launching param is true, we give the user new mouse icons.
- holder.mouse_up_icon = 'icons/effects/supplypod_target.dmi' //Icon for when mouse is released
- holder.mouse_down_icon = 'icons/effects/supplypod_down_target.dmi' //Icon for when mouse is pressed
- holder.mouse_pointer_icon = holder.mouse_up_icon //Icon for idle mouse (same as icon for when released)
- holder.click_intercept = src //Create a click_intercept so we know where the user is clicking
- else
- var/mob/M = holder.mob
- holder.mouse_up_icon = null
- holder.mouse_down_icon = null
- holder.click_intercept = null
- if (M)
- M.update_mouse_pointer() //set the moues icons to null, then call update_moues_pointer() which resets them to the correct values based on what the mob is doing (in a mech, holding a spell, etc)()
+/datum/centcom_podlauncher/proc/updateCursor(var/launching, var/turf_picking) //Update the mouse of the user
+ if (!holder) //Can't update the mouse icon if the client doesnt exist!
+ return
+ if (launching || turf_picking) //If the launching param is true, we give the user new mouse icons.
+ if(launching)
+ holder.mouse_up_icon = 'icons/effects/mouse_pointers/supplypod_target.dmi' //Icon for when mouse is released
+ holder.mouse_down_icon = 'icons/effects/mouse_pointers/supplypod_down_target.dmi' //Icon for when mouse is pressed
+ if(turf_picking)
+ holder.mouse_up_icon = 'icons/effects/mouse_pointers/supplypod_pickturf.dmi' //Icon for when mouse is released
+ holder.mouse_down_icon = 'icons/effects/mouse_pointers/supplypod_pickturf_down.dmi' //Icon for when mouse is pressed
+ holder.mouse_pointer_icon = holder.mouse_up_icon //Icon for idle mouse (same as icon for when released)
+ holder.click_intercept = src //Create a click_intercept so we know where the user is clicking
+ else
+ var/mob/M = holder.mob
+ holder.mouse_up_icon = null
+ holder.mouse_down_icon = null
+ holder.click_intercept = null
+ if (M)
+ M.update_mouse_pointer() //set the moues icons to null, then call update_moues_pointer() which resets them to the correct values based on what the mob is doing (in a mech, holding a spell, etc)()
/datum/centcom_podlauncher/proc/InterceptClickOn(user,params,atom/target) //Click Intercept so we know where to send pods where the user clicks
var/list/pa = params2list(params)
@@ -461,7 +523,7 @@ force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.adm
else
return //if target is null and we don't have a specific target, cancel
if (effectAnnounce)
- deadchat_broadcast("A special package is being launched at the station!", turf_target = target)
+ deadchat_broadcast("A special package is being launched at the station!", turf_target = target) //, message_type=DEADCHAT_ANNOUNCEMENT)
var/list/bouttaDie = list()
for (var/mob/living/M in target)
bouttaDie.Add(M)
@@ -479,6 +541,15 @@ force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.adm
else
launch(target) //If we couldn't locate an adjacent turf, just launch at the normal target
sleep(rand()*2) //looks cooler than them all appearing at once. Gives the impression of burst fire.
+ else if (picking_dropoff_turf)
+ //Clicking on UI elements shouldn't pick a dropoff turf
+ if(istype(target,/obj/screen))
+ return FALSE
+
+ . = TRUE
+ if(left_click) //When we left click:
+ dropoff_turf = get_turf(target)
+ to_chat(user, " You've selected [dropoff_turf] at [COORD(dropoff_turf)] as your dropoff location.")
/datum/centcom_podlauncher/proc/refreshBay() //Called whenever the bay is switched, as well as wheneber a pod is launched
orderedArea = createOrderedArea(bay) //Create an ordered list full of turfs form the bay
@@ -489,7 +560,7 @@ force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.adm
to_chat(holder.mob, "No /area/centcom/supplypod/loading/one (or /two or /three or /four) in the world! You can make one yourself (then refresh) for now, but yell at a mapper to fix this, today!")
CRASH("No /area/centcom/supplypod/loading/one (or /two or /three or /four) has been mapped into the centcom z-level!")
orderedArea = list()
- if (!isemptylist(A.contents)) //Go through the area passed into the proc, and figure out the top left and bottom right corners by calculating max and min values
+ if (length(A.contents)) //Go through the area passed into the proc, and figure out the top left and bottom right corners by calculating max and min values
var/startX = A.contents[1].x //Create the four values (we do it off a.contents[1] so they have some sort of arbitrary initial value. They should be overwritten in a few moments)
var/endX = A.contents[1].x
var/startY = A.contents[1].y
@@ -512,12 +583,12 @@ force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.adm
numTurfs = 0 //Counts the number of turfs that can be launched (remember, supplypods either launch all at once or one turf-worth of items at a time)
acceptableTurfs = list()
for (var/turf/T in orderedArea) //Go through the orderedArea list
- if (typecache_filter_list_reverse(T.contents, ignored_atoms).len != 0) //if there is something in this turf that isnt in the blacklist, we consider this turf "acceptable" and add it to the acceptableTurfs list
+ if (typecache_filter_list_reverse(T.contents, ignored_atoms).len != 0) //if there is something in this turf that isn't in the blacklist, we consider this turf "acceptable" and add it to the acceptableTurfs list
acceptableTurfs.Add(T) //Because orderedArea was an ordered linear list, acceptableTurfs will be as well.
numTurfs ++
launchList = list() //Anything in launchList will go into the supplypod when it is launched
- if (!isemptylist(acceptableTurfs) && !temp_pod.reversing && !temp_pod.effectMissile) //We dont fill the supplypod if acceptableTurfs is empty, if the pod is going in reverse (effectReverse=true), or if the pod is acitng like a missile (effectMissile=true)
+ if (length(acceptableTurfs) && !temp_pod.reversing && !temp_pod.effectMissile) //We dont fill the supplypod if acceptableTurfs is empty, if the pod is going in reverse (effectReverse=true), or if the pod is acitng like a missile (effectMissile=true)
switch(launchChoice)
if(0) //If we are launching all the turfs at once
for (var/turf/T in acceptableTurfs)
@@ -536,22 +607,36 @@ force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.adm
if (isnull(A))
return
var/obj/structure/closet/supplypod/centcompod/toLaunch = DuplicateObject(temp_pod) //Duplicate the temp_pod (which we have been varediting or configuring with the UI) and store the result
- toLaunch.bay = bay //Bay is currently a nonstatic expression, so it cant go into toLaunch using DuplicateObject
- toLaunch.update_icon()//we update_icon() here so that the door doesnt "flicker on" right after it lands
- if (launchClone) //We arent launching the actual items from the bay, rather we are creating clones and launching those
- for (var/atom/movable/O in launchList)
- DuplicateObject(O).forceMove(toLaunch) //Duplicate each atom/movable in launchList and forceMove them into the supplypod
- new /obj/effect/abstract/DPtarget(A, toLaunch) //Create the DPTarget, which will eventually forceMove the temp_pod to it's location
+ /*
+ if(dropoff_turf)
+ toLaunch.reverse_dropoff_turf = dropoff_turf
else
- for (var/atom/movable/O in launchList) //If we aren't cloning the objects, just go through the launchList
+ toLaunch.reverse_dropoff_turf = bay //Bay is currently a nonstatic expression, so it cant go into toLaunch using DuplicateObject
+ */
+ toLaunch.update_icon()//we update_icon() here so that the door doesnt "flicker on" right after it lands
+ // var/shippingLane = GLOB.areas_by_type[/area/centcom/supplypod/fly_me_to_the_moon]
+ // toLaunch.forceMove(shippingLane) The shipping lane is temporarily closed due to ratvarian blockades
+ if (launchClone) //We arent launching the actual items from the bay, rather we are creating clones and launching those
+ if(launchRandomItem)
+ var/atom/movable/O = pick_n_take(launchList)
+ DuplicateObject(O).forceMove(toLaunch) //Duplicate a single atom/movable from launchList and forceMove it into the supplypod
+ else
+ for (var/atom/movable/O in launchList)
+ DuplicateObject(O).forceMove(toLaunch) //Duplicate each atom/movable in launchList and forceMove them into the supplypod
+ else
+ if(launchRandomItem)
+ var/atom/movable/O = pick_n_take(launchList)
O.forceMove(toLaunch) //and forceMove any atom/moveable into the supplypod
- new /obj/effect/abstract/DPtarget(A, toLaunch) //Then, create the DPTarget effect, which will eventually forceMove the temp_pod to it's location
+ else
+ for (var/atom/movable/O in launchList) //If we aren't cloning the objects, just go through the launchList
+ O.forceMove(toLaunch) //and forceMove any atom/moveable into the supplypod
+ new /obj/effect/abstract/DPtarget(A, toLaunch) //Then, create the DPTarget effect, which will eventually forceMove the temp_pod to it's location
if (launchClone)
launchCounter++ //We only need to increment launchCounter if we are cloning objects.
//If we aren't cloning objects, taking and removing the first item each time from the acceptableTurfs list will inherently iterate through the list in order
/datum/centcom_podlauncher/proc/updateSelector() //Ensures that the selector effect will showcase the next item if needed
- if (launchChoice == 1 && !isemptylist(acceptableTurfs) && !temp_pod.reversing && !temp_pod.effectMissile) //We only show the selector if we are taking items from the bay
+ if (launchChoice == 1 && length(acceptableTurfs) && !temp_pod.reversing && !temp_pod.effectMissile) //We only show the selector if we are taking items from the bay
var/index = launchCounter + 1 //launchCounter acts as an index to the ordered acceptableTurfs list, so adding one will show the next item in the list
if (index > acceptableTurfs.len) //out of bounds check
index = 1
@@ -559,8 +644,14 @@ force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.adm
else
selector.moveToNullspace() //Otherwise, we move the selector to nullspace until it is needed again
+/datum/centcom_podlauncher/proc/clearBay() //Clear all objs and mobs from the selected bay
+ for (var/obj/O in bay.GetAllContents())
+ qdel(O)
+ for (var/mob/M in bay.GetAllContents())
+ qdel(M)
+
/datum/centcom_podlauncher/Destroy() //The Destroy() proc. This is called by ui_close proc, or whenever the user leaves the game
- updateCursor(FALSE) //Make sure our moues cursor resets to default. False means we are not in launch mode
+ updateCursor(FALSE, FALSE) //Make sure our moues cursor resets to default. False means we are not in launch mode
qdel(temp_pod) //Delete the temp_pod
qdel(selector) //Delete the selector effect
. = ..()
@@ -581,8 +672,8 @@ force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.adm
for (var/X in temp_pod.explosionSize)
explosionString += "[X]|"
- var/msg = "launched [podString][whomString].[delayString][damageString][explosionString]]"
- message_admins("[key_name_admin(usr)] [msg] in [AREACOORD(specificTarget)].")
- if (!isemptylist(whoDyin))
+ var/msg = "launched [podString] towards [whomString] [delayString][damageString][explosionString]"
+ message_admins("[key_name_admin(usr)] [msg] in [ADMIN_VERBOSEJMP(specificTarget)].")
+ if (length(whoDyin))
for (var/mob/living/M in whoDyin)
admin_ticket_log(M, "[key_name_admin(usr)] [msg]")
diff --git a/code/modules/cargo/console.dm b/code/modules/cargo/console.dm
index 8a438a1342..f5a8d21278 100644
--- a/code/modules/cargo/console.dm
+++ b/code/modules/cargo/console.dm
@@ -3,9 +3,6 @@
desc = "Used to order supplies, approve requests, and control the shuttle."
icon_screen = "supply"
circuit = /obj/item/circuitboard/computer/cargo
- req_access = list(ACCESS_CARGO)
- ui_x = 780
- ui_y = 750
var/requestonly = FALSE
var/contraband = FALSE
@@ -27,7 +24,6 @@
desc = "Used to request supplies from cargo."
icon_screen = "request"
circuit = /obj/item/circuitboard/computer/cargo/request
- req_access = list()
requestonly = TRUE
/obj/machinery/computer/cargo/Initialize()
@@ -66,15 +62,12 @@
var/obj/item/circuitboard/computer/cargo/board = circuit
board.contraband = TRUE
board.obj_flags |= EMAGGED
- req_access = list()
update_static_data(user)
- return ..()
-/obj/machinery/computer/cargo/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \
- datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state)
- ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+/obj/machinery/computer/cargo/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
- ui = new(user, src, ui_key, "Cargo", name, ui_x, ui_y, master_ui, state)
+ ui = new(user, src, "Cargo", name)
ui.open()
/obj/machinery/computer/cargo/ui_data()
@@ -120,7 +113,6 @@
var/list/data = list()
data["requestonly"] = requestonly
data["supplies"] = list()
- data["emagged"] = obj_flags & EMAGGED
for(var/pack in SSshuttle.supply_packs)
var/datum/supply_pack/P = SSshuttle.supply_packs[pack]
if(!data["supplies"][P.group])
@@ -135,8 +127,8 @@
"cost" = P.cost,
"id" = pack,
"desc" = P.desc || P.name, // If there is a description, use it. Otherwise use the pack's name.
+ "goody" = P.goody,
"private_goody" = P.goody == PACK_GOODY_PRIVATE,
- "goody" = P.goody == PACK_GOODY_PUBLIC,
"access" = P.access,
"can_private_buy" = P.can_private_buy
))
@@ -145,9 +137,6 @@
/obj/machinery/computer/cargo/ui_act(action, params, datum/tgui/ui)
if(..())
return
- if(!allowed(usr))
- to_chat(usr, "Access denied.")
- return
switch(action)
if("send")
if(!SSshuttle.supply.canMove())
@@ -179,6 +168,8 @@
else
SSshuttle.shuttle_loan.loan_shuttle()
say("The supply shuttle has been loaned to CentCom.")
+ investigate_log("[key_name(usr)] accepted a shuttle loan event.", INVESTIGATE_CARGO)
+ log_game("[key_name(usr)] accepted a shuttle loan event.")
. = TRUE
if("add")
var/id = text2path(params["id"])
@@ -200,13 +191,15 @@
rank = "Silicon"
var/datum/bank_account/account
- if(self_paid)
- if(!pack.can_private_buy && !(obj_flags & EMAGGED))
- return
- var/obj/item/card/id/id_card = usr.get_idcard(TRUE)
+ if(self_paid && ishuman(usr))
+ var/mob/living/carbon/human/H = usr
+ var/obj/item/card/id/id_card = H.get_idcard(TRUE)
if(!istype(id_card))
say("No ID card detected.")
return
+ if(istype(id_card, /obj/item/card/id/departmental_budget))
+ say("The [src] rejects [id_card].")
+ return
account = id_card.registered_account
if(!istype(account))
say("Invalid bank account.")
@@ -241,6 +234,9 @@
SSshuttle.shoppinglist += SO
if(self_paid)
say("Order processed. The price will be charged to [account.account_holder]'s bank account on delivery.")
+ if(requestonly && message_cooldown < world.time)
+ radio.talk_into(src, "A new order has been requested.", RADIO_CHANNEL_SUPPLY)
+ message_cooldown = world.time + 30 SECONDS
. = TRUE
if("remove")
var/id = text2num(params["id"])
diff --git a/code/modules/cargo/exports/gear.dm b/code/modules/cargo/exports/gear.dm
index 646e1c6e47..678948128f 100644
--- a/code/modules/cargo/exports/gear.dm
+++ b/code/modules/cargo/exports/gear.dm
@@ -310,7 +310,7 @@
/datum/export/gear/combatgloves
cost = 80
unit_name = "combat gloves"
- export_types = list(/obj/item/clothing/gloves/tackler/combat, /obj/item/clothing/gloves/tackler/dolphin, /obj/item/clothing/gloves/fingerless/pugilist/rapid, /obj/item/clothing/gloves/krav_maga)
+ export_types = list(/obj/item/clothing/gloves/tackler/combat, /obj/item/clothing/gloves/tackler/dolphin, /obj/item/clothing/gloves/krav_maga)
include_subtypes = TRUE
/datum/export/gear/bonegloves
diff --git a/code/modules/cargo/exports/organs_robotics.dm b/code/modules/cargo/exports/organs_robotics.dm
index 7f54e675ca..b65cf28949 100644
--- a/code/modules/cargo/exports/organs_robotics.dm
+++ b/code/modules/cargo/exports/organs_robotics.dm
@@ -2,11 +2,11 @@
/datum/export/robotics
include_subtypes = FALSE
- k_elasticity = 1/50
+ k_elasticity = 1/200
/datum/export/implant
include_subtypes = FALSE
- k_elasticity = 1/50
+ k_elasticity = 1/200
/datum/export/organs
include_subtypes = TRUE
@@ -34,8 +34,8 @@
export_types = list(/obj/item/organ/cyberimp/brain/anti_stun)
/datum/export/implant/breathtube
- cost = 150
- k_elasticity = 300/20 //Large before depleating
+ cost = 175
+ k_elasticity = 1/350 //Large before depleating
unit_name = "breath implant"
export_types = list(/obj/item/organ/cyberimp/mouth/breathing_tube)
@@ -71,35 +71,35 @@
export_types = list(/obj/item/organ/cyberimp/arm/gun/laser, /obj/item/organ/cyberimp/arm/gun/taser, /obj/item/organ/cyberimp/arm/esword, /obj/item/organ/cyberimp/arm/medibeam, /obj/item/organ/cyberimp/arm/combat, /obj/item/organ/cyberimp/arm/flash, /obj/item/organ/cyberimp/arm/baton)
include_subtypes = TRUE
-/datum/export/orgains/heart
+/datum/export/organs/heart
cost = 250
unit_name = "heart"
export_types = list(/obj/item/organ/heart)
exclude_types = list(/obj/item/organ/heart/cursed, /obj/item/organ/heart/cybernetic)
-/datum/export/orgains/tongue
+/datum/export/organs/tongue
cost = 75
unit_name = "tongue"
export_types = list(/obj/item/organ/tongue)
-/datum/export/orgains/eyes
+/datum/export/organs/eyes
cost = 50 //So many things take your eyes out anyways
unit_name = "eyes"
export_types = list(/obj/item/organ/eyes)
exclude_types = list(/obj/item/organ/eyes/robotic)
-/datum/export/orgains/stomach
+/datum/export/organs/stomach
cost = 50 //can be replaced
unit_name = "stomach"
export_types = list(/obj/item/organ/stomach)
-/datum/export/orgains/lungs
+/datum/export/organs/lungs
cost = 150
unit_name = "lungs"
export_types = list(/obj/item/organ/lungs)
exclude_types = list(/obj/item/organ/lungs/cybernetic, /obj/item/organ/lungs/cybernetic/upgraded)
-/datum/export/orgains/liver
+/datum/export/organs/liver
cost = 175
unit_name = "liver"
export_types = list(/obj/item/organ/liver)
@@ -116,33 +116,33 @@
unit_name = "upgraded cybernetic organ"
export_types = list(/obj/item/organ/lungs/cybernetic/upgraded, /obj/item/organ/liver/cybernetic/upgraded)
-/datum/export/organs/tail //Shhh
+/datum/export/organs/tail // yeah have fun pulling this off someone without catching a bwoink
cost = 500
- unit_name = "error shipment failer"
+ unit_name = "organic tail"
export_types = list(/obj/item/organ/tail)
-/datum/export/orgains/vocal_cords
+/datum/export/organs/vocal_cords
cost = 500
unit_name = "vocal cords"
export_types = list(/obj/item/organ/vocal_cords) //These are gotten via different races
-/datum/export/robotics/lims
- cost = 30
- unit_name = "robotic lim replacement"
+/datum/export/robotics/limbs
+ cost = 60
+ unit_name = "robotic limb replacement"
export_types = list(/obj/item/bodypart/l_arm/robot, /obj/item/bodypart/r_arm/robot, /obj/item/bodypart/l_leg/robot, /obj/item/bodypart/r_leg/robot, /obj/item/bodypart/chest/robot, /obj/item/bodypart/head/robot)
/datum/export/robotics/surpluse
- cost = 40
- unit_name = "robotic lim replacement"
+ cost = 50
+ unit_name = "robotic limb replacement"
export_types = list(/obj/item/bodypart/l_arm/robot/surplus, /obj/item/bodypart/r_arm/robot/surplus, /obj/item/bodypart/l_leg/robot/surplus, /obj/item/bodypart/r_leg/robot/surplus)
/datum/export/robotics/surplus_upgraded
- cost = 50
- unit_name = "upgraded robotic lim replacement"
+ cost = 80
+ unit_name = "upgraded robotic limb replacement"
export_types = list(/obj/item/bodypart/l_arm/robot/surplus_upgraded, /obj/item/bodypart/r_arm/robot/surplus_upgraded, /obj/item/bodypart/l_leg/robot/surplus_upgraded, /obj/item/bodypart/r_leg/robot/surplus_upgraded)
/datum/export/robotics/surgery_gear_basic
- cost = 10
+ cost = 50
unit_name = "surgery tool"
export_types = list(/obj/item/retractor, /obj/item/hemostat, /obj/item/cautery, /obj/item/surgicaldrill, /obj/item/scalpel, /obj/item/circular_saw, /obj/item/bonesetter, /obj/item/surgical_drapes)
diff --git a/code/modules/cargo/exports/parts.dm b/code/modules/cargo/exports/parts.dm
index da3c0cf31d..3e52780d44 100644
--- a/code/modules/cargo/exports/parts.dm
+++ b/code/modules/cargo/exports/parts.dm
@@ -102,6 +102,7 @@
export_types = list(/obj/item/stock_parts/cell/high/slime/hypercharged)
//Glass working stuff
+// i'd just like to say how i despise the previous coder's fetish for their funny glasswork
/datum/export/glasswork_dish
cost = 300
diff --git a/code/modules/cargo/exports/sheets.dm b/code/modules/cargo/exports/sheets.dm
index a562210164..b0676fbde2 100644
--- a/code/modules/cargo/exports/sheets.dm
+++ b/code/modules/cargo/exports/sheets.dm
@@ -138,11 +138,11 @@
message = "of bones"
export_types = list(/obj/item/stack/sheet/bone)
-/datum/export/stack/bronze
+/datum/export/stack/sheet/bronze
unit_name = "tiles"
cost = 5
message = "of brozne"
- export_types = list(/obj/item/stack/tile/bronze)
+ export_types = list(/obj/item/stack/sheet/bronze)
/datum/export/stack/brass
unit_name = "tiles"
diff --git a/code/modules/cargo/exports/weapons.dm b/code/modules/cargo/exports/weapons.dm
index 983348a358..dc2703c146 100644
--- a/code/modules/cargo/exports/weapons.dm
+++ b/code/modules/cargo/exports/weapons.dm
@@ -5,7 +5,7 @@
/datum/export/weapon/makeshift_shield
cost = 30
- unit_name = "unknown shield"
+ unit_name = "nonstandard shield"
export_types = list(/obj/item/shield/riot, /obj/item/shield/riot/roman, /obj/item/shield/riot/buckler, /obj/item/shield/makeshift)
/datum/export/weapon/riot_shield
@@ -37,7 +37,7 @@
/datum/export/weapon/taser
cost = 200
- unit_name = "advanced taser"
+ unit_name = "hybrid taser"
export_types = list(/obj/item/gun/energy/e_gun/advtaser)
/datum/export/weapon/laser
@@ -104,12 +104,12 @@
/datum/export/weapon/aeg
cost = 200 //Endless power
- unit_name = "advance engery gun"
+ unit_name = "advanced energy gun"
export_types = list(/obj/item/gun/energy/e_gun/nuclear)
/datum/export/weapon/deconer
cost = 600
- unit_name = "deconer"
+ unit_name = "decloner"
export_types = list(/obj/item/gun/energy/decloner)
/datum/export/weapon/ntsniper
@@ -123,9 +123,8 @@
export_types = list(/obj/item/gun/syringe/rapidsyringe)
/datum/export/weapon/temp_gun
- cost = 175 //Its just smaller
+ cost = 175
unit_name = "small temperature gun"
- k_elasticity = 1/30 //Its just a smaller temperature gun, easy to mass make
export_types = list(/obj/item/gun/energy/temperature)
/datum/export/weapon/flowergun
@@ -139,8 +138,7 @@
export_types = list(/obj/item/gun/energy/xray)
/datum/export/weapon/ioncarbine
- cost = 200
- k_elasticity = 1/30 //Its just a smaller temperature gun, easy to mass make
+ cost = 200
unit_name = "ion carbine"
export_types = list(/obj/item/gun/energy/ionrifle/carbine)
@@ -194,7 +192,7 @@
export_types = list(/obj/item/firing_pin/test_range)
/datum/export/weapon/techslug
- cost = 25
+ cost = 30
k_elasticity = 0
unit_name = "advanced shotgun shell"
export_types = list(/obj/item/ammo_casing/shotgun/dragonsbreath, /obj/item/ammo_casing/shotgun/meteorslug, /obj/item/ammo_casing/shotgun/pulseslug, /obj/item/ammo_casing/shotgun/frag12, /obj/item/ammo_casing/shotgun/ion, /obj/item/ammo_casing/shotgun/laserslug)
@@ -215,7 +213,7 @@
/datum/export/weapon/bow_teaching
cost = 500
- unit_name = "stone tablets"
+ unit_name = "bowyery tablet"
export_types = list(/obj/item/book/granter/crafting_recipe/bone_bow)
/datum/export/weapon/quiver
@@ -230,48 +228,48 @@
/datum/export/weapon/pistol
cost = 120
- unit_name = "illegal firearm"
+ unit_name = "nonstandard sidearm"
export_types = list(/obj/item/gun/ballistic/automatic/pistol)
/datum/export/weapon/revolver
cost = 200
- unit_name = "large handgun"
+ unit_name = "large-caliber revolver"
export_types = list(/obj/item/gun/ballistic/revolver)
exclude_types = list(/obj/item/gun/ballistic/revolver/russian, /obj/item/gun/ballistic/revolver/doublebarrel)
/datum/export/weapon/rocketlauncher
cost = 1000
- unit_name = "rocketlauncher"
+ unit_name = "PML-9 rocket-propelled grenade launcher"
export_types = list(/obj/item/gun/ballistic/rocketlauncher)
/datum/export/weapon/antitank
cost = 300
- unit_name = "hand cannon"
+ unit_name = "anti-tank pistol"
export_types = list(/obj/item/gun/ballistic/automatic/pistol/antitank/syndicate)
/datum/export/weapon/clownstuff
cost = 500
- unit_name = "clown war tech"
- export_types = list(/obj/item/pneumatic_cannon/pie/selfcharge, /obj/item/shield/energy/bananium, /obj/item/melee/transforming/energy/sword/bananium, )
+ unit_name = "clown combat equipment"
+ export_types = list(/obj/item/pneumatic_cannon/pie/selfcharge, /obj/item/shield/energy/bananium, /obj/item/melee/transforming/energy/sword/bananium)
/datum/export/weapon/bulldog
cost = 400
- unit_name = "drum loaded shotgun"
+ unit_name = "drum-fed compact combat shotgun"
export_types = list(/obj/item/gun/ballistic/automatic/shotgun/bulldog)
/datum/export/weapon/smg
cost = 350
- unit_name = "automatic c-20r"
+ unit_name = "C-20r sub-machine gun"
export_types = list(/obj/item/gun/ballistic/automatic/c20r)
/datum/export/weapon/duelsaber
- cost = 360 //Get it?
- unit_name = "energy saber"
+ cost = 360
+ unit_name = "double-bladed energy saber"
export_types = list(/obj/item/dualsaber)
/datum/export/weapon/esword
cost = 130
- unit_name = "energy sword"
+ unit_name = "energy saber"
export_types = list(/obj/item/melee/transforming/energy/sword/cx/traitor, /obj/item/melee/transforming/energy/sword/saber)
/datum/export/weapon/rapier
@@ -286,32 +284,32 @@
/datum/export/weapon/gloves
cost = 90
- unit_name = "star struck gloves"
+ unit_name = "anomalous armwraps"
export_types = list(/obj/item/clothing/gloves/fingerless/pugilist/rapid)
/datum/export/weapon/l6
cost = 500
- unit_name = "law 6 saw"
+ unit_name = "Aussec Armory L6 SAW"
export_types = list(/obj/item/gun/ballistic/automatic/l6_saw)
/datum/export/weapon/m90
cost = 400
- unit_name = "assault class weapon"
+ unit_name = "M90-gl carbine"
export_types = list(/obj/item/gun/ballistic/automatic/m90)
/datum/export/weapon/powerglove
cost = 100
- unit_name = "hydraulic glove"
+ unit_name = "pneumatic gauntlet"
export_types = list(/obj/item/melee/powerfist)
/datum/export/weapon/sniper
cost = 750
- unit_name = ".50 sniper"
+ unit_name = "anti-materiel rifle"
export_types = list(/obj/item/gun/ballistic/automatic/sniper_rifle/syndicate)
/datum/export/weapon/ebow
cost = 600
- unit_name = "mini crossbow"
+ unit_name = "compact energy crossbow"
export_types = list(/obj/item/gun/energy/kinetic_accelerator/crossbow)
/datum/export/weapon/m10mm
@@ -333,12 +331,12 @@
/datum/export/weapon/smg_mag
cost = 45
- unit_name = "smg magazine"
+ unit_name = "SMG/carbine magazine"
export_types = list(/obj/item/ammo_box/magazine/smgm45, /obj/item/ammo_box/magazine/m556)
/datum/export/weapon/l6sawammo
cost = 60
- unit_name = "law 6 saw ammo box"
+ unit_name = "L6 SAW ammo box"
export_types = list(/obj/item/ammo_box/magazine/mm195x129)
include_subtypes = TRUE
@@ -355,13 +353,13 @@
/datum/export/weapon/fletcher_ammo
cost = 60
- unit_name = "illegal ammo magazines"
+ unit_name = "flechette launcher magazine"
export_types = list(/obj/item/ammo_box/magazine/flechette)
include_subtypes = TRUE
/datum/export/weapon/dj_a_pizzabomb
cost = -6000
- unit_name = "Repair Costs"
+ unit_name = "undeclared ordinance and subsequent repair costs"
export_types = list(/obj/item/pizzabox/bomb, /obj/item/sbeacondrop/bomb)
/datum/export/weapon/real_toolbox
@@ -371,12 +369,12 @@
/datum/export/weapon/melee
cost = 50
- unit_name = "unlisted weapon"
+ unit_name = "any other melee weapon"
export_types = list(/obj/item/melee)
include_subtypes = TRUE
/datum/export/weapon/gun
cost = 50
- unit_name = "unlisted weapon"
+ unit_name = "any other weapon"
export_types = list(/obj/item/gun)
include_subtypes = TRUE
diff --git a/code/modules/cargo/expressconsole.dm b/code/modules/cargo/expressconsole.dm
index dc7e4b5a06..4ca97a13a5 100644
--- a/code/modules/cargo/expressconsole.dm
+++ b/code/modules/cargo/expressconsole.dm
@@ -1,5 +1,5 @@
#define MAX_EMAG_ROCKETS 8
-#define BEACON_COST 5000
+#define BEACON_COST 500
#define SP_LINKED 1
#define SP_READY 2
#define SP_LAUNCH 3
@@ -13,10 +13,9 @@
All sales are near instantaneous - please choose carefully"
icon_screen = "supply_express"
circuit = /obj/item/circuitboard/computer/cargo/express
- ui_x = 600
- ui_y = 700
blockade_warning = "Bluespace instability detected. Delivery impossible."
req_access = list(ACCESS_QM)
+
var/message
var/printed_beacons = 0 //number of beacons printed. Used to determine beacon names.
var/list/meme_pack_data
@@ -42,7 +41,7 @@
to_chat(user, "You [locked ? "lock" : "unlock"] the interface.")
return
else if(istype(W, /obj/item/disk/cargo/bluespace_pod))
- podType = /obj/structure/closet/supplypod/bluespacepod
+ podType = /obj/structure/closet/supplypod/bluespacepod //doesnt effect circuit board, making reversal possible
to_chat(user, "You insert the disk into [src], allowing for advanced supply delivery vehicles.")
qdel(W)
return TRUE
@@ -52,22 +51,20 @@
sb.link_console(src, user)
return TRUE
else
- to_chat(user, "[src] is already linked to [sb].")
+ to_chat(user, "[src] is already linked to [sb].")
..()
/obj/machinery/computer/cargo/express/emag_act(mob/living/user)
- . = SEND_SIGNAL(src, COMSIG_ATOM_EMAG_ACT)
if(obj_flags & EMAGGED)
return
- user.visible_message("[user] swipes a suspicious card through [src]!",
- "You change the routing protocols, allowing the Supply Pod to land anywhere on the station.")
+ if(user)
+ user.visible_message("[user] swipes a suspicious card through [src]!",
+ "You change the routing protocols, allowing the Supply Pod to land anywhere on the station.")
obj_flags |= EMAGGED
// This also sets this on the circuit board
var/obj/item/circuitboard/computer/cargo/board = circuit
board.obj_flags |= EMAGGED
packin_up()
- req_access = list()
- return TRUE
/obj/machinery/computer/cargo/express/proc/packin_up() // oh shit, I'm sorry
meme_pack_data = list() // sorry for what?
@@ -89,10 +86,10 @@
"desc" = P.desc || P.name // If there is a description, use it. Otherwise use the pack's name.
))
-/obj/machinery/computer/cargo/express/ui_interact(mob/living/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) // Remember to use the appropriate state.
- ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+/obj/machinery/computer/cargo/express/ui_interact(mob/living/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
- ui = new(user, src, ui_key, "CargoExpress", name, ui_x, ui_y, master_ui, state)
+ ui = new(user, src, "CargoExpress", name)
ui.open()
/obj/machinery/computer/cargo/express/ui_data(mob/user)
@@ -131,9 +128,6 @@
return data
/obj/machinery/computer/cargo/express/ui_act(action, params, datum/tgui/ui)
- if(!allowed(usr))
- to_chat(usr, "Access denied.")
- return
switch(action)
if("LZCargo")
usingBeacon = FALSE
@@ -153,6 +147,7 @@
printed_beacons++//printed_beacons starts at 0, so the first one out will be called beacon # 1
beacon.name = "Supply Pod Beacon #[printed_beacons]"
+
if("add")//Generate Supply Order first
var/id = text2path(params["id"])
var/datum/supply_pack/pack = SSshuttle.supply_packs[id]
@@ -195,7 +190,6 @@
LZ = pick(empty_turfs)
if (SO.pack.cost <= points_to_check && LZ)//we need to call the cost check again because of the CHECK_TICK call
D.adjust_money(-SO.pack.cost)
- SSblackbox.record_feedback("nested tally", "cargo_imports", 1, list("[SO.pack.cost]", "[SO.pack.name]"))
new /obj/effect/abstract/DPtarget(LZ, podType, SO)
. = TRUE
update_icon()
@@ -209,7 +203,7 @@
CHECK_TICK
if(empty_turfs && empty_turfs.len)
D.adjust_money(-(SO.pack.cost * (0.72*MAX_EMAG_ROCKETS)))
- SSblackbox.record_feedback("nested tally", "cargo_imports", MAX_EMAG_ROCKETS, list("[SO.pack.cost * 0.72]", "[SO.pack.name]"))
+
SO.generateRequisition(get_turf(src))
for(var/i in 1 to MAX_EMAG_ROCKETS)
var/LZ = pick(empty_turfs)
diff --git a/code/modules/cargo/packs/armory.dm b/code/modules/cargo/packs/armory.dm
index 835457536f..9f8bb2f25f 100644
--- a/code/modules/cargo/packs/armory.dm
+++ b/code/modules/cargo/packs/armory.dm
@@ -158,11 +158,10 @@
/datum/supply_pack/security/armory/russian
name = "Russian Surplus Crate"
- desc = "Hello Comrade, we have the most modern russian military equipment the black market can offer, for the right price of course. Sadly we couldnt remove the lock so it requires Armory access to open."
+ desc = "Hello Comrade, we have the most modern Russian military equipment the black market can offer, for the right price of course. Sadly we couldn't remove the lock so it requires Armory access to open."
cost = 7500
contraband = TRUE
contains = list(/obj/item/reagent_containers/food/snacks/rationpack,
- /obj/item/ammo_box/magazine/m10mm/rifle,
/obj/item/clothing/suit/armor/vest/russian,
/obj/item/clothing/head/helmet/rus_helmet,
/obj/item/clothing/shoes/russian,
@@ -172,7 +171,10 @@
/obj/item/clothing/mask/russian_balaclava,
/obj/item/clothing/head/helmet/rus_ushanka,
/obj/item/clothing/suit/armor/vest/russian_coat,
- /obj/item/gun/ballistic/automatic/surplus)
+ /obj/effect/spawner/bundle/crate/mosin,
+ /obj/item/storage/toolbox/ammo,
+ /obj/effect/spawner/bundle/crate/surplusrifle,
+ /obj/item/storage/toolbox/ammo/surplus)
crate_name = "surplus military crate"
/datum/supply_pack/security/armory/russian/fill(obj/structure/closet/crate/C)
@@ -223,3 +225,10 @@
/obj/item/ammo_box/magazine/wt550m9/wtrubber,
/obj/item/ammo_box/magazine/wt550m9/wtrubber)
crate_name = "auto rifle ammo crate"
+
+/datum/supply_pack/security/armory/hell_single
+ name = "Hellgun Single-Pack"
+ crate_name = "hellgun crate"
+ desc = "Contains one hellgun, an old pattern of laser gun infamous for its ability to horribly disfigure targets with burns. Technically violates the Space Geneva Convention when used on humanoids."
+ cost = 1500
+ contains = list(/obj/item/gun/energy/laser/hellgun)
diff --git a/code/modules/cargo/packs/goodies.dm b/code/modules/cargo/packs/goodies.dm
index 2ff02d0473..423d2fc743 100644
--- a/code/modules/cargo/packs/goodies.dm
+++ b/code/modules/cargo/packs/goodies.dm
@@ -52,8 +52,8 @@
cost = 300
contains = list(/obj/item/storage/toolbox/mechanical)
-/datum/supply_pack/goody/electrical_toolbox // mostly just to water down coupon probability
- name = "Mechanical Toolbox"
+/datum/supply_pack/goody/electrical_toolbox
+ name = "Electrical Toolbox"
desc = "A fully stocked electrical toolbox, for when you're too lazy to just print them out."
cost = 300
contains = list(/obj/item/storage/toolbox/electrical)
@@ -76,8 +76,8 @@
cost = 200
contains = list(/obj/item/toy/beach_ball)
-/datum/supply_pack/goody/hell_single
- name = "Hellgun Single-Pack"
- desc = "Contains one hellgun, an old pattern of laser gun infamous for its ability to horribly disfigure targets with burns. Technically violates the Space Geneva Convention when used on humanoids."
- cost = 1500
- contains = list(/obj/item/gun/energy/laser/hellgun)
+/datum/supply_pack/goody/medipen_twopak
+ name = "Medipen Two-Pak"
+ desc = "Contains one standard epinephrine medipen and one standard emergency first-aid kit medipen. For when you want to prepare for the worst."
+ cost = 500
+ contains = list(/obj/item/reagent_containers/hypospray/medipen, /obj/item/reagent_containers/hypospray/medipen/ekit)
diff --git a/code/modules/cargo/packs/medical.dm b/code/modules/cargo/packs/medical.dm
index 5eacc2f583..6a4165f840 100644
--- a/code/modules/cargo/packs/medical.dm
+++ b/code/modules/cargo/packs/medical.dm
@@ -218,3 +218,18 @@
/obj/item/storage/box/beakers)
crate_name = "virus containment unit crate"
crate_type = /obj/structure/closet/crate/secure/plasma
+
+/datum/supply_pack/medical/medipen_variety
+ name = "Medipen Variety-Pak"
+ desc = "Contains eight different medipens in three different varieties, to assist in quickly treating seriously injured patients."
+ cost = 2000
+ contains = list(/obj/item/reagent_containers/hypospray/medipen/,
+ /obj/item/reagent_containers/hypospray/medipen/,
+ /obj/item/reagent_containers/hypospray/medipen/ekit,
+ /obj/item/reagent_containers/hypospray/medipen/ekit,
+ /obj/item/reagent_containers/hypospray/medipen/ekit,
+ /obj/item/reagent_containers/hypospray/medipen/blood_loss,
+ /obj/item/reagent_containers/hypospray/medipen/blood_loss,
+ /obj/item/reagent_containers/hypospray/medipen/blood_loss)
+
+ crate_name = "medipen crate"
diff --git a/code/modules/cargo/packs/misc.dm b/code/modules/cargo/packs/misc.dm
index a84e22f6f9..394b86bb81 100644
--- a/code/modules/cargo/packs/misc.dm
+++ b/code/modules/cargo/packs/misc.dm
@@ -13,6 +13,13 @@
//////////////////// Paperwork and Writing Supplies //////////////////////////
//////////////////////////////////////////////////////////////////////////////
+
+/datum/supply_pack/misc/anvil
+ name = "Anvil Crate"
+ desc = "An anvil in a crate, we had to dig this out of the old warehouse. It's got wheels on it so you can move it."
+ cost = 7500
+ contains = list(/obj/structure/anvil/obtainable/basic)
+
/datum/supply_pack/misc/artsupply
name = "Art Supplies"
desc = "Make some happy little accidents with six canvasses, two easels, two boxes of crayons, and a rainbow crayon!"
@@ -194,9 +201,9 @@
/datum/supply_pack/misc/dirtymags
name = "Dirty Magazines"
- desc = "Get your mind out of the gutter operative, you have work to do. Three items per order. Possible Results: .357 Speedloaders, Kitchen Gun Mags, Stetchkin Mags."
+ desc = "Get your mind out of the gutter operative, you have work to do. Three items per order. Possible Results: .357 Speedloaders, Kitchen Gun patented magazines, or Stetchkin magazines."
hidden = TRUE
- cost = 12000
+ cost = 4000
var/num_contained = 3
contains = list(/obj/item/ammo_box/a357,
/obj/item/ammo_box/magazine/pistolm9mm,
@@ -415,21 +422,10 @@
/obj/item/restraints/handcuffs/fake/kinky,
/obj/item/clothing/head/kitty/genuine, // Why its illegal
/obj/item/clothing/head/kitty/genuine,
- /obj/item/storage/pill_bottle/penis_enlargement,
- /obj/structure/reagent_dispensers/keg/aphro)
+ /obj/item/storage/pill_bottle/penis_enlargement)
crate_name = "lewd kit"
crate_type = /obj/structure/closet/crate
-/datum/supply_pack/misc/lewdkeg
- name = "Lewd Deluxe Keg"
- desc = "That other stuff not getting you ready? Well I have a Chemslut making tons of the good stuff."
- cost = 7500 //It can be a weapon
- contraband = TRUE
- contains = list(/obj/structure/reagent_dispensers/keg/aphro/strong)
- crate_name = "deluxe keg"
- crate_type = /obj/structure/closet/crate
-
-
///Special supply crate that generates random syndicate gear up to a determined TC value
/datum/supply_pack/misc/syndicate
@@ -466,4 +462,4 @@
if(crate_value < I.cost)
continue
crate_value -= I.cost
- new I.item(C)
\ No newline at end of file
+ new I.item(C)
diff --git a/code/modules/cargo/packs/science.dm b/code/modules/cargo/packs/science.dm
index fd6fee362d..125bfe2034 100644
--- a/code/modules/cargo/packs/science.dm
+++ b/code/modules/cargo/packs/science.dm
@@ -192,3 +192,49 @@
crate_type = /obj/structure/closet/crate/secure/science
dangerous = TRUE
+//////// RAW ANOMALY CORES
+
+/datum/supply_pack/science/raw_flux_anomaly
+ name = "Raw Flux Anomaly"
+ desc = "The raw core of a flux anomaly, ready to be implosion-compressed into a powerful artifact."
+ cost = 5000
+ access = ACCESS_TOX
+ contains = list(/obj/item/raw_anomaly_core/flux)
+ crate_name = "raw flux anomaly"
+ crate_type = /obj/structure/closet/crate/secure/science
+
+/datum/supply_pack/science/raw_grav_anomaly
+ name = "Raw Gravitational Anomaly"
+ desc = "The raw core of a gravitational anomaly, ready to be implosion-compressed into a powerful artifact."
+ cost = 5000
+ access = ACCESS_TOX
+ contains = list(/obj/item/raw_anomaly_core/grav)
+ crate_name = "raw pyro anomaly"
+ crate_type = /obj/structure/closet/crate/secure/science
+
+/datum/supply_pack/science/raw_vortex_anomaly
+ name = "Raw Vortex Anomaly"
+ desc = "The raw core of a vortex anomaly, ready to be implosion-compressed into a powerful artifact."
+ cost = 5000
+ access = ACCESS_TOX
+ contains = list(/obj/item/raw_anomaly_core/vortex)
+ crate_name = "raw vortex anomaly"
+ crate_type = /obj/structure/closet/crate/secure/science
+
+/datum/supply_pack/science/raw_bluespace_anomaly
+ name = "Raw Bluespace Anomaly"
+ desc = "The raw core of a bluespace anomaly, ready to be implosion-compressed into a powerful artifact."
+ cost = 5000
+ access = ACCESS_TOX
+ contains = list(/obj/item/raw_anomaly_core/bluespace)
+ crate_name = "raw bluespace anomaly"
+ crate_type = /obj/structure/closet/crate/secure/science
+
+/datum/supply_pack/science/raw_pyro_anomaly
+ name = "Raw Pyro Anomaly"
+ desc = "The raw core of a pyro anomaly, ready to be implosion-compressed into a powerful artifact."
+ cost = 5000
+ access = ACCESS_TOX
+ contains = list(/obj/item/raw_anomaly_core/pyro)
+ crate_name = "raw pyro anomaly"
+ crate_type = /obj/structure/closet/crate/secure/science
diff --git a/code/modules/cargo/packs/security.dm b/code/modules/cargo/packs/security.dm
index 738eb03fbf..cf9cc5e0d1 100644
--- a/code/modules/cargo/packs/security.dm
+++ b/code/modules/cargo/packs/security.dm
@@ -100,7 +100,7 @@
crate_name = "surplus russian clothing"
crate_type = /obj/structure/closet/crate/internals
-/datum/supply_pack/security/russianmosin
+/datum/supply_pack/security/russian_partisan
name = "Russian Partisan Gear"
desc = "An old russian partisan equipment crate, comes with a full russian outfit, a loaded surplus rifle and a second magazine."
contraband = TRUE
@@ -112,12 +112,17 @@
/obj/item/clothing/suit/armor/bulletproof,
/obj/item/clothing/head/helmet/alt,
/obj/item/clothing/gloves/tackler/combat/insulated,
- /obj/item/clothing/mask/gas,
- /obj/item/ammo_box/magazine/m10mm/rifle,
- /obj/item/gun/ballistic/automatic/surplus)
+ /obj/item/clothing/mask/gas)
crate_name = "surplus russian gear"
crate_type = /obj/structure/closet/crate/internals
+/datum/supply_pack/security/russian_partisan/fill(obj/structure/closet/crate/C)
+ ..()
+ if(prob(20))
+ new /obj/effect/spawner/bundle/crate/mosin(C)
+ else
+ new /obj/effect/spawner/bundle/crate/surplusrifle(C)
+
/datum/supply_pack/security/sechardsuit
name = "Sec Hardsuit"
desc = "One Sec Hardsuit with a small air tank and mask."
diff --git a/code/modules/client/client_defines.dm b/code/modules/client/client_defines.dm
index ae48fddfb4..d5e1e91478 100644
--- a/code/modules/client/client_defines.dm
+++ b/code/modules/client/client_defines.dm
@@ -75,11 +75,12 @@
var/inprefs = FALSE
var/list/topiclimiter
+
+ ///Used for limiting the rate of clicks sends by the client to avoid abuse
var/list/clicklimiter
- var/datum/chatOutput/chatOutput
-
- var/list/credits //lazy list of all credit object bound to this client
+ ///lazy list of all credit object bound to this client
+ var/list/credits
var/datum/player_details/player_details //these persist between logins/logouts during the same round.
diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm
index 5c9b1eec2e..72250fa8f2 100644
--- a/code/modules/client/client_procs.dm
+++ b/code/modules/client/client_procs.dm
@@ -20,7 +20,9 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
When somebody clicks a link in game, this Topic is called first.
It does the stuff in this proc and then is redirected to the Topic() proc for the src=[0xWhatever]
(if specified in the link). ie locate(hsrc).Topic()
+
Such links can be spoofed.
+
Because of this certain things MUST be considered whenever adding a Topic() for something:
- Can it be fed harmful values which could cause runtimes?
- Is the Topic call an admin-only thing?
@@ -38,9 +40,10 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
var/asset_cache_job
if(href_list["asset_cache_confirm_arrival"])
asset_cache_job = asset_cache_confirm_arrival(href_list["asset_cache_confirm_arrival"])
- if(!asset_cache_job)
+ if (!asset_cache_job)
return
+ // Rate limiting
var/mtl = CONFIG_GET(number/minute_topic_limit)
if (!holder && mtl)
var/minute = round(world.time, 600)
@@ -98,15 +101,18 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
keyUp(keycode)
return
+ // Tgui Topic middleware
+ if(tgui_Topic(href_list))
+ return
+
// Admin PM
if(href_list["priv_msg"])
cmd_admin_pm(href_list["priv_msg"],null)
return
- // CITADEL Start - Mentor PM
+ // Mentor PM (cit.)
if (citadel_client_procs(href_list))
return
- // CITADEL End
switch(href_list["_src_"])
if("holder")
@@ -114,7 +120,7 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
if("usr")
hsrc = mob
if("mentor") // CITADEL
- hsrc = mentor_datum // CITADEL END
+ hsrc = mentor_datum
if("prefs")
if (inprefs)
return
@@ -124,8 +130,6 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
return
if("vars")
return view_var_Topic(href,href_list,hsrc)
- if("chat")
- return chatOutput.Topic(href, href_list)
switch(href_list["action"])
if("openLink")
@@ -142,7 +146,6 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
to_chat(src, "Become a BYOND member to access member-perks and features, as well as support the engine that makes this game possible. Only 10 bucks for 3 months! Click Here to find out more.")
return 0
return 1
-
/*
* Call back proc that should be checked in all paths where a client can send messages
*
@@ -205,14 +208,10 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
///////////
//CONNECT//
///////////
-#if (PRELOAD_RSC == 0)
-GLOBAL_LIST_EMPTY(external_rsc_urls)
-#endif
/client/New(TopicData)
- world.SetConfig("APP/admin", ckey, "role=admin") //CITADEL EDIT - Allows admins to reboot in OOM situations
+ world.SetConfig("APP/admin", ckey, "role=admin")
var/tdata = TopicData //save this for later use
- chatOutput = new /datum/chatOutput(src)
TopicData = null //Prevent calls to client.Topic from connect
if(connection != "seeker" && connection != "web")//Invalid connection type.
@@ -221,6 +220,9 @@ GLOBAL_LIST_EMPTY(external_rsc_urls)
GLOB.clients += src
GLOB.directory[ckey] = src
+ // Instantiate tgui panel
+ tgui_panel = new(src)
+
GLOB.ahelp_tickets.ClientLogin(src)
var/connecting_admin = FALSE //because de-admined admins connecting should be treated like admins.
//Admin Authorisation
@@ -261,7 +263,6 @@ GLOBAL_LIST_EMPTY(external_rsc_urls)
new /datum/admins(localhost_rank, ckey, 1, 1)
//preferences datum - also holds some persistent data for the client (because we may as well keep these datums to a minimum)
prefs = GLOB.preferences_datums[ckey]
-
if(prefs)
prefs.parent = src
else
@@ -271,7 +272,7 @@ GLOBAL_LIST_EMPTY(external_rsc_urls)
prefs.last_ip = address //these are gonna be used for banning
prefs.last_id = computer_id //these are gonna be used for banning
- fps = prefs.clientfps
+ fps = prefs.clientfps //(prefs.clientfps < 0) ? RECOMMENDED_FPS : prefs.clientfps
if(fexists(roundend_report_file()))
verbs += /client/proc/show_previous_roundend_report
@@ -296,22 +297,26 @@ GLOBAL_LIST_EMPTY(external_rsc_urls)
alert_mob_dupe_login = TRUE
if(matches)
if(C)
- message_admins("Notice: [key_name_admin(src)] has the same [matches] as [key_name_admin(C)].")
- log_access("Notice: [key_name(src)] has the same [matches] as [key_name(C)].")
+ message_admins("Notice: [key_name_admin(src)] has the same [matches] as [key_name_admin(C)].")
+ log_admin_private("Notice: [key_name(src)] has the same [matches] as [key_name(C)].")
else
- message_admins("Notice: [key_name_admin(src)] has the same [matches] as [key_name_admin(C)] (no longer logged in). ")
- log_access("Notice: [key_name(src)] has the same [matches] as [key_name(C)] (no longer logged in).")
+ message_admins("Notice: [key_name_admin(src)] has the same [matches] as [key_name_admin(C)] (no longer logged in). ")
+ log_admin_private("Notice: [key_name(src)] has the same [matches] as [key_name(C)] (no longer logged in).")
if(GLOB.player_details[ckey])
player_details = GLOB.player_details[ckey]
player_details.byond_version = full_version
else
- player_details = new
+ player_details = new(ckey)
player_details.byond_version = full_version
GLOB.player_details[ckey] = player_details
. = ..() //calls mob.Login()
+ // if (length(GLOB.stickybanadminexemptions))
+ // GLOB.stickybanadminexemptions -= ckey
+ // if (!length(GLOB.stickybanadminexemptions))
+ // restore_stickybans()
if (byond_version >= 512)
if (!byond_build || byond_build < 1386)
@@ -331,7 +336,12 @@ GLOBAL_LIST_EMPTY(external_rsc_urls)
qdel(src)
return
- chatOutput.start() // Starts the chat
+ // if(SSinput.initialized) placed here on tg.
+ // set_macros()
+ // update_movement_keys()
+
+ // Initialize tgui panel
+ tgui_panel.initialize()
if(alert_mob_dupe_login)
spawn()
@@ -342,12 +352,13 @@ GLOBAL_LIST_EMPTY(external_rsc_urls)
connection_timeofday = world.timeofday
winset(src, null, "command=\".configure graphics-hwmode on\"")
var/cev = CONFIG_GET(number/client_error_version)
+ var/ceb = CONFIG_GET(number/client_error_build)
var/cwv = CONFIG_GET(number/client_warn_version)
- if (byond_version < cev) //Out of date client.
+ if (byond_version < cev || (byond_version == cev && byond_build < ceb)) //Out of date client.
to_chat(src, "Your version of BYOND is too old:")
to_chat(src, CONFIG_GET(string/client_error_message))
- to_chat(src, "Your version: [byond_version]")
- to_chat(src, "Required version: [cev] or later")
+ to_chat(src, "Your version: [byond_version].[byond_build]")
+ to_chat(src, "Required version: [cev].[ceb] or later")
to_chat(src, "Visit BYOND's website to get the latest version of BYOND.")
if (connecting_admin)
to_chat(src, "Because you are an admin, you are being allowed to walk past this limitation, But it is still STRONGLY suggested you upgrade")
@@ -459,6 +470,10 @@ GLOBAL_LIST_EMPTY(external_rsc_urls)
if (menuitem)
menuitem.Load_checked(src)
+ // view_size = new(src, getScreenSize(prefs.widescreenpref))
+ // view_size.resetFormat()
+ // view_size.setZoomMode()
+ // fit_viewport()
Master.UpdateTickRate()
/client/proc/ensure_keys_set()
@@ -472,13 +487,17 @@ GLOBAL_LIST_EMPTY(external_rsc_urls)
/client/Del()
if(!gc_destroyed)
- Destroy()
+ Destroy() //Clean up signals and timers.
return ..()
/client/Destroy()
+ GLOB.clients -= src
+ GLOB.directory -= ckey
+ log_access("Logout: [key_name(src)]")
+ GLOB.ahelp_tickets.ClientLogout(src)
+ // SSserver_maint.UpdateHubStatus()
if(credits)
QDEL_LIST(credits)
- log_access("Logout: [key_name(src)]")
if(holder)
adminGreet(1)
holder.owner = null
@@ -500,16 +519,13 @@ GLOBAL_LIST_EMPTY(external_rsc_urls)
)
send2irc("Server", "[cheesy_message] (No admins online)")
-
- GLOB.ahelp_tickets.ClientLogout(src)
- GLOB.directory -= ckey
- GLOB.clients -= src
QDEL_LIST_ASSOC_VAL(char_render_holders)
if(movingmob != null)
movingmob.client_mobs_in_contents -= mob
UNSETEMPTY(movingmob.client_mobs_in_contents)
+ // seen_messages = null
Master.UpdateTickRate()
- . = ..()
+ . = ..() //Even though we're going to be hard deleted there are still some things that want to know the destroy is happening
return QDEL_HINT_HARDDEL_NOW
/client/proc/set_client_age_from_db(connectiontopic)
@@ -611,6 +627,9 @@ GLOBAL_LIST_EMPTY(external_rsc_urls)
var/datum/DBQuery/query_log_connection = SSdbcore.NewQuery("INSERT INTO `[format_table_name("connection_log")]` (`id`,`datetime`,`server_ip`,`server_port`,`round_id`,`ckey`,`ip`,`computerid`) VALUES(null,Now(),INET_ATON(IF('[world.internet_address]' LIKE '', '0', '[world.internet_address]')),'[world.port]','[GLOB.round_id]','[sql_ckey]',INET_ATON('[sql_ip]'),'[sql_computerid]')")
query_log_connection.Execute()
qdel(query_log_connection)
+
+ // SSserver_maint.UpdateHubStatus()
+
if(new_player)
player_age = -1
. = player_age
@@ -852,40 +871,31 @@ GLOBAL_LIST_EMPTY(external_rsc_urls)
return inactivity
return FALSE
-//send resources to the client. It's here in its own proc so we can move it around easiliy if need be
+/// Send resources to the client.
+/// Sends both game resources and browser assets.
/client/proc/send_resources()
#if (PRELOAD_RSC == 0)
var/static/next_external_rsc = 0
- if(GLOB.external_rsc_urls && GLOB.external_rsc_urls.len)
- next_external_rsc = WRAP(next_external_rsc+1, 1, GLOB.external_rsc_urls.len+1)
- preload_rsc = GLOB.external_rsc_urls[next_external_rsc]
+ var/list/external_rsc_urls = CONFIG_GET(keyed_list/external_rsc_urls)
+ if(length(external_rsc_urls))
+ next_external_rsc = WRAP(next_external_rsc+1, 1, external_rsc_urls.len+1)
+ preload_rsc = external_rsc_urls[next_external_rsc]
#endif
- //get the common files
- getFiles(
- 'html/search.js',
- 'html/panels.css',
- 'html/browser/common.css',
- 'html/browser/scannernew.css',
- 'html/browser/playeroptions.css',
- )
+
spawn (10) //removing this spawn causes all clients to not get verbs.
//load info on what assets the client has
src << browse('code/modules/asset_cache/validate_assets.html', "window=asset_cache_browser")
//Precache the client with all other assets slowly, so as to not block other browse() calls
- getFilesSlow(src, SSassets.preload, register_asset = FALSE)
- addtimer(CALLBACK(GLOBAL_PROC, /proc/getFilesSlow, src, SSassets.preload, FALSE), 5 SECONDS)
+ if (CONFIG_GET(flag/asset_simple_preload))
+ addtimer(CALLBACK(SSassets.transport, /datum/asset_transport.proc/send_assets_slow, src, SSassets.transport.preload), 5 SECONDS)
#if (PRELOAD_RSC == 0)
for (var/name in GLOB.vox_sounds)
var/file = GLOB.vox_sounds[name]
Export("##action=load_rsc", file)
stoplag()
- for (var/name in GLOB.vox_sounds_male)
- var/file = GLOB.vox_sounds_male[name]
- Export("##action=load_rsc", file)
- stoplag()
#endif
@@ -988,3 +998,6 @@ GLOBAL_LIST_EMPTY(external_rsc_urls)
screen -= S
qdel(S)
char_render_holders = null
+
+/client/proc/can_have_part(part_name)
+ return prefs.pref_species.mutant_bodyparts[part_name] || (part_name in GLOB.unlocked_mutant_parts)
diff --git a/code/modules/client/darkmode.dm b/code/modules/client/darkmode.dm
deleted file mode 100644
index f806e5c964..0000000000
--- a/code/modules/client/darkmode.dm
+++ /dev/null
@@ -1,65 +0,0 @@
-//Darkmode preference by Kmc2000//
-
-/*
-This lets you switch chat themes by using winset and CSS loading, you must relog to see this change (or rebuild your browseroutput datum)
-Things to note:
-If you change ANYTHING in interface/skin.dmf you need to change it here:
-Format:
-winset(src, "window as appears in skin.dmf after elem", "var to change = desired value")
-How this works:
-I've added a function to browseroutput.js which registers a cookie for darkmode and swaps the chat accordingly. You can find the button to do this under the "cog" icon next to the ping button (top right of chat)
-This then swaps the window theme automatically
-Thanks to spacemaniac and mcdonald for help with the JS side of this.
-*/
-
-/client/proc/force_white_theme() //There's no way round it. We're essentially changing the skin by hand. It's painful but it works, and is the way Lummox suggested.
- //Main windows
- winset(src, "infowindow", "background-color = [COLOR_WHITEMODE_DARKBACKGROUND];text-color = [COLOR_WHITEMODE_TEXT]")
- winset(src, "info", "background-color = [COLOR_WHITEMODE_BACKGROUND];text-color = [COLOR_WHITEMODE_TEXT]")
- winset(src, "browseroutput", "background-color = [COLOR_WHITEMODE_DARKBACKGROUND];text-color = [COLOR_WHITEMODE_TEXT]")
- winset(src, "outputwindow", "background-color = [COLOR_WHITEMODE_DARKBACKGROUND];text-color = [COLOR_WHITEMODE_TEXT]")
- winset(src, "mainwindow", "background-color = [COLOR_WHITEMODE_DARKBACKGROUND]")
- winset(src, "split", "background-color = [COLOR_WHITEMODE_BACKGROUND]")
- //Buttons
- winset(src, "changelog", "background-color = [COLOR_WHITEMODE_INFO_BUTTONS_BG];text-color = [COLOR_WHITEMODE_TEXT]")
- winset(src, "rules", "background-color = [COLOR_WHITEMODE_INFO_BUTTONS_BG];text-color = [COLOR_WHITEMODE_TEXT]")
- winset(src, "wiki", "background-color = [COLOR_WHITEMODE_INFO_BUTTONS_BG];text-color = [COLOR_WHITEMODE_TEXT]")
- winset(src, "forum", "background-color = [COLOR_WHITEMODE_INFO_BUTTONS_BG];text-color = [COLOR_WHITEMODE_TEXT]")
- winset(src, "github", "background-color = [COLOR_WHITEMODE_INFO_BUTTONS_BG];text-color = [COLOR_WHITEMODE_TEXT]")
- winset(src, "report-issue", "background-color = [COLOR_WHITEMODE_ISSUE_BUTTON_BG];text-color = [COLOR_WHITEMODE_TEXT]")
- //Status and verb tabs
- winset(src, "output", "background-color = [COLOR_WHITEMODE_BACKGROUND];text-color = [COLOR_WHITEMODE_TEXT]")
- winset(src, "statwindow", "background-color = [COLOR_WHITEMODE_DARKBACKGROUND];text-color = [COLOR_WHITEMODE_TEXT]")
- winset(src, "stat", "background-color = [COLOR_WHITEMODE_BACKGROUND];tab-background-color = [COLOR_WHITEMODE_DARKBACKGROUND];\
- text-color = [COLOR_WHITEMODE_TEXT];tab-text-color = [COLOR_WHITEMODE_TEXT];\
- prefix-color = [COLOR_WHITEMODE_TEXT];suffix-color = [COLOR_WHITEMODE_TEXT]")
- //Etc.
- winset(src, "say", "background-color = [COLOR_WHITEMODE_DARKBACKGROUND];text-color = [COLOR_WHITEMODE_TEXT]")
- winset(src, "asset_cache_browser", "background-color = [COLOR_WHITEMODE_DARKBACKGROUND];text-color = [COLOR_WHITEMODE_TEXT]")
- winset(src, "tooltip", "background-color = [COLOR_WHITEMODE_BACKGROUND];text-color = [COLOR_WHITEMODE_TEXT]")
-
-/client/proc/force_dark_theme() //Inversely, if theyre using white theme and want to swap to the superior dark theme, let's get WINSET() ing
- //Main windows
- winset(src, "infowindow", "background-color = [COLOR_DARKMODE_DARKBACKGROUND];text-color = [COLOR_DARKMODE_TEXT]")
- winset(src, "info", "background-color = [COLOR_DARKMODE_BACKGROUND];text-color = [COLOR_DARKMODE_TEXT]")
- winset(src, "browseroutput", "background-color = [COLOR_DARKMODE_BACKGROUND];text-color = [COLOR_DARKMODE_TEXT]")
- winset(src, "outputwindow", "background-color = [COLOR_DARKMODE_BACKGROUND];text-color = [COLOR_DARKMODE_TEXT]")
- winset(src, "mainwindow", "background-color = [COLOR_DARKMODE_DARKBACKGROUND]")
- winset(src, "split", "background-color = [COLOR_DARKMODE_BACKGROUND]")
- //Buttons
- winset(src, "changelog", "background-color = [COLOR_DARKMODE_INFO_BUTTONS_BG];text-color = [COLOR_DARKMODE_TEXT]")
- winset(src, "rules", "background-color = [COLOR_DARKMODE_INFO_BUTTONS_BG];text-color = [COLOR_DARKMODE_TEXT]")
- winset(src, "wiki", "background-color = [COLOR_DARKMODE_INFO_BUTTONS_BG];text-color = [COLOR_DARKMODE_TEXT]")
- winset(src, "forum", "background-color = [COLOR_DARKMODE_INFO_BUTTONS_BG];text-color = [COLOR_DARKMODE_TEXT]")
- winset(src, "github", "background-color = [COLOR_DARKMODE_INFO_BUTTONS_BG];text-color = [COLOR_DARKMODE_TEXT]")
- winset(src, "report-issue", "background-color = [COLOR_DARKMODE_ISSUE_BUTTON_BG];text-color = [COLOR_DARKMODE_TEXT]")
- //Status and verb tabs
- winset(src, "output", "background-color = [COLOR_DARKMODE_BACKGROUND];text-color = [COLOR_DARKMODE_TEXT]")
- winset(src, "statwindow", "background-color = [COLOR_DARKMODE_DARKBACKGROUND];text-color = [COLOR_DARKMODE_TEXT]")
- winset(src, "stat", "background-color = [COLOR_DARKMODE_DARKBACKGROUND];tab-background-color = [COLOR_DARKMODE_BACKGROUND];\
- text-color = [COLOR_DARKMODE_TEXT];tab-text-color = [COLOR_DARKMODE_TEXT];\
- prefix-color = [COLOR_DARKMODE_TEXT];suffix-color = [COLOR_DARKMODE_TEXT]")
- //Etc.
- winset(src, "say", "background-color = [COLOR_DARKMODE_BACKGROUND];text-color = [COLOR_DARKMODE_TEXT]")
- winset(src, "asset_cache_browser", "background-color = [COLOR_DARKMODE_BACKGROUND];text-color = [COLOR_DARKMODE_TEXT]")
- winset(src, "tooltip", "background-color = [COLOR_DARKMODE_BACKGROUND];text-color = [COLOR_DARKMODE_TEXT]")
\ No newline at end of file
diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm
index 75e914ea68..cff3040926 100644
--- a/code/modules/client/preferences.dm
+++ b/code/modules/client/preferences.dm
@@ -40,7 +40,6 @@ GLOBAL_LIST_EMPTY(preferences_datums)
//If it's 0, that's good, if it's anything but 0, the owner of this prefs file's antag choices were,
//autocorrected this round, not that you'd need to check that.
-
var/UI_style = null
var/buttons_locked = FALSE
var/hotkeys = FALSE
@@ -164,6 +163,8 @@ GLOBAL_LIST_EMPTY(preferences_datums)
)
var/custom_speech_verb = "default" //if your say_mod is to be something other than your races
var/custom_tongue = "default" //if your tongue is to be something other than your races
+ var/modified_limbs = list() //prosthetic/amputated limbs
+ var/chosen_limb_id //body sprite selected to load for the users limbs, null means default, is sanitized when loaded
/// Security record note section
var/security_records
@@ -230,7 +231,8 @@ GLOBAL_LIST_EMPTY(preferences_datums)
var/gear_points = 10
var/list/gear_categories
var/list/chosen_gear = list()
- var/gear_tab
+ var/gear_category
+ var/gear_subcategory
var/screenshake = 100
var/damagescreenshake = 2
@@ -246,6 +248,8 @@ GLOBAL_LIST_EMPTY(preferences_datums)
/// Which of the 5 persistent scar slots we randomly roll to load for this round, if enabled. Actually rolled in [/datum/preferences/proc/load_character(slot)]
var/scars_index = 1
+ var/hide_ckey = FALSE //pref for hiding if your ckey shows round-end or not
+
/datum/preferences/New(client/C)
parent = C
@@ -370,6 +374,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
dat += "[medical_records]"
else
dat += "[TextPreview(medical_records)]... "
+ dat += " Hide ckey: [hide_ckey ? "Enabled" : "Disabled"] "
dat += "
"
//Character Appearance
@@ -425,12 +430,19 @@ GLOBAL_LIST_EMPTY(preferences_datums)
dat += "Gender:[gender == MALE ? "Male" : (gender == FEMALE ? "Female" : (gender == PLURAL ? "Non-binary" : "Object"))] "
if(gender != NEUTER && pref_species.sexes)
dat += "Body Model:[features["body_model"] == MALE ? "Masculine" : "Feminine"] "
+ dat += "Limb Modification: "
+ dat += "Modify Limbs "
+ for(var/modification in modified_limbs)
+ if(modified_limbs[modification][1] == LOADOUT_LIMB_PROSTHETIC)
+ dat += "[modification]: [modified_limbs[modification][2]] "
+ else
+ dat += "[modification]: [modified_limbs[modification][1]] "
+ dat += " "
dat += "Species:[pref_species.name] "
dat += "Custom Species Name:[custom_species ? custom_species : "None"] "
dat += "Random Body:Randomize! "
dat += "Always Random Body:[be_random_body ? "Yes" : "No"] "
dat += " Cycle background:[bgstate] "
-
var/use_skintones = pref_species.use_skintones
if(use_skintones)
dat += APPEARANCE_CATEGORY_COLUMN
@@ -507,310 +519,25 @@ GLOBAL_LIST_EMPTY(preferences_datums)
dat += ""
mutant_category = 0
- if(pref_species.mutant_bodyparts["tail_lizard"])
- if(!mutant_category)
- dat += APPEARANCE_CATEGORY_COLUMN
-
- dat += "
Tail
"
-
- dat += "[features["tail_lizard"]]"
-
- mutant_category++
- if(mutant_category >= MAX_MUTANT_ROWS)
- dat += ""
- mutant_category = 0
-
- if(pref_species.mutant_bodyparts["mam_tail"])
- if(!mutant_category)
- dat += APPEARANCE_CATEGORY_COLUMN
-
- dat += "
Tail
"
-
- dat += "[features["mam_tail"]]"
-
- mutant_category++
- if(mutant_category >= MAX_MUTANT_ROWS)
- dat += ""
- mutant_category = 0
- if(pref_species.mutant_bodyparts["tail_human"])
- if(!mutant_category)
- dat += APPEARANCE_CATEGORY_COLUMN
-
- dat += "
Tail
"
-
- dat += "[features["tail_human"]]"
-
- mutant_category++
- if(mutant_category >= MAX_MUTANT_ROWS)
- dat += ""
- mutant_category = 0
-
- if(pref_species.mutant_bodyparts["meat_type"])
- if(!mutant_category)
- dat += APPEARANCE_CATEGORY_COLUMN
-
- dat += "
Meat Type
"
-
- dat += "[features["meat_type"]]"
-
- mutant_category++
- if(mutant_category >= MAX_MUTANT_ROWS)
- dat += ""
- mutant_category = 0
- if(pref_species.mutant_bodyparts["snout"])
- if(!mutant_category)
- dat += APPEARANCE_CATEGORY_COLUMN
-
- dat += "
Snout
"
-
- dat += "[features["snout"]]"
-
- mutant_category++
- if(mutant_category >= MAX_MUTANT_ROWS)
- dat += ""
- mutant_category = 0
- if(pref_species.mutant_bodyparts["horns"])
- if(!mutant_category)
- dat += APPEARANCE_CATEGORY_COLUMN
-
- dat += "
Horns
"
-
- dat += "[features["horns"]]"
- dat += "Change "
-
-
- mutant_category++
- if(mutant_category >= MAX_MUTANT_ROWS)
- dat += ""
- if(pref_species.mutant_bodyparts["frills"])
- if(!mutant_category)
- dat += APPEARANCE_CATEGORY_COLUMN
-
- dat += "
Frills
"
-
- dat += "[features["frills"]]"
-
- mutant_category++
- if(mutant_category >= MAX_MUTANT_ROWS)
- dat += ""
- mutant_category = 0
-
- if(pref_species.mutant_bodyparts["spines"])
- if(!mutant_category)
- dat += APPEARANCE_CATEGORY_COLUMN
-
- dat += "
Spines
"
-
- dat += "[features["spines"]]"
-
- mutant_category++
- if(mutant_category >= MAX_MUTANT_ROWS)
- dat += ""
- mutant_category = 0
-
- if(pref_species.mutant_bodyparts["body_markings"])
- if(!mutant_category)
- dat += APPEARANCE_CATEGORY_COLUMN
-
- dat += "
Body Markings
"
-
- dat += "[features["body_markings"]]"
-
- mutant_category++
- if(mutant_category >= MAX_MUTANT_ROWS)
- dat += ""
- mutant_category = 0
- if(pref_species.mutant_bodyparts["mam_body_markings"])
- if(!mutant_category)
- dat += APPEARANCE_CATEGORY_COLUMN
-
- dat += "
Species Markings
"
-
- dat += "[features["mam_body_markings"]]"
-
- mutant_category++
- if(mutant_category >= MAX_MUTANT_ROWS)
- dat += ""
-
- if(pref_species.mutant_bodyparts["mam_ears"])
- if(!mutant_category)
- dat += APPEARANCE_CATEGORY_COLUMN
-
- dat += "
Ears
"
-
- dat += "[features["mam_ears"]]"
-
- mutant_category++
- if(mutant_category >= MAX_MUTANT_ROWS)
- dat += ""
- mutant_category = 0
-
- if(pref_species.mutant_bodyparts["ears"])
- if(!mutant_category)
- dat += APPEARANCE_CATEGORY_COLUMN
-
- dat += "
Ears
"
-
- dat += "[features["ears"]]"
-
- mutant_category++
- if(mutant_category >= MAX_MUTANT_ROWS)
- dat += ""
- mutant_category = 0
-
- if(pref_species.mutant_bodyparts["mam_snouts"])
- if(!mutant_category)
- dat += APPEARANCE_CATEGORY_COLUMN
-
- dat += "
Snout
"
-
- dat += "[features["mam_snouts"]]"
-
- mutant_category++
- if(mutant_category >= MAX_MUTANT_ROWS)
- dat += ""
- mutant_category = 0
- if(pref_species.mutant_bodyparts["legs"])
- if(!mutant_category)
- dat += APPEARANCE_CATEGORY_COLUMN
-
- dat += "
Legs
"
-
- dat += "[features["legs"]]"
-
- mutant_category++
- if(mutant_category >= MAX_MUTANT_ROWS)
- dat += ""
- mutant_category = 0
- if(pref_species.mutant_bodyparts["deco_wings"])
- if(!mutant_category)
- dat += APPEARANCE_CATEGORY_COLUMN
-
- dat += "
Decorative wings
"
-
- dat += "[features["deco_wings"]]"
- dat += "Change "
-
- if(pref_species.mutant_bodyparts["insect_wings"])
- if(!mutant_category)
- dat += APPEARANCE_CATEGORY_COLUMN
-
- dat += "
Insect wings
"
-
- dat += "[features["insect_wings"]]"
- dat += "Change "
- mutant_category++
- if(mutant_category >= MAX_MUTANT_ROWS)
- dat += ""
- mutant_category = 0
- if(pref_species.mutant_bodyparts["insect_fluff"])
- if(!mutant_category)
- dat += APPEARANCE_CATEGORY_COLUMN
-
- dat += "
Insect Fluff
"
-
- dat += "[features["insect_fluff"]]"
- mutant_category++
- if(mutant_category >= MAX_MUTANT_ROWS)
- dat += ""
- mutant_category = 0
- if(pref_species.mutant_bodyparts["taur"])
- if(!mutant_category)
- dat += APPEARANCE_CATEGORY_COLUMN
-
- dat += "
Tauric Body
"
-
- dat += "[features["taur"]]"
-
- if(pref_species.mutant_bodyparts["insect_markings"])
- if(!mutant_category)
- dat += APPEARANCE_CATEGORY_COLUMN
-
- dat += "
"
dat += " "
if(5) // Custom keybindings
@@ -1489,6 +1243,9 @@ GLOBAL_LIST_EMPTY(preferences_datums)
for(var/V in all_quirks)
var/datum/quirk/T = SSquirks.quirks[V]
bal -= initial(T.value)
+ for(var/modification in modified_limbs)
+ if(modified_limbs[modification][1] == LOADOUT_LIMB_PROSTHETIC)
+ return bal + 1 //max 1 point regardless of how many prosthetics
return bal
/datum/preferences/proc/GetPositiveQuirkCount()
@@ -1704,6 +1461,11 @@ GLOBAL_LIST_EMPTY(preferences_datums)
if(!isnull(msg))
features["ooc_notes"] = msg
+ if("hide_ckey")
+ hide_ckey = !hide_ckey
+ if(user)
+ user.mind?.hide_ckey = hide_ckey
+
if("hair")
var/new_hair = input(user, "Choose your character's hair colour:", "Character Preference","#"+hair_color) as color|null
if(new_hair)
@@ -1741,6 +1503,29 @@ GLOBAL_LIST_EMPTY(preferences_datums)
if("cycle_bg")
bgstate = next_list_item(bgstate, bgstate_options)
+ if("modify_limbs")
+ var/limb_type = input(user, "Choose the limb to modify:", "Character Preference") as null|anything in LOADOUT_ALLOWED_LIMB_TARGETS
+ if(limb_type)
+ var/modification_type = input(user, "Choose the modification to the limb:", "Character Preference") as null|anything in LOADOUT_LIMBS
+ if(modification_type)
+ if(modification_type == LOADOUT_LIMB_PROSTHETIC)
+ var/prosthetic_type = input(user, "Choose the type of prosthetic", "Character Preference") as null|anything in (list("prosthetic") + GLOB.prosthetic_limb_types)
+ if(prosthetic_type)
+ var/number_of_prosthetics = 0
+ for(var/modification in modified_limbs)
+ if(modified_limbs[modification][1] == LOADOUT_LIMB_PROSTHETIC)
+ number_of_prosthetics += 1
+ if(number_of_prosthetics >= MAXIMUM_LOADOUT_PROSTHETICS && !(limb_type in modified_limbs && modified_limbs[limb_type][1] == LOADOUT_LIMB_PROSTHETIC))
+ to_chat(user, "You can only have up to two prosthetic limbs!")
+ else
+ //save the actual prosthetic data
+ modified_limbs[limb_type] = list(modification_type, prosthetic_type)
+ else
+ if(modification_type == LOADOUT_LIMB_NORMAL)
+ modified_limbs -= limb_type
+ else
+ modified_limbs[limb_type] = list(modification_type)
+
if("underwear")
var/new_underwear = input(user, "Choose your character's underwear:", "Character Preference") as null|anything in GLOB.underwear_list
if(new_underwear)
@@ -1783,14 +1568,14 @@ GLOBAL_LIST_EMPTY(preferences_datums)
pref_species = new newtype()
//let's ensure that no weird shit happens on species swapping.
custom_species = null
- if(!pref_species.mutant_bodyparts["body_markings"])
+ if(!parent.can_have_part("body_markings"))
features["body_markings"] = "None"
- if(!pref_species.mutant_bodyparts["mam_body_markings"])
+ if(!parent.can_have_part("mam_body_markings"))
features["mam_body_markings"] = "None"
- if(pref_species.mutant_bodyparts["mam_body_markings"])
+ if(parent.can_have_part("mam_body_markings"))
if(features["mam_body_markings"] == "None")
features["mam_body_markings"] = "Plain"
- if(pref_species.mutant_bodyparts["tail_lizard"])
+ if(parent.can_have_part("tail_lizard"))
features["tail_lizard"] = "Smooth"
if(pref_species.id == "felinid")
features["mam_tail"] = "Cat"
@@ -1902,7 +1687,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
if("mam_tail")
var/list/snowflake_tails_list = list()
for(var/path in GLOB.mam_tails_list)
- var/datum/sprite_accessory/mam_tails/instance = GLOB.mam_tails_list[path]
+ var/datum/sprite_accessory/tails/mam_tails/instance = GLOB.mam_tails_list[path]
if(istype(instance, /datum/sprite_accessory))
var/datum/sprite_accessory/S = instance
if(!show_mismatched_markings && S.recommended_species && !S.recommended_species.Find(pref_species.id))
@@ -1918,7 +1703,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
features["tail_human"] = "None"
features["tail_lizard"] = "None"
- if("meats")
+ if("meat_type")
var/new_meat
new_meat = input(user, "Choose your character's meat type:", "Character Preference") as null|anything in GLOB.meat_types
if(new_meat)
@@ -1927,7 +1712,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
if("snout")
var/list/snowflake_snouts_list = list()
for(var/path in GLOB.snouts_list)
- var/datum/sprite_accessory/mam_snouts/instance = GLOB.snouts_list[path]
+ var/datum/sprite_accessory/snouts/mam_snouts/instance = GLOB.snouts_list[path]
if(istype(instance, /datum/sprite_accessory))
var/datum/sprite_accessory/S = instance
if(!show_mismatched_markings && S.recommended_species && !S.recommended_species.Find(pref_species.id))
@@ -1944,7 +1729,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
if("mam_snouts")
var/list/snowflake_mam_snouts_list = list()
for(var/path in GLOB.mam_snouts_list)
- var/datum/sprite_accessory/mam_snouts/instance = GLOB.mam_snouts_list[path]
+ var/datum/sprite_accessory/snouts/mam_snouts/instance = GLOB.mam_snouts_list[path]
if(istype(instance, /datum/sprite_accessory))
var/datum/sprite_accessory/S = instance
if(!show_mismatched_markings && S.recommended_species && !S.recommended_species.Find(pref_species.id))
@@ -2023,7 +1808,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
if(new_deco_wings)
features["deco_wings"] = new_deco_wings
- if("insect_fluffs")
+ if("insect_fluff")
var/new_insect_fluff
new_insect_fluff = input(user, "Choose your character's wings:", "Character Preference") as null|anything in GLOB.insect_fluffs_list
if(new_insect_fluff)
@@ -2093,7 +1878,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
if("mam_ears")
var/list/snowflake_ears_list = list()
for(var/path in GLOB.mam_ears_list)
- var/datum/sprite_accessory/mam_ears/instance = GLOB.mam_ears_list[path]
+ var/datum/sprite_accessory/ears/mam_ears/instance = GLOB.mam_ears_list[path]
if(istype(instance, /datum/sprite_accessory))
var/datum/sprite_accessory/S = instance
if(!show_mismatched_markings && S.recommended_species && !S.recommended_species.Find(pref_species.id))
@@ -2171,7 +1956,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
if("cock_shape")
var/new_shape
var/list/hockeys = list()
- if(pref_species.mutant_bodyparts["taur"])
+ if(parent.can_have_part("taur"))
var/datum/sprite_accessory/taur/T = GLOB.taur_list[features["taur"]]
for(var/A in GLOB.cock_shapes_list)
var/datum/sprite_accessory/penis/P = GLOB.cock_shapes_list[A]
@@ -2358,8 +2143,6 @@ GLOBAL_LIST_EMPTY(preferences_datums)
else
features["body_model"] = chosengender
gender = chosengender
- facial_hair_style = random_facial_hair_style(gender)
- hair_style = random_hair_style(gender)
if("body_size")
var/min = CONFIG_GET(number/body_size_min)
@@ -2385,6 +2168,11 @@ GLOBAL_LIST_EMPTY(preferences_datums)
var/selected_custom_speech_verb = input(user, "Choose your desired speech verb (none means your species speech verb)", "Character Preference") as null|anything in GLOB.speech_verbs
if(selected_custom_speech_verb)
custom_speech_verb = selected_custom_speech_verb
+
+ if("bodysprite")
+ var/selected_body_sprite = input(user, "Choose your desired body sprite", "Character Preference") as null|anything in pref_species.allowed_limb_ids
+ if(selected_body_sprite)
+ chosen_limb_id = selected_body_sprite //this gets sanitized before loading
else
switch(href_list["preference"])
//CITADEL PREFERENCES EDIT - I can't figure out how to modularize these, so they have to go here. :c -Pooj
@@ -2608,6 +2396,13 @@ GLOBAL_LIST_EMPTY(preferences_datums)
parent.mob.hud_used.update_parallax_pref(parent.mob)
// Citadel edit - Prefs don't work outside of this. :c
+
+ if("genital_examine")
+ cit_toggles ^= GENITAL_EXAMINE
+
+ if("vore_examine")
+ cit_toggles ^= VORE_EXAMINE
+
if("hound_sleeper")
cit_toggles ^= MEDIHOUND_SLEEPER
@@ -2644,6 +2439,9 @@ GLOBAL_LIST_EMPTY(preferences_datums)
if("bimbo")
cit_toggles ^= BIMBOFICATION
+ if("auto_wag")
+ cit_toggles ^= NO_AUTO_WAG
+
//END CITADEL EDIT
if("ambientocclusion")
@@ -2687,11 +2485,13 @@ GLOBAL_LIST_EMPTY(preferences_datums)
gear_points = CONFIG_GET(number/initial_gear_points)
save_preferences()
if(href_list["select_category"])
- for(var/i in GLOB.loadout_items)
- if(i == href_list["select_category"])
- gear_tab = i
+ gear_category = html_decode(href_list["select_category"])
+ gear_subcategory = GLOB.loadout_categories[gear_category][1]
+ if(href_list["select_subcategory"])
+ gear_subcategory = html_decode(href_list["select_subcategory"])
if(href_list["toggle_gear_path"])
- var/datum/gear/G = GLOB.loadout_items[gear_tab][html_decode(href_list["toggle_gear_path"])]
+ var/name = html_decode(href_list["toggle_gear_path"])
+ var/datum/gear/G = GLOB.loadout_items[gear_category][gear_subcategory][name]
if(!G)
return
var/toggle = text2num(href_list["toggle_gear"])
@@ -2712,7 +2512,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
ShowChoices(user)
return 1
-/datum/preferences/proc/copy_to(mob/living/carbon/human/character, icon_updates = 1, roundstart_checks = TRUE)
+/datum/preferences/proc/copy_to(mob/living/carbon/human/character, icon_updates = 1, roundstart_checks = TRUE, initial_spawn = FALSE)
if(be_random_name)
real_name = pref_species.random_name(gender)
@@ -2771,14 +2571,16 @@ GLOBAL_LIST_EMPTY(preferences_datums)
character.dna.features = features.Copy()
character.set_species(chosen_species, icon_update = FALSE, pref_load = TRUE)
+ if(chosen_limb_id && (chosen_limb_id in character.dna.species.allowed_limb_ids))
+ character.dna.species.mutant_bodyparts["limbs_id"] = chosen_limb_id
character.dna.real_name = character.real_name
character.dna.nameless = character.nameless
character.dna.custom_species = character.custom_species
- if(pref_species.mutant_bodyparts["meat_type"])
+ if((parent && parent.can_have_part("meat_type")) || pref_species.mutant_bodyparts["meat_type"])
character.type_of_meat = GLOB.meat_types[features["meat_type"]]
- if(character.dna.species.mutant_bodyparts["legs"] && (character.dna.features["legs"] == "Digitigrade" || character.dna.features["legs"] == "Avian"))
+ if(((parent && parent.can_have_part("legs")) || pref_species.mutant_bodyparts["legs"]) && (character.dna.features["legs"] == "Digitigrade" || character.dna.features["legs"] == "Avian"))
pref_species.species_traits |= DIGITIGRADE
else
pref_species.species_traits -= DIGITIGRADE
@@ -2796,6 +2598,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
if(custom_tongue != "default")
var/new_tongue = GLOB.roundstart_tongues[custom_tongue]
if(new_tongue)
+ character.dna.species.mutanttongue = new_tongue //this means we get our tongue when we clone
var/obj/item/organ/tongue/T = character.getorganslot(ORGAN_SLOT_TONGUE)
if(T)
qdel(T)
@@ -2804,6 +2607,35 @@ GLOBAL_LIST_EMPTY(preferences_datums)
if(custom_speech_verb != "default")
character.dna.species.say_mod = custom_speech_verb
+ //limb stuff, only done when initially spawning in
+ if(initial_spawn)
+ //delete any existing prosthetic limbs to make sure no remnant prosthetics are left over
+ for(var/obj/item/bodypart/part in character.bodyparts)
+ if(part.status == BODYPART_ROBOTIC)
+ qdel(part)
+ character.regenerate_limbs() //regenerate limbs so now you only have normal limbs
+ for(var/modified_limb in modified_limbs)
+ var/modification = modified_limbs[modified_limb][1]
+ var/obj/item/bodypart/old_part = character.get_bodypart(modified_limb)
+ if(modification == LOADOUT_LIMB_PROSTHETIC)
+ var/obj/item/bodypart/new_limb
+ switch(modified_limb)
+ if(BODY_ZONE_L_ARM)
+ new_limb = new/obj/item/bodypart/l_arm/robot/surplus(character)
+ if(BODY_ZONE_R_ARM)
+ new_limb = new/obj/item/bodypart/r_arm/robot/surplus(character)
+ if(BODY_ZONE_L_LEG)
+ new_limb = new/obj/item/bodypart/l_leg/robot/surplus(character)
+ if(BODY_ZONE_R_LEG)
+ new_limb = new/obj/item/bodypart/r_leg/robot/surplus(character)
+ var/prosthetic_type = modified_limbs[modified_limb][2]
+ if(prosthetic_type != "prosthetic") //lets just leave the old sprites as they are
+ new_limb.icon = file("icons/mob/augmentation/cosmetic_prosthetic/[prosthetic_type].dmi")
+ new_limb.replace_limb(character)
+ qdel(old_part)
+
+ if(length(modified_limbs))
+ character.regenerate_icons()
SEND_SIGNAL(character, COMSIG_HUMAN_PREFS_COPIED_TO, src, icon_updates, roundstart_checks)
@@ -2812,6 +2644,18 @@ GLOBAL_LIST_EMPTY(preferences_datums)
character.update_body()
character.update_hair()
+/datum/preferences/proc/post_copy_to(mob/living/carbon/human/character)
+ //if no legs, and not a paraplegic or a slime, give them a free wheelchair
+ if(modified_limbs[BODY_ZONE_L_LEG] == LOADOUT_LIMB_AMPUTATED && modified_limbs[BODY_ZONE_R_LEG] == LOADOUT_LIMB_AMPUTATED && !character.has_quirk(/datum/quirk/paraplegic) && !isjellyperson(character))
+ if(character.buckled)
+ character.buckled.unbuckle_mob(character)
+ var/turf/T = get_turf(character)
+ var/obj/structure/chair/spawn_chair = locate() in T
+ var/obj/vehicle/ridden/wheelchair/wheels = new(T)
+ if(spawn_chair) // Makes spawning on the arrivals shuttle more consistent looking
+ wheels.setDir(spawn_chair.dir)
+ wheels.buckle_mob(character)
+
/datum/preferences/proc/get_default_name(name_id)
switch(name_id)
if("human")
@@ -2870,17 +2714,17 @@ GLOBAL_LIST_EMPTY(preferences_datums)
LAZYINITLIST(L)
for(var/i in chosen_gear)
var/datum/gear/G = i
- var/occupied_slots = L[slot_to_string(initial(G.category))] ? L[slot_to_string(initial(G.category))] + 1 : 1
- LAZYSET(L, slot_to_string(initial(G.category)), occupied_slots)
+ var/occupied_slots = L[initial(G.category)] ? L[initial(G.category)] + 1 : 1
+ LAZYSET(L, initial(G.category), occupied_slots)
switch(slot)
if(SLOT_IN_BACKPACK)
- if(L[slot_to_string(SLOT_IN_BACKPACK)] < BACKPACK_SLOT_AMT)
+ if(L[LOADOUT_CATEGORY_BACKPACK] < BACKPACK_SLOT_AMT)
return TRUE
if(SLOT_HANDS)
- if(L[slot_to_string(SLOT_HANDS)] < HANDS_SLOT_AMT)
+ if(L[LOADOUT_CATEGORY_HANDS] < HANDS_SLOT_AMT)
return TRUE
else
- if(L[slot_to_string(slot)] < DEFAULT_SLOT_AMT)
+ if(L[slot] < DEFAULT_SLOT_AMT)
return TRUE
#undef DEFAULT_SLOT_AMT
diff --git a/code/modules/client/preferences_savefile.dm b/code/modules/client/preferences_savefile.dm
index 59d99674eb..5856a7b6e1 100644
--- a/code/modules/client/preferences_savefile.dm
+++ b/code/modules/client/preferences_savefile.dm
@@ -5,7 +5,7 @@
// You do not need to raise this if you are adding new values that have sane defaults.
// Only raise this value when changing the meaning/format/name/layout of an existing value
// where you would want the updater procs below to run
-#define SAVEFILE_VERSION_MAX 33
+#define SAVEFILE_VERSION_MAX 35
/*
SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Carn
@@ -200,6 +200,10 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
features["silicon_flavor_text"] = html_encode(features["silicon_flavor_text"])
features["ooc_notes"] = html_encode(features["ooc_notes"])
+ if(current_version < 35)
+ if(S["species"] == "lizard")
+ features["mam_snouts"] = features["snout"]
+
/datum/preferences/proc/load_path(ckey,filename="preferences.sav")
if(!ckey)
return
@@ -224,7 +228,7 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
var/needs_update = savefile_needs_update(S)
if(needs_update == -2) //fatal, can't load any data
return 0
-
+
. = TRUE
//general preferences
@@ -443,7 +447,7 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
return 0
. = TRUE
-
+
//Species
var/species_id
S["species"] >> species_id
@@ -510,7 +514,14 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
S["scars3"] >> scars_list["3"]
S["scars4"] >> scars_list["4"]
S["scars5"] >> scars_list["5"]
-
+ var/limbmodstr
+ S["modified_limbs"] >> limbmodstr
+ if(length(limbmodstr))
+ modified_limbs = safe_json_decode(limbmodstr)
+ else
+ modified_limbs = list()
+ S["chosen_limb_id"] >> chosen_limb_id
+ S["hide_ckey"] >> hide_ckey //saved per-character
//Custom names
for(var/custom_name_id in GLOB.preferences_custom_names)
@@ -852,6 +863,7 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
WRITE_FILE(S["joblessrole"] , joblessrole)
//Write prefs
WRITE_FILE(S["job_preferences"] , job_preferences)
+ WRITE_FILE(S["hide_ckey"] , hide_ckey)
//Quirks
WRITE_FILE(S["all_quirks"] , all_quirks)
@@ -866,6 +878,10 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
WRITE_FILE(S["scars3"] , scars_list["3"])
WRITE_FILE(S["scars4"] , scars_list["4"])
WRITE_FILE(S["scars5"] , scars_list["5"])
+ if(islist(modified_limbs))
+ WRITE_FILE(S["modified_limbs"] , safe_json_encode(modified_limbs))
+ WRITE_FILE(S["chosen_limb_id"], chosen_limb_id)
+
//gear loadout
if(chosen_gear.len)
diff --git a/code/modules/client/preferences_toggles.dm b/code/modules/client/preferences_toggles.dm
index 951e36f9a6..e16a491c61 100644
--- a/code/modules/client/preferences_toggles.dm
+++ b/code/modules/client/preferences_toggles.dm
@@ -145,8 +145,7 @@ TOGGLE_CHECKBOX(/datum/verbs/menu/Settings/Sound, togglemidis)()
to_chat(usr, "You will no longer hear sounds uploaded by admins")
usr.stop_sound_channel(CHANNEL_ADMIN)
var/client/C = usr.client
- if(C && C.chatOutput && !C.chatOutput.broken && C.chatOutput.loaded)
- C.chatOutput.stopMusic()
+ C?.tgui_panel?.stop_music()
SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Hearing Midis", "[usr.client.prefs.toggles & SOUND_MIDI ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/datum/verbs/menu/Settings/Sound/togglemidis/Get_checked(client/C)
return C.prefs.toggles & SOUND_MIDI
@@ -234,8 +233,7 @@ TOGGLE_CHECKBOX(/datum/verbs/menu/Settings/Sound, toggleprayersounds)()
set desc = "Stop Current Sounds"
SEND_SOUND(usr, sound(null))
var/client/C = usr.client
- if(C && C.chatOutput && !C.chatOutput.broken && C.chatOutput.loaded)
- C.chatOutput.stopMusic()
+ C?.tgui_panel?.stop_music()
SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Stop Self Sounds")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
diff --git a/code/modules/client/verbs/ooc.dm b/code/modules/client/verbs/ooc.dm
index fa1145ecea..99703482b9 100644
--- a/code/modules/client/verbs/ooc.dm
+++ b/code/modules/client/verbs/ooc.dm
@@ -158,88 +158,6 @@ GLOBAL_VAR_INIT(normal_ooc_colour, "#002eb8")
else
to_chat(src, "There are no admin notices at the moment.")
-/client/verb/fix_chat()
- set name = "Fix chat"
- set category = "OOC"
- if (!chatOutput || !istype(chatOutput))
- var/action = alert(src, "Invalid Chat Output data found!\nRecreate data?", "Wot?", "Recreate Chat Output data", "Cancel")
- if (action != "Recreate Chat Output data")
- return
- chatOutput = new /datum/chatOutput(src)
- chatOutput.start()
- action = alert(src, "Goon chat reloading, wait a bit and tell me if it's fixed", "", "Fixed", "Nope")
- if (action == "Fixed")
- log_game("GOONCHAT: [key_name(src)] Had to fix their goonchat by re-creating the chatOutput datum")
- else
- chatOutput.load()
- action = alert(src, "How about now? (give it a moment (it may also try to load twice))", "", "Yes", "No")
- if (action == "Yes")
- log_game("GOONCHAT: [key_name(src)] Had to fix their goonchat by re-creating the chatOutput datum and forcing a load()")
- else
- action = alert(src, "Welp, I'm all out of ideas. Try closing byond and reconnecting.\nWe could also disable fancy chat and re-enable oldchat", "", "Thanks anyways", "Switch to old chat")
- if (action == "Switch to old chat")
- winset(src, "output", "is-visible=true;is-disabled=false")
- winset(src, "browseroutput", "is-visible=false")
- log_game("GOONCHAT: [key_name(src)] Failed to fix their goonchat window after recreating the chatOutput and forcing a load()")
-
- else if (chatOutput.loaded)
- var/action = alert(src, "ChatOutput seems to be loaded\nDo you want me to force a reload, wiping the chat log or just refresh the chat window because it broke/went away?", "Hmmm", "Force Reload", "Refresh", "Cancel")
- switch (action)
- if ("Force Reload")
- chatOutput.loaded = FALSE
- chatOutput.start() //this is likely to fail since it asks , but we should try it anyways so we know.
- action = alert(src, "Goon chat reloading, wait a bit and tell me if it's fixed", "", "Fixed", "Nope")
- if (action == "Fixed")
- log_game("GOONCHAT: [key_name(src)] Had to fix their goonchat by forcing a start()")
- else
- chatOutput.load()
- action = alert(src, "How about now? (give it a moment (it may also try to load twice))", "", "Yes", "No")
- if (action == "Yes")
- log_game("GOONCHAT: [key_name(src)] Had to fix their goonchat by forcing a load()")
- else
- action = alert(src, "Welp, I'm all out of ideas. Try closing byond and reconnecting.\nWe could also disable fancy chat and re-enable oldchat", "", "Thanks anyways", "Switch to old chat")
- if (action == "Switch to old chat")
- winset(src, "output", "is-visible=true;is-disabled=false")
- winset(src, "browseroutput", "is-visible=false")
- log_game("GOONCHAT: [key_name(src)] Failed to fix their goonchat window forcing a start() and forcing a load()")
-
- if ("Refresh")
- chatOutput.showChat()
- action = alert(src, "Goon chat refreshing, wait a bit and tell me if it's fixed", "", "Fixed", "Nope, force a reload")
- if (action == "Fixed")
- log_game("GOONCHAT: [key_name(src)] Had to fix their goonchat by forcing a show()")
- else
- chatOutput.loaded = FALSE
- chatOutput.load()
- action = alert(src, "How about now? (give it a moment)", "", "Yes", "No")
- if (action == "Yes")
- log_game("GOONCHAT: [key_name(src)] Had to fix their goonchat by forcing a load()")
- else
- action = alert(src, "Welp, I'm all out of ideas. Try closing byond and reconnecting.\nWe could also disable fancy chat and re-enable oldchat", "", "Thanks anyways", "Switch to old chat")
- if (action == "Switch to old chat")
- winset(src, "output", "is-visible=true;is-disabled=false")
- winset(src, "browseroutput", "is-visible=false")
- log_game("GOONCHAT: [key_name(src)] Failed to fix their goonchat window forcing a show() and forcing a load()")
- return
-
- else
- chatOutput.start()
- var/action = alert(src, "Manually loading Chat, wait a bit and tell me if it's fixed", "", "Fixed", "Nope")
- if (action == "Fixed")
- log_game("GOONCHAT: [key_name(src)] Had to fix their goonchat by manually calling start()")
- else
- chatOutput.load()
- alert(src, "How about now? (give it a moment (it may also try to load twice))", "", "Yes", "No")
- if (action == "Yes")
- log_game("GOONCHAT: [key_name(src)] Had to fix their goonchat by manually calling start() and forcing a load()")
- else
- action = alert(src, "Welp, I'm all out of ideas. Try closing byond and reconnecting.\nWe could also disable fancy chat and re-enable oldchat", "", "Thanks anyways", "Switch to old chat")
- if (action == "Switch to old chat")
- winset(src, "output", list2params(list("on-show" = "", "is-disabled" = "false", "is-visible" = "true")))
- winset(src, "browseroutput", "is-disabled=true;is-visible=false")
- log_game("GOONCHAT: [key_name(src)] Failed to fix their goonchat window after manually calling start() and forcing a load()")
-
-
/client/verb/motd()
set name = "MOTD"
diff --git a/code/modules/clothing/clothing.dm b/code/modules/clothing/clothing.dm
index d540336b86..2fbe738acb 100644
--- a/code/modules/clothing/clothing.dm
+++ b/code/modules/clothing/clothing.dm
@@ -118,8 +118,7 @@
// Set the clothing's integrity back to 100%, remove all damage to bodyparts, and generally fix it up
/obj/item/clothing/proc/repair(mob/user, params)
- damaged_clothes = CLOTHING_PRISTINE
- update_clothes_damaged_state(FALSE)
+ update_clothes_damaged_state(CLOTHING_PRISTINE)
obj_integrity = max_integrity
name = initial(name) // remove "tattered" or "shredded" if there's a prefix
body_parts_covered = initial(body_parts_covered)
@@ -196,7 +195,7 @@
if(3 to INFINITY) // take better care of your shit, dude
name = "tattered [initial(name)]"
- update_clothes_damaged_state()
+ update_clothes_damaged_state(CLOTHING_DAMAGED)
/obj/item/clothing/Destroy()
user_vars_remembered = null //Oh god somebody put REFERENCES in here? not to worry, we'll clean it up
@@ -257,7 +256,7 @@
how_cool_are_your_threads += "Adding or removing items from [src] makes no noise.\n"
how_cool_are_your_threads += ""
. += how_cool_are_your_threads.Join()
-
+
if(LAZYLEN(armor_list))
armor_list.Cut()
if(armor.bio)
@@ -346,10 +345,16 @@
var/mob/M = loc
to_chat(M, "Your [name] starts to fall apart!")
-/obj/item/clothing/proc/update_clothes_damaged_state(damaging = TRUE)
- var/index = "[REF(initial(icon))]-[initial(icon_state)]"
- var/static/list/damaged_clothes_icons = list()
- if(damaging)
+//This mostly exists so subtypes can call appriopriate update icon calls on the wearer.
+/obj/item/clothing/proc/update_clothes_damaged_state(damaged_state = CLOTHING_DAMAGED)
+ damaged_clothes = damaged_state
+ update_icon()
+
+/obj/item/clothing/update_overlays()
+ . = ..()
+ if(damaged_clothes)
+ var/index = "[REF(initial(icon))]-[initial(icon_state)]"
+ var/static/list/damaged_clothes_icons = list()
var/icon/damaged_clothes_icon = damaged_clothes_icons[index]
if(!damaged_clothes_icon)
damaged_clothes_icon = icon(initial(icon), initial(icon_state), , 1) //we only want to apply damaged effect to the initial icon_state for each object
@@ -357,9 +362,7 @@
damaged_clothes_icon.Blend(icon('icons/effects/item_damage.dmi', "itemdamaged"), ICON_MULTIPLY) //adds damage effect and the remaining white areas become transparant
damaged_clothes_icon = fcopy_rsc(damaged_clothes_icon)
damaged_clothes_icons[index] = damaged_clothes_icon
- add_overlay(damaged_clothes_icon, TRUE)
- else
- cut_overlay(damaged_clothes_icons[index], TRUE)
+ . += damaged_clothes_icon
/*
SEE_SELF // can see self, no matter what
diff --git a/code/modules/clothing/glasses/_glasses.dm b/code/modules/clothing/glasses/_glasses.dm
index 01effea6f2..3f6d21bcd2 100644
--- a/code/modules/clothing/glasses/_glasses.dm
+++ b/code/modules/clothing/glasses/_glasses.dm
@@ -96,7 +96,7 @@
throw_speed = 4
attack_verb = list("sliced")
hitsound = 'sound/weapons/bladeslice.ogg'
- sharpness = IS_SHARP
+ sharpness = SHARP_EDGED
/obj/item/clothing/glasses/meson/eyepatch
name = "eyepatch mesons"
@@ -149,6 +149,30 @@
icon_state = "eyepatch"
item_state = "eyepatch"
+/obj/item/clothing/glasses/eyepatch/syndicate
+ name = "cybernetic eyepatch"
+ desc = "An eyepatch used to enhance one's aim with guns."
+ icon_state = "syndicatepatch"
+ item_state = "syndicatepatch"
+ resistance_flags = ACID_PROOF
+
+/obj/item/clothing/glasses/eyepatch/syndicate/equipped(mob/living/carbon/human/user, slot)
+ . = ..()
+ if(slot == SLOT_GLASSES)
+ user.visible_message("Circuitry from the eyepatch links itself to your brain as you put on the eyepatch.")
+ if(HAS_TRAIT(user, TRAIT_POOR_AIM))
+ user.visible_message("You hear a fizzing noise from the circuit. That can't be good.")
+ ADD_TRAIT(user, TRAIT_INSANE_AIM, "SYNDICATE_EYEPATCH_AIM")
+ ADD_TRAIT(src, TRAIT_NODROP, "SYNDICATE_EYEPATCH_NODROP")
+
+/obj/item/clothing/glasses/eyepatch/syndicate/dropped(mob/living/carbon/human/user)
+ . = ..()
+ REMOVE_TRAIT(user, TRAIT_INSANE_AIM, "SYNDICATE_EYEPATCH_AIM")
+ var/obj/item/organ/eyes/eyes = user.getorganslot(ORGAN_SLOT_EYES)
+ if(eyes)
+ eyes.applyOrganDamage(30)
+ user.visible_message("Your eye stings as the circuitry is removed from your eye!")
+
/obj/item/clothing/glasses/monocle
name = "monocle"
desc = "Such a dapper eyepiece!"
@@ -180,7 +204,7 @@
throw_speed = 4
attack_verb = list("sliced")
hitsound = 'sound/weapons/bladeslice.ogg'
- sharpness = IS_SHARP
+ sharpness = SHARP_EDGED
vision_correction = 1
glass_colour_type = /datum/client_colour/glass_colour/lightgreen
@@ -237,7 +261,7 @@
throw_speed = 4
attack_verb = list("sliced")
hitsound = 'sound/weapons/bladeslice.ogg'
- sharpness = IS_SHARP
+ sharpness = SHARP_EDGED
/obj/item/clothing/glasses/sunglasses/garb/supergarb
name = "black giga gar glasses"
@@ -257,7 +281,7 @@
throw_speed = 4
attack_verb = list("sliced")
hitsound = 'sound/weapons/bladeslice.ogg'
- sharpness = IS_SHARP
+ sharpness = SHARP_EDGED
glass_colour_type = /datum/client_colour/glass_colour/orange
/obj/item/clothing/glasses/sunglasses/gar/supergar
diff --git a/code/modules/clothing/glasses/engine_goggles.dm b/code/modules/clothing/glasses/engine_goggles.dm
index 2a64445776..d7e7ae3669 100644
--- a/code/modules/clothing/glasses/engine_goggles.dm
+++ b/code/modules/clothing/glasses/engine_goggles.dm
@@ -97,14 +97,14 @@
if(get_dist(user, place) >= range*8) //Rads are easier to see than wires under the floor
continue
var/strength = round(rad_places[i] / 1000, 0.1)
- var/image/pic = new(loc = place)
+ var/image/pic = image(loc = place)
var/mutable_appearance/MA = new()
- MA.alpha = 180
- MA.maptext = "[strength]k"
- MA.color = "#64C864"
- MA.layer = FLY_LAYER
+ MA.maptext = "[strength]k"
+ MA.color = "#04e604"
+ MA.layer = RAD_TEXT_LAYER
+ MA.plane = GAME_PLANE
pic.appearance = MA
- flick_overlay(pic, list(user.client), 8)
+ flick_overlay(pic, list(user.client), 10)
/obj/item/clothing/glasses/meson/engine/proc/show_shuttle()
var/mob/living/carbon/human/user = loc
diff --git a/code/modules/clothing/glasses/hud.dm b/code/modules/clothing/glasses/hud.dm
index c745cd74f0..9ca98b0bca 100644
--- a/code/modules/clothing/glasses/hud.dm
+++ b/code/modules/clothing/glasses/hud.dm
@@ -70,7 +70,7 @@
flash_protect = -2
lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE
glass_colour_type = /datum/client_colour/glass_colour/green
-
+
/obj/item/clothing/glasses/hud/health/night/syndicate
name = "combat night vision health scanner HUD"
desc = "An advanced shielded medical heads-up display that allows soldiers to approximate how much lead poisoning their allies have suffered in complete darkness."
@@ -221,7 +221,7 @@
throw_speed = 4
attack_verb = list("sliced")
hitsound = 'sound/weapons/bladeslice.ogg'
- sharpness = IS_SHARP
+ sharpness = SHARP_EDGED
/obj/item/clothing/glasses/hud/security/sunglasses/gars/supergars
name = "giga HUD gar glasses"
diff --git a/code/modules/clothing/gloves/miscellaneous.dm b/code/modules/clothing/gloves/miscellaneous.dm
index 1c3ac0f340..b5d92fb780 100644
--- a/code/modules/clothing/gloves/miscellaneous.dm
+++ b/code/modules/clothing/gloves/miscellaneous.dm
@@ -199,12 +199,16 @@
/obj/item/clothing/gloves/evening
name = "evening gloves"
- desc = "Thin, pretty gloves intended for use in regal feminine attire, but knowing Space China these are just for some maid fetish."
+ desc = "Thin, pretty gloves intended for use in regal feminine attire. A tag on the hem claims they were 'maid' in Space China, these were probably intended for use in some maid fetish."
icon_state = "evening"
item_state = "evening"
- strip_delay = 40
- equip_delay_other = 20
+ transfer_prints = TRUE
cold_protection = HANDS
min_cold_protection_temperature = GLOVES_MIN_TEMP_PROTECT
strip_mod = 0.9
- custom_price = PRICE_ALMOST_CHEAP
+
+/obj/item/clothing/gloves/evening/black
+ name = "midnight gloves"
+ desc = "Thin, pretty gloves intended for use in sexy feminine attire. A tag on the hem claims they pair great with black stockings."
+ icon_state = "eveningblack"
+ item_state = "eveningblack"
diff --git a/code/modules/clothing/gloves/ring.dm b/code/modules/clothing/gloves/ring.dm
index 8354f13bd2..daca843c45 100644
--- a/code/modules/clothing/gloves/ring.dm
+++ b/code/modules/clothing/gloves/ring.dm
@@ -21,7 +21,7 @@
desc = "An expensive ring, studded with a diamond. Cultures have used these rings in courtship for a millenia."
icon_state = "ringdiamond"
item_state = "dring"
-
+
/obj/item/clothing/gloves/ring/diamond/attack_self(mob/user)
user.visible_message("\The [user] gets down on one knee, presenting \the [src].","You get down on one knee, presenting \the [src].")
@@ -30,3 +30,12 @@
desc = "A tiny silver ring, sized to wrap around a finger."
icon_state = "ringsilver"
item_state = "sring"
+
+/obj/item/clothing/gloves/ring/custom
+ name = "ring"
+ desc = "A ring."
+ gender = NEUTER
+ w_class = WEIGHT_CLASS_TINY
+ obj_flags = UNIQUE_RENAME
+ icon_state = "ringsilver"
+ item_state = "sring"
diff --git a/code/modules/clothing/gloves/tacklers.dm b/code/modules/clothing/gloves/tacklers.dm
index 11b2afa968..f4b4140a1a 100644
--- a/code/modules/clothing/gloves/tacklers.dm
+++ b/code/modules/clothing/gloves/tacklers.dm
@@ -72,6 +72,25 @@
siemens_coefficient = 0
permeability_coefficient = 0.05
+/obj/item/clothing/gloves/tackler/combat/insulated/infiltrator
+ name = "insidious guerrilla gloves"
+ desc = "Specialized combat gloves for carrying people around. Transfers tactical kidnapping and tackling knowledge to the user via the use of nanochips."
+ icon_state = "infiltrator"
+ item_state = "infiltrator"
+ siemens_coefficient = 0
+ permeability_coefficient = 0.05
+ resistance_flags = FIRE_PROOF | ACID_PROOF
+ var/carrytrait = TRAIT_QUICKER_CARRY
+
+/obj/item/clothing/gloves/tackler/combat/insulated/infiltrator/equipped(mob/user, slot)
+ . = ..()
+ if(slot == SLOT_GLOVES)
+ ADD_TRAIT(user, carrytrait, GLOVE_TRAIT)
+
+/obj/item/clothing/gloves/tackler/combat/insulated/infiltrator/dropped(mob/user)
+ . = ..()
+ REMOVE_TRAIT(user, carrytrait, GLOVE_TRAIT)
+
/obj/item/clothing/gloves/tackler/rocket
name = "rocket gloves"
desc = "The ultimate in high risk, high reward, perfect for when you need to stop a criminal from fifty feet away or die trying. Banned in most Spinward gridiron football and rugby leagues."
diff --git a/code/modules/clothing/head/misc.dm b/code/modules/clothing/head/misc.dm
index 72d0f68c18..124ece8fdc 100644
--- a/code/modules/clothing/head/misc.dm
+++ b/code/modules/clothing/head/misc.dm
@@ -469,3 +469,16 @@
icon_state = "maid"
item_state = "maid"
dynamic_hair_suffix = ""
+
+/obj/item/clothing/head/widered
+ name = "Wide red hat"
+ desc = "It is both wide, and red. Stylish!"
+ icon_state = "widehat_red"
+ item_state = "widehat_red"
+
+/obj/item/clothing/head/kabuto
+ name = "Kabuto helmet"
+ desc = "A traditional kabuto helmet."
+ icon_state = "kabuto"
+ item_state = "kabuto"
+ flags_inv = HIDEHAIR|HIDEEARS
diff --git a/code/modules/clothing/masks/boxing.dm b/code/modules/clothing/masks/boxing.dm
index f11c89d00c..6701b53c10 100644
--- a/code/modules/clothing/masks/boxing.dm
+++ b/code/modules/clothing/masks/boxing.dm
@@ -12,6 +12,10 @@
/obj/item/clothing/mask/balaclava/attack_self(mob/user)
adjustmask(user)
+/obj/item/clothing/mask/balaclava/breath
+ name = "breathaclava"
+ clothing_flags = ALLOWINTERNALS
+
/obj/item/clothing/mask/infiltrator
name = "insidious balaclava"
desc = "An incredibly suspicious balaclava made with Syndicate nanofibers to absorb impacts slightly while obfuscating the voice and face using a garbled vocoder."
diff --git a/code/modules/clothing/shoes/_shoes.dm b/code/modules/clothing/shoes/_shoes.dm
index 746bd3458a..49256b490d 100644
--- a/code/modules/clothing/shoes/_shoes.dm
+++ b/code/modules/clothing/shoes/_shoes.dm
@@ -257,10 +257,8 @@
if(14 to 25) // 1.3ish% chance to stumble and be a bit off balance (like being disarmed)
to_chat(our_guy, "You stumble a bit on your untied shoelaces!")
- if(!our_guy.has_movespeed_modifier(/datum/movespeed_modifier/shove))
- our_guy.add_movespeed_modifier(/datum/movespeed_modifier/shove)
- addtimer(CALLBACK(our_guy, /mob/living/carbon/human/proc/clear_shove_slowdown), SHOVE_SLOWDOWN_LENGTH)
-
+ our_guy.ShoveOffBalance(SHOVE_OFFBALANCE_DURATION)
+ our_guy.Stagger(SHOVE_OFFBALANCE_DURATION) //yes, same.
if(26 to 1000)
wiser = FALSE
if(wiser)
diff --git a/code/modules/clothing/spacesuits/_spacesuits.dm b/code/modules/clothing/spacesuits/_spacesuits.dm
index 82dd3142ed..cba27845f1 100644
--- a/code/modules/clothing/spacesuits/_spacesuits.dm
+++ b/code/modules/clothing/spacesuits/_spacesuits.dm
@@ -7,7 +7,7 @@
clothing_flags = STOPSPRESSUREDAMAGE | THICKMATERIAL | BLOCK_GAS_SMOKE_EFFECT | ALLOWINTERNALS
item_state = "spaceold"
permeability_coefficient = 0.01
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 50, "fire" = 80, "acid" = 70)
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 50, "fire" = 80, "acid" = 70, "wound" = 5)
flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR|HIDESNOUT
dynamic_hair_suffix = ""
dynamic_fhair_suffix = ""
@@ -36,7 +36,7 @@
body_parts_covered = CHEST|GROIN|LEGS|FEET|ARMS|HANDS
allowed = list(/obj/item/flashlight, /obj/item/tank/internals)
slowdown = 1
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 50, "fire" = 80, "acid" = 70)
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 50, "fire" = 80, "acid" = 70, "wound" = 5)
flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT|HIDETAUR
cold_protection = CHEST | GROIN | LEGS | FEET | ARMS | HANDS
min_cold_protection_temperature = SPACE_SUIT_MIN_TEMP_PROTECT
diff --git a/code/modules/clothing/spacesuits/chronosuit.dm b/code/modules/clothing/spacesuits/chronosuit.dm
index 4dc6fd1f6c..3f77a2befc 100644
--- a/code/modules/clothing/spacesuits/chronosuit.dm
+++ b/code/modules/clothing/spacesuits/chronosuit.dm
@@ -4,7 +4,7 @@
icon_state = "chronohelmet"
item_state = "chronohelmet"
slowdown = 1
- armor = list("melee" = 60, "bullet" = 60, "laser" = 60, "energy" = 60, "bomb" = 30, "bio" = 90, "rad" = 90, "fire" = 100, "acid" = 100)
+ armor = list("melee" = 60, "bullet" = 60, "laser" = 60, "energy" = 60, "bomb" = 30, "bio" = 90, "rad" = 90, "fire" = 100, "acid" = 100, "wound" = 80)
resistance_flags = FIRE_PROOF | ACID_PROOF
var/obj/item/clothing/suit/space/chronos/suit = null
@@ -19,7 +19,7 @@
icon_state = "chronosuit"
item_state = "chronosuit"
actions_types = list(/datum/action/item_action/toggle)
- armor = list("melee" = 60, "bullet" = 60, "laser" = 60, "energy" = 60, "bomb" = 30, "bio" = 90, "rad" = 90, "fire" = 100, "acid" = 1000)
+ armor = list("melee" = 60, "bullet" = 60, "laser" = 60, "energy" = 60, "bomb" = 30, "bio" = 90, "rad" = 90, "fire" = 100, "acid" = 1000, "wound" = 80)
resistance_flags = FIRE_PROOF | ACID_PROOF
mutantrace_variation = STYLE_DIGITIGRADE
var/list/chronosafe_items = list(/obj/item/chrono_eraser, /obj/item/gun/energy/chrono_gun)
diff --git a/code/modules/clothing/spacesuits/hardsuit.dm b/code/modules/clothing/spacesuits/hardsuit.dm
index b588deaf01..12829ed0e7 100644
--- a/code/modules/clothing/spacesuits/hardsuit.dm
+++ b/code/modules/clothing/spacesuits/hardsuit.dm
@@ -5,7 +5,7 @@
icon_state = "hardsuit0-engineering"
item_state = "eng_helm"
max_integrity = 300
- armor = list("melee" = 10, "bullet" = 5, "laser" = 10, "energy" = 5, "bomb" = 10, "bio" = 100, "rad" = 75, "fire" = 50, "acid" = 75)
+ armor = list("melee" = 10, "bullet" = 5, "laser" = 10, "energy" = 5, "bomb" = 10, "bio" = 100, "rad" = 75, "fire" = 50, "acid" = 75, "wound" = 10)
var/basestate = "hardsuit"
var/brightness_on = 4 //luminosity when on
var/on = FALSE
@@ -214,7 +214,7 @@
name = "advanced hardsuit"
desc = "An advanced suit that protects against hazardous, low pressure environments. Shines with a high polish."
item_state = "ce_hardsuit"
- armor = list("melee" = 40, "bullet" = 5, "laser" = 10, "energy" = 5, "bomb" = 50, "bio" = 100, "rad" = 95, "fire" = 100, "acid" = 90, "wound" = 10)
+ armor = list("melee" = 40, "bullet" = 5, "laser" = 10, "energy" = 5, "bomb" = 50, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 90, "wound" = 10)
heat_protection = CHEST|GROIN|LEGS|FEET|ARMS|HANDS
max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT
helmettype = /obj/item/clothing/head/helmet/space/hardsuit/engine/elite
@@ -717,7 +717,7 @@
item_state = "rig0-soviet"
hardsuit_type = "soviet"
icon_state = "rig0-soviet"
- armor = list("melee" = 40, "bullet" = 30, "laser" = 30, "energy" = 15, "bomb" = 35, "bio" = 100, "rad" = 20, "fire" = 50, "acid" = 75)
+ armor = list("melee" = 40, "bullet" = 30, "laser" = 30, "energy" = 15, "bomb" = 35, "bio" = 100, "rad" = 20, "fire" = 50, "acid" = 75, "wound" = 15)
mutantrace_variation = NONE
/obj/item/clothing/suit/space/hardsuit/soviet
@@ -726,7 +726,7 @@
item_state = "rig-soviet"
icon_state = "rig-soviet"
slowdown = 0.8
- armor = list("melee" = 40, "bullet" = 30, "laser" = 30, "energy" = 15, "bomb" = 35, "bio" = 100, "rad" = 20, "fire" = 50, "acid" = 75)
+ armor = list("melee" = 40, "bullet" = 30, "laser" = 30, "energy" = 15, "bomb" = 35, "bio" = 100, "rad" = 20, "fire" = 50, "acid" = 75, "wound" = 15)
helmettype = /obj/item/clothing/head/helmet/space/hardsuit/soviet
mutantrace_variation = NONE
@@ -771,7 +771,7 @@
item_state = "ert_medical"
hardsuit_type = "ert_medical"
helmettype = /obj/item/clothing/head/helmet/space/hardsuit/shielded/ctf
- armor = list("melee" = 0, "bullet" = 30, "laser" = 30, "energy" = 30, "bomb" = 50, "bio" = 100, "rad" = 100, "fire" = 95, "acid" = 95)
+ armor = list("melee" = 0, "bullet" = 30, "laser" = 30, "energy" = 30, "bomb" = 50, "bio" = 100, "rad" = 100, "fire" = 95, "acid" = 95, "wound" = 30)
slowdown = 0
max_charges = 5
@@ -800,7 +800,7 @@
icon_state = "hardsuit0-ert_medical"
item_state = "hardsuit0-ert_medical"
hardsuit_type = "ert_medical"
- armor = list("melee" = 0, "bullet" = 30, "laser" = 30, "energy" = 30, "bomb" = 50, "bio" = 100, "rad" = 100, "fire" = 95, "acid" = 95)
+ armor = list("melee" = 0, "bullet" = 30, "laser" = 30, "energy" = 30, "bomb" = 50, "bio" = 100, "rad" = 100, "fire" = 95, "acid" = 95, "wound" = 30)
/obj/item/clothing/head/helmet/space/hardsuit/shielded/ctf/red
icon_state = "hardsuit0-ert_security"
@@ -879,7 +879,7 @@
max_heat_protection_temperature = FIRE_SUIT_MAX_TEMP_PROTECT
resistance_flags = FIRE_PROOF | LAVA_PROOF
heat_protection = HEAD
- armor = list(melee = 50, bullet = 10, laser = 10, energy = 10, bomb = 50, bio = 100, rad = 50, fire = 100, acid = 100)
+ armor = list(melee = 50, bullet = 10, laser = 10, energy = 10, bomb = 50, bio = 100, rad = 50, fire = 100, acid = 100, "wound" = 30)
brightness_on = 7
allowed = list(/obj/item/flashlight, /obj/item/tank/internals, /obj/item/resonator, /obj/item/mining_scanner, /obj/item/t_scanner/adv_mining_scanner, /obj/item/gun/energy/kinetic_accelerator)
var/energy_color = "#35FFF0"
@@ -923,7 +923,7 @@
item_state = "swat_suit"
max_heat_protection_temperature = FIRE_SUIT_MAX_TEMP_PROTECT
resistance_flags = FIRE_PROOF | LAVA_PROOF
- armor = list(melee = 50, bullet = 10, laser = 10, energy = 10, bomb = 50, bio = 100, rad = 50, fire = 100, acid = 100)
+ armor = list(melee = 50, bullet = 10, laser = 10, energy = 10, bomb = 50, bio = 100, rad = 50, fire = 100, acid = 100, "wound" = 30)
allowed = list(/obj/item/flashlight, /obj/item/tank/internals, /obj/item/storage/bag/ore, /obj/item/pickaxe)
helmettype = /obj/item/clothing/head/helmet/space/hardsuit/lavaknight
heat_protection = CHEST|GROIN|LEGS|FEET|ARMS|HANDS
diff --git a/code/modules/clothing/spacesuits/miscellaneous.dm b/code/modules/clothing/spacesuits/miscellaneous.dm
index ee70f54ed0..5124c5d62e 100644
--- a/code/modules/clothing/spacesuits/miscellaneous.dm
+++ b/code/modules/clothing/spacesuits/miscellaneous.dm
@@ -51,7 +51,7 @@ Contains:
icon_state = "heavy"
item_state = "swat_suit"
allowed = list(/obj/item/gun, /obj/item/ammo_box, /obj/item/ammo_casing, /obj/item/melee/baton, /obj/item/restraints/handcuffs, /obj/item/tank/internals, /obj/item/kitchen/knife/combat)
- armor = list("melee" = 40, "bullet" = 30, "laser" = 30,"energy" = 30, "bomb" = 50, "bio" = 90, "rad" = 20, "fire" = 100, "acid" = 100)
+ armor = list("melee" = 40, "bullet" = 30, "laser" = 30,"energy" = 30, "bomb" = 50, "bio" = 90, "rad" = 20, "fire" = 100, "acid" = 100, "wound" = 25)
strip_delay = 120
resistance_flags = FIRE_PROOF | ACID_PROOF
mutantrace_variation = STYLE_DIGITIGRADE
@@ -63,7 +63,7 @@ Contains:
dynamic_hair_suffix = "+generic"
dynamic_fhair_suffix = "+generic"
flags_inv = 0
- armor = list("melee" = 80, "bullet" = 80, "laser" = 50, "energy" = 50, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 100)
+ armor = list("melee" = 80, "bullet" = 80, "laser" = 50, "energy" = 50, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 100, "wound" = 30)
strip_delay = 130
max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT
resistance_flags = FIRE_PROOF | ACID_PROOF
@@ -79,7 +79,7 @@ Contains:
flags_inv = 0
w_class = WEIGHT_CLASS_NORMAL
allowed = list(/obj/item/gun, /obj/item/ammo_box, /obj/item/ammo_casing, /obj/item/melee/baton, /obj/item/restraints/handcuffs, /obj/item/tank/internals)
- armor = list("melee" = 80, "bullet" = 80, "laser" = 50, "energy" = 50, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 100)
+ armor = list("melee" = 80, "bullet" = 80, "laser" = 50, "energy" = 50, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 100, "wound" = 30)
strip_delay = 130
max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT
resistance_flags = FIRE_PROOF | ACID_PROOF
@@ -140,7 +140,7 @@ Contains:
desc = "A thick, space-proof tricorne from the royal Space Queen. It's lined with a layer of reflective kevlar."
icon_state = "pirate"
item_state = "pirate"
- armor = list("melee" = 30, "bullet" = 50, "laser" = 30,"energy" = 15, "bomb" = 30, "bio" = 30, "rad" = 30, "fire" = 60, "acid" = 75)
+ armor = list("melee" = 30, "bullet" = 50, "laser" = 30,"energy" = 15, "bomb" = 30, "bio" = 30, "rad" = 30, "fire" = 60, "acid" = 75, "wound" = 30)
flags_inv = HIDEHAIR
strip_delay = 40
equip_delay_other = 20
@@ -163,7 +163,7 @@ Contains:
flags_inv = 0
allowed = list(/obj/item/gun, /obj/item/ammo_box, /obj/item/ammo_casing, /obj/item/melee/baton, /obj/item/restraints/handcuffs, /obj/item/tank/internals, /obj/item/melee/transforming/energy/sword/pirate, /obj/item/clothing/glasses/eyepatch, /obj/item/reagent_containers/food/drinks/bottle/rum)
slowdown = 0
- armor = list("melee" = 30, "bullet" = 50, "laser" = 30,"energy" = 15, "bomb" = 30, "bio" = 30, "rad" = 30, "fire" = 60, "acid" = 75)
+ armor = list("melee" = 30, "bullet" = 50, "laser" = 30,"energy" = 15, "bomb" = 30, "bio" = 30, "rad" = 30, "fire" = 60, "acid" = 75, "wound" = 30)
strip_delay = 40
equip_delay_other = 20
mutantrace_variation = STYLE_DIGITIGRADE
@@ -175,7 +175,7 @@ Contains:
icon_state = "hardsuit0-ert_commander"
item_state = "hardsuit0-ert_commander"
hardsuit_type = "ert_commander"
- armor = list("melee" = 65, "bullet" = 50, "laser" = 50, "energy" = 50, "bomb" = 50, "bio" = 100, "rad" = 100, "fire" = 80, "acid" = 80)
+ armor = list("melee" = 65, "bullet" = 50, "laser" = 50, "energy" = 50, "bomb" = 50, "bio" = 100, "rad" = 100, "fire" = 80, "acid" = 80, "wound" = 30)
strip_delay = 130
brightness_on = 7
resistance_flags = ACID_PROOF
@@ -191,7 +191,7 @@ Contains:
item_state = "ert_command"
helmettype = /obj/item/clothing/head/helmet/space/hardsuit/ert
allowed = list(/obj/item/gun, /obj/item/ammo_box, /obj/item/ammo_casing, /obj/item/melee/baton, /obj/item/restraints/handcuffs, /obj/item/tank/internals)
- armor = list("melee" = 65, "bullet" = 50, "laser" = 50, "energy" = 50, "bomb" = 50, "bio" = 100, "rad" = 100, "fire" = 80, "acid" = 80)
+ armor = list("melee" = 65, "bullet" = 50, "laser" = 50, "energy" = 50, "bomb" = 50, "bio" = 100, "rad" = 100, "fire" = 80, "acid" = 80, "wound" = 30)
slowdown = 0
strip_delay = 130
resistance_flags = ACID_PROOF
@@ -244,7 +244,7 @@ Contains:
icon_state = "hardsuit0-ert_commander-alert"
item_state = "hardsuit0-ert_commander-alert"
hardsuit_type = "ert_commander-alert"
- armor = list("melee" = 70, "bullet" = 55, "laser" = 50, "energy" = 50, "bomb" = 65, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 100)
+ armor = list("melee" = 70, "bullet" = 55, "laser" = 50, "energy" = 50, "bomb" = 65, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 100, "wound" = 50)
brightness_on = 8
resistance_flags = FIRE_PROOF | ACID_PROOF
@@ -254,7 +254,7 @@ Contains:
icon_state = "ert_command-alert"
item_state = "ert_command-alert"
helmettype = /obj/item/clothing/head/helmet/space/hardsuit/ert/alert
- armor = list("melee" = 70, "bullet" = 55, "laser" = 50, "energy" = 50, "bomb" = 65, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 100)
+ armor = list("melee" = 70, "bullet" = 55, "laser" = 50, "energy" = 50, "bomb" = 65, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 100, "wound" = 50)
resistance_flags = FIRE_PROOF | ACID_PROOF
mutantrace_variation = STYLE_DIGITIGRADE|STYLE_SNEK_TAURIC
@@ -303,7 +303,7 @@ Contains:
icon_state = "space"
item_state = "s_suit"
desc = "A lightweight space suit with the basic ability to protect the wearer from the vacuum of space during emergencies."
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 20, "fire" = 50, "acid" = 65)
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 20, "fire" = 50, "acid" = 65, "wound" = 10)
/obj/item/clothing/head/helmet/space/eva
name = "EVA helmet"
@@ -311,7 +311,7 @@ Contains:
item_state = "space"
desc = "A lightweight space helmet with the basic ability to protect the wearer from the vacuum of space during emergencies."
flash_protect = 0
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 20, "fire" = 50, "acid" = 65)
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 20, "fire" = 50, "acid" = 65, "wound" = 10)
//Radiation
/obj/item/clothing/head/helmet/space/rad
@@ -319,7 +319,7 @@ Contains:
desc = "A special helmet that protects against radiation and space. Not much else unfortunately."
icon_state = "cespace_helmet"
item_state = "nothing"
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 0, "acid" = 0)
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 0, "acid" = 0, "wound" = 5)
resistance_flags = FIRE_PROOF
rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE
actions_types = list()
@@ -329,7 +329,7 @@ Contains:
desc = "A special suit that protects against radiation and space. Not much else unfortunately."
icon_state = "hardsuit-rad"
item_state = "nothing"
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 0, "acid" = 0)
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 0, "acid" = 0, "wound" = 5)
resistance_flags = FIRE_PROOF
rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE
mutantrace_variation = NONE
@@ -339,7 +339,7 @@ Contains:
desc = "An advanced, space-proof helmet. It appears to be modeled after an old-world eagle."
icon_state = "griffinhat"
item_state = "griffinhat"
- armor = list("melee" = 20, "bullet" = 40, "laser" = 30, "energy" = 25, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 80, "acid" = 80)
+ armor = list("melee" = 20, "bullet" = 40, "laser" = 30, "energy" = 25, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 80, "acid" = 80, "wound" = 20)
strip_delay = 130
max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT
resistance_flags = ACID_PROOF | FIRE_PROOF
@@ -351,7 +351,7 @@ Contains:
icon_state = "freedom"
item_state = "freedom"
allowed = list(/obj/item/gun, /obj/item/ammo_box, /obj/item/ammo_casing, /obj/item/melee/baton, /obj/item/restraints/handcuffs, /obj/item/tank/internals)
- armor = list("melee" = 20, "bullet" = 40, "laser" = 30,"energy" = 25, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 80, "acid" = 80)
+ armor = list("melee" = 20, "bullet" = 40, "laser" = 30,"energy" = 25, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 80, "acid" = 80, "wound" = 20)
strip_delay = 130
max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT
resistance_flags = ACID_PROOF | FIRE_PROOF
@@ -364,7 +364,7 @@ Contains:
desc = "Spaceworthy and it looks like a space carp's head, smells like one too."
icon_state = "carp_helm"
item_state = "syndicate"
- armor = list("melee" = -20, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 75, "fire" = 60, "acid" = 75) //As whimpy as a space carp
+ armor = list("melee" = -20, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 75, "fire" = 60, "acid" = 75, "wound" = 5) //As whimpy as a space carp
brightness_on = 0 //luminosity when on
actions_types = list()
mutantrace_variation = NONE
@@ -380,7 +380,7 @@ Contains:
icon_state = "carp_suit"
item_state = "space_suit_syndicate"
slowdown = 0 //Space carp magic, never stop believing
- armor = list("melee" = -20, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 75, "fire" = 60, "acid" = 75) //As whimpy whimpy whoo
+ armor = list("melee" = -20, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 75, "fire" = 60, "acid" = 75, "wound" = 5) //As whimpy whimpy whoo
allowed = list(/obj/item/tank/internals, /obj/item/gun/ballistic/automatic/speargun) //I'm giving you a hint here
helmettype = /obj/item/clothing/head/helmet/space/hardsuit/carp
mutantrace_variation = STYLE_DIGITIGRADE
@@ -442,14 +442,14 @@ Contains:
/obj/item/clothing/suit/space/hardsuit/ert/paranormal/inquisitor/old
desc = "Powerful wards are built into this hardsuit, protecting the user from all manner of paranormal threats. Alas, this one looks pretty worn out and rusted."
- armor = list("melee" = 55, "bullet" = 40, "laser" = 40, "energy" = 40, "bomb" = 40, "bio" = 80, "rad" = 80, "fire" = 60, "acid" = 60)
+ armor = list("melee" = 55, "bullet" = 40, "laser" = 40, "energy" = 40, "bomb" = 40, "bio" = 80, "rad" = 80, "fire" = 60, "acid" = 60, "wound" = 20)
slowdown = 0.8
helmettype = /obj/item/clothing/head/helmet/space/hardsuit/ert/paranormal/inquisitor/old
charges = 12
/obj/item/clothing/head/helmet/space/hardsuit/ert/paranormal/inquisitor/old
desc = "A helmet worn by those who deal with paranormal threats for a living. Alas, this one looks pretty worn out and rusted."
- armor = list("melee" = 55, "bullet" = 40, "laser" = 40, "energy" = 40, "bomb" = 40, "bio" = 80, "rad" = 80, "fire" = 60, "acid" = 60)
+ armor = list("melee" = 55, "bullet" = 40, "laser" = 40, "energy" = 40, "bomb" = 40, "bio" = 80, "rad" = 80, "fire" = 60, "acid" = 60, "wound" = 20)
charges = 12
/obj/item/clothing/suit/space/hardsuit/ert/paranormal/beserker
@@ -467,14 +467,14 @@ Contains:
/obj/item/clothing/suit/space/hardsuit/ert/paranormal/beserker/old
desc = "Voices echo from the hardsuit, driving the user insane. This one is pretty battle-worn, but still fearsome."
- armor = list("melee" = 55, "bullet" = 40, "laser" = 40, "energy" = 40, "bomb" = 40, "bio" = 80, "rad" = 80, "fire" = 60, "acid" = 60)
+ armor = list("melee" = 55, "bullet" = 40, "laser" = 40, "energy" = 40, "bomb" = 40, "bio" = 80, "rad" = 80, "fire" = 60, "acid" = 60, "wound" = 20)
slowdown = 0.8
helmettype = /obj/item/clothing/head/helmet/space/hardsuit/ert/paranormal/beserker/old
charges = 6
/obj/item/clothing/head/helmet/space/hardsuit/ert/paranormal/beserker/old
desc = "Peering into the eyes of the helmet is enough to seal damnation. This one is pretty battle-worn, but still fearsome."
- armor = list("melee" = 55, "bullet" = 40, "laser" = 40, "energy" = 40, "bomb" = 40, "bio" = 80, "rad" = 80, "fire" = 60, "acid" = 60)
+ armor = list("melee" = 55, "bullet" = 40, "laser" = 40, "energy" = 40, "bomb" = 40, "bio" = 80, "rad" = 80, "fire" = 60, "acid" = 60, "wound" = 20)
charges = 6
/obj/item/clothing/head/helmet/space/fragile
@@ -482,7 +482,7 @@ Contains:
desc = "A bulky, air-tight helmet meant to protect the user during emergency situations. It doesn't look very durable."
icon_state = "syndicate-helm-orange"
item_state = "syndicate-helm-orange"
- armor = list("melee" = 5, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 10, "fire" = 0, "acid" = 0)
+ armor = list("melee" = 5, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 10, "fire" = 0, "acid" = 0, "wound" = 5)
strip_delay = 65
/obj/item/clothing/suit/space/fragile
@@ -492,7 +492,7 @@ Contains:
icon_state = "syndicate-orange"
item_state = "syndicate-orange"
slowdown = 2
- armor = list("melee" = 5, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 10, "fire" = 0, "acid" = 0)
+ armor = list("melee" = 5, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 10, "fire" = 0, "acid" = 0, "wound" = 5)
strip_delay = 65
/obj/item/clothing/suit/space/fragile/run_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return)
@@ -525,7 +525,7 @@ Contains:
icon_state = "hunter"
item_state = "swat_suit"
allowed = list(/obj/item/gun, /obj/item/ammo_box, /obj/item/ammo_casing, /obj/item/melee/baton, /obj/item/restraints/handcuffs, /obj/item/tank/internals, /obj/item/kitchen/knife/combat)
- armor = list("melee" = 60, "bullet" = 40, "laser" = 40, "energy" = 50, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 100)
+ armor = list("melee" = 60, "bullet" = 40, "laser" = 40, "energy" = 50, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 100, "wpound" = 25)
strip_delay = 130
resistance_flags = FIRE_PROOF | ACID_PROOF
diff --git a/code/modules/clothing/spacesuits/plasmamen.dm b/code/modules/clothing/spacesuits/plasmamen.dm
index f17eb91b74..5128f77433 100644
--- a/code/modules/clothing/spacesuits/plasmamen.dm
+++ b/code/modules/clothing/spacesuits/plasmamen.dm
@@ -5,7 +5,7 @@
name = "EVA plasma envirosuit"
desc = "A special plasma containment suit designed to be space-worthy, as well as worn over other clothing. Like its smaller counterpart, it can automatically extinguish the wearer in a crisis, and holds twice as many charges."
allowed = list(/obj/item/gun, /obj/item/ammo_casing, /obj/item/ammo_casing, /obj/item/melee/baton, /obj/item/melee/transforming/energy/sword, /obj/item/restraints/handcuffs, /obj/item/tank)
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 0, "fire" = 100, "acid" = 75)
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 0, "fire" = 100, "acid" = 75, "wound" = 10)
resistance_flags = FIRE_PROOF
icon_state = "plasmaman_suit"
item_state = "plasmaman_suit"
@@ -40,7 +40,7 @@
icon_state = "plasmaman-helm"
item_state = "plasmaman-helm"
strip_delay = 80
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 0, "fire" = 100, "acid" = 75)
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 0, "fire" = 100, "acid" = 75, "wound" = 10)
resistance_flags = FIRE_PROOF
var/brightness_on = 4 //luminosity when the light is on
var/on = FALSE
@@ -77,7 +77,7 @@
desc = "A plasmaman containment helmet designed for security officers, protecting them from being flashed and burning alive, along-side other undesirables."
icon_state = "security_envirohelm"
item_state = "security_envirohelm"
- armor = list("melee" = 10, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 0, "fire" = 100, "acid" = 75)
+ armor = list("melee" = 10, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 0, "fire" = 100, "acid" = 75, "wound" = 20)
/obj/item/clothing/head/helmet/space/plasmaman/security/warden
name = "warden's plasma envirosuit helmet"
@@ -132,7 +132,7 @@
desc = "A sturdier plasmaman envirohelmet designed for research directors."
icon_state = "rd_envirohelm"
item_state = "rd_envirohelm"
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 10, "bio" = 100, "rad" = 0, "fire" = 100, "acid" = 75)
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 10, "bio" = 100, "rad" = 0, "fire" = 100, "acid" = 75, "wound" = 10)
/obj/item/clothing/head/helmet/space/plasmaman/robotics
name = "robotics plasma envirosuit helmet"
@@ -145,7 +145,7 @@
desc = "A space-worthy helmet specially designed for engineer plasmamen, the usual purple stripes being replaced by engineering's orange."
icon_state = "engineer_envirohelm"
item_state = "engineer_envirohelm"
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 10, "fire" = 100, "acid" = 75)
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 10, "fire" = 100, "acid" = 75, "wound" = 10)
/obj/item/clothing/head/helmet/space/plasmaman/engineering/ce
name = "chief engineer's plasma envirosuit helmet"
@@ -194,7 +194,7 @@
desc = "A blue and gold envirohelm designed for the station's captain, nonetheless. Made of superior materials to protect them from the station hazards and more."
icon_state = "captain_envirohelm"
item_state = "captain_envirohelm"
- armor = list("melee" = 10, "bullet" = 10, "laser" = 10, "energy" = 10, "bomb" = 10, "bio" = 100, "rad" = 10, "fire" = 100, "acid" = 85)
+ armor = list("melee" = 10, "bullet" = 10, "laser" = 10, "energy" = 10, "bomb" = 10, "bio" = 100, "rad" = 10, "fire" = 100, "acid" = 85, "wound" = 15)
/obj/item/clothing/head/helmet/space/plasmaman/curator
name = "curator's plasma envirosuit helmet"
diff --git a/code/modules/clothing/spacesuits/syndi.dm b/code/modules/clothing/spacesuits/syndi.dm
index 662e333f59..f55379da2f 100644
--- a/code/modules/clothing/spacesuits/syndi.dm
+++ b/code/modules/clothing/spacesuits/syndi.dm
@@ -4,7 +4,7 @@
icon_state = "syndicate"
item_state = "syndicate"
desc = "Has a tag on it: Totally not property of an enemy corporation, honest!"
- armor = list("melee" = 40, "bullet" = 50, "laser" = 30,"energy" = 15, "bomb" = 30, "bio" = 30, "rad" = 30, "fire" = 80, "acid" = 85)
+ armor = list("melee" = 40, "bullet" = 50, "laser" = 30,"energy" = 15, "bomb" = 30, "bio" = 30, "rad" = 30, "fire" = 80, "acid" = 85, "wound" = 20)
/obj/item/clothing/suit/space/syndicate
name = "red space suit"
@@ -13,7 +13,7 @@
desc = "Has a tag on it: Totally not property of an enemy corporation, honest!"
w_class = WEIGHT_CLASS_NORMAL
allowed = list(/obj/item/gun, /obj/item/ammo_box, /obj/item/ammo_casing, /obj/item/melee/baton, /obj/item/melee/transforming/energy/sword/saber, /obj/item/restraints/handcuffs, /obj/item/tank/internals)
- armor = list("melee" = 40, "bullet" = 50, "laser" = 30,"energy" = 15, "bomb" = 30, "bio" = 30, "rad" = 30, "fire" = 80, "acid" = 85)
+ armor = list("melee" = 40, "bullet" = 50, "laser" = 30,"energy" = 15, "bomb" = 30, "bio" = 30, "rad" = 30, "fire" = 80, "acid" = 85, "wound" = 20)
mutantrace_variation = STYLE_DIGITIGRADE
//Green syndicate space suit
diff --git a/code/modules/clothing/suits/armor.dm b/code/modules/clothing/suits/armor.dm
index b61743729f..6732f1c86c 100644
--- a/code/modules/clothing/suits/armor.dm
+++ b/code/modules/clothing/suits/armor.dm
@@ -8,7 +8,7 @@
equip_delay_other = 40
max_integrity = 250
resistance_flags = NONE
- armor = list("melee" = 30, "bullet" = 30, "laser" = 30, "energy" = 10, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50, "wound" = 15)
+ armor = list("melee" = 30, "bullet" = 30, "laser" = 30, "energy" = 10, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50, "wound" = 10)
/obj/item/clothing/suit/armor/Initialize()
@@ -58,7 +58,7 @@
icon_state = "hos"
item_state = "greatcoat"
body_parts_covered = CHEST|GROIN|ARMS|LEGS
- armor = list("melee" = 30, "bullet" = 30, "laser" = 30, "energy" = 10, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 70, "acid" = 90, "wound" = 20)
+ armor = list("melee" = 30, "bullet" = 30, "laser" = 30, "energy" = 10, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 70, "acid" = 90, "wound" = 10)
cold_protection = CHEST|GROIN|LEGS|ARMS
heat_protection = CHEST|GROIN|LEGS|ARMS
strip_delay = 80
@@ -80,7 +80,9 @@
item_state = "hostrench"
flags_inv = 0
strip_delay = 80
- unique_reskin = list("Coat" = "hostrench", "Cloak" = "trenchcloak")
+ unique_reskin = list("Coat" = "hostrench",
+ "Cloak" = "trenchcloak"
+ )
/obj/item/clothing/suit/armor/vest/warden
name = "warden's jacket"
@@ -148,7 +150,7 @@
body_parts_covered = CHEST|GROIN|LEGS|FEET|ARMS|HANDS
cold_protection = CHEST|GROIN|LEGS|FEET|ARMS|HANDS
heat_protection = CHEST|GROIN|LEGS|FEET|ARMS|HANDS
- armor = list("melee" = 50, "bullet" = 10, "laser" = 10, "energy" = 10, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 80, "wound" = 30)
+ armor = list("melee" = 50, "bullet" = 10, "laser" = 10, "energy" = 10, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 80, "wound" = 20)
blocks_shove_knockdown = TRUE
strip_delay = 80
equip_delay_other = 60
diff --git a/code/modules/clothing/suits/miscellaneous.dm b/code/modules/clothing/suits/miscellaneous.dm
index 0b1121b22b..d6853f52ca 100644
--- a/code/modules/clothing/suits/miscellaneous.dm
+++ b/code/modules/clothing/suits/miscellaneous.dm
@@ -543,6 +543,7 @@
cold_protection = HEAD
min_cold_protection_temperature = FIRE_SUIT_MIN_TEMP_PROTECT
flags_inv = HIDEHAIR|HIDEEARS
+ rad_flags = RAD_NO_CONTAMINATE
/obj/item/clothing/suit/hooded/wintercoat/centcom
name = "centcom winter coat"
@@ -624,7 +625,7 @@
desc = "An arctic white winter coat with a small blue caduceus instead of a plastic zipper tab. Snazzy."
icon_state = "coatmedical"
item_state = "coatmedical"
- allowed = list(/obj/item/analyzer, /obj/item/stack/medical, /obj/item/dnainjector, /obj/item/reagent_containers/dropper, /obj/item/reagent_containers/syringe, /obj/item/reagent_containers/hypospray, /obj/item/healthanalyzer, /obj/item/flashlight/pen, /obj/item/reagent_containers/glass/bottle, /obj/item/reagent_containers/glass/beaker, /obj/item/reagent_containers/pill, /obj/item/storage/pill_bottle, /obj/item/paper, /obj/item/melee/classic_baton/telescopic, /obj/item/toy, /obj/item/storage/fancy/cigarettes, /obj/item/lighter, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman)
+ allowed = list(/obj/item/analyzer, /obj/item/sensor_device, /obj/item/stack/medical, /obj/item/dnainjector, /obj/item/reagent_containers/dropper, /obj/item/reagent_containers/syringe, /obj/item/reagent_containers/hypospray, /obj/item/healthanalyzer, /obj/item/flashlight/pen, /obj/item/reagent_containers/glass/bottle, /obj/item/reagent_containers/glass/beaker, /obj/item/reagent_containers/pill, /obj/item/storage/pill_bottle, /obj/item/paper, /obj/item/melee/classic_baton/telescopic, /obj/item/toy, /obj/item/storage/fancy/cigarettes, /obj/item/lighter, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman)
armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 50, "rad" = 0, "fire" = 0, "acid" = 45)
hoodtype = /obj/item/clothing/head/hooded/winterhood/medical
@@ -637,7 +638,7 @@
desc = "An arctic white winter coat with a small blue caduceus instead of a plastic zipper tab. The normal liner is replaced with an exceptionally thick, soft layer of fur."
icon_state = "coatcmo"
item_state = "coatcmo"
- allowed = list(/obj/item/analyzer, /obj/item/stack/medical, /obj/item/dnainjector, /obj/item/reagent_containers/dropper, /obj/item/reagent_containers/syringe, /obj/item/reagent_containers/hypospray, /obj/item/healthanalyzer, /obj/item/flashlight/pen, /obj/item/reagent_containers/glass/bottle, /obj/item/reagent_containers/glass/beaker, /obj/item/reagent_containers/pill, /obj/item/storage/pill_bottle, /obj/item/paper, /obj/item/melee/classic_baton/telescopic, /obj/item/toy, /obj/item/storage/fancy/cigarettes, /obj/item/lighter, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman)
+ allowed = list(/obj/item/analyzer, /obj/item/sensor_device, /obj/item/stack/medical, /obj/item/dnainjector, /obj/item/reagent_containers/dropper, /obj/item/reagent_containers/syringe, /obj/item/reagent_containers/hypospray, /obj/item/healthanalyzer, /obj/item/flashlight/pen, /obj/item/reagent_containers/glass/bottle, /obj/item/reagent_containers/glass/beaker, /obj/item/reagent_containers/pill, /obj/item/storage/pill_bottle, /obj/item/paper, /obj/item/melee/classic_baton/telescopic, /obj/item/toy, /obj/item/storage/fancy/cigarettes, /obj/item/lighter, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman)
armor = list("melee" = 5, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 50, "rad" = 0, "fire" = 0, "acid" = 0)
hoodtype = /obj/item/clothing/head/hooded/winterhood/cmo
@@ -650,7 +651,7 @@
desc = "A lab-grade winter coat made with acid resistant polymers. For the enterprising chemist who was exiled to a frozen wasteland on the go."
icon_state = "coatchemistry"
item_state = "coatchemistry"
- allowed = list(/obj/item/analyzer, /obj/item/stack/medical, /obj/item/dnainjector, /obj/item/reagent_containers/dropper, /obj/item/reagent_containers/syringe, /obj/item/reagent_containers/hypospray, /obj/item/healthanalyzer, /obj/item/flashlight/pen, /obj/item/reagent_containers/glass/bottle, /obj/item/reagent_containers/glass/beaker, /obj/item/reagent_containers/pill, /obj/item/storage/pill_bottle, /obj/item/paper, /obj/item/melee/classic_baton/telescopic, /obj/item/toy, /obj/item/storage/fancy/cigarettes, /obj/item/lighter, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman)
+ allowed = list(/obj/item/analyzer, /obj/item/sensor_device, /obj/item/stack/medical, /obj/item/dnainjector, /obj/item/reagent_containers/dropper, /obj/item/reagent_containers/syringe, /obj/item/reagent_containers/hypospray, /obj/item/healthanalyzer, /obj/item/flashlight/pen, /obj/item/reagent_containers/glass/bottle, /obj/item/reagent_containers/glass/beaker, /obj/item/reagent_containers/pill, /obj/item/storage/pill_bottle, /obj/item/paper, /obj/item/melee/classic_baton/telescopic, /obj/item/toy, /obj/item/storage/fancy/cigarettes, /obj/item/lighter, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman)
armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 30, "rad" = 0, "fire" = 30, "acid" = 45)
hoodtype = /obj/item/clothing/head/hooded/winterhood/chemistry
@@ -663,7 +664,7 @@
desc = "A white winter coat with green markings. Warm, but wont fight off the common cold or any other disease. Might make people stand far away from you in the hallway. The zipper tab looks like an oversized bacteriophage."
icon_state = "coatviro"
item_state = "coatviro"
- allowed = list(/obj/item/analyzer, /obj/item/stack/medical, /obj/item/dnainjector, /obj/item/reagent_containers/dropper, /obj/item/reagent_containers/syringe, /obj/item/reagent_containers/hypospray, /obj/item/healthanalyzer, /obj/item/flashlight/pen, /obj/item/reagent_containers/glass/bottle, /obj/item/reagent_containers/glass/beaker, /obj/item/reagent_containers/pill, /obj/item/storage/pill_bottle, /obj/item/paper, /obj/item/melee/classic_baton/telescopic, /obj/item/toy, /obj/item/storage/fancy/cigarettes, /obj/item/lighter, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman)
+ allowed = list(/obj/item/analyzer, /obj/item/sensor_device, /obj/item/stack/medical, /obj/item/dnainjector, /obj/item/reagent_containers/dropper, /obj/item/reagent_containers/syringe, /obj/item/reagent_containers/hypospray, /obj/item/healthanalyzer, /obj/item/flashlight/pen, /obj/item/reagent_containers/glass/bottle, /obj/item/reagent_containers/glass/beaker, /obj/item/reagent_containers/pill, /obj/item/storage/pill_bottle, /obj/item/paper, /obj/item/melee/classic_baton/telescopic, /obj/item/toy, /obj/item/storage/fancy/cigarettes, /obj/item/lighter, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman)
armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 30, "rad" = 0, "fire" = 0, "acid" = 0)
hoodtype = /obj/item/clothing/head/hooded/winterhood/viro
@@ -676,7 +677,7 @@
desc = "A winter coat with blue markings. Warm, but probably won't protect from biological agents. For the cozy doctor on the go."
icon_state = "coatparamed"
item_state = "coatparamed"
- allowed = list(/obj/item/analyzer, /obj/item/stack/medical, /obj/item/dnainjector, /obj/item/reagent_containers/dropper, /obj/item/reagent_containers/syringe, /obj/item/reagent_containers/hypospray, /obj/item/healthanalyzer, /obj/item/flashlight/pen, /obj/item/reagent_containers/glass/bottle, /obj/item/reagent_containers/glass/beaker, /obj/item/reagent_containers/pill, /obj/item/storage/pill_bottle, /obj/item/paper, /obj/item/melee/classic_baton/telescopic, /obj/item/toy, /obj/item/storage/fancy/cigarettes, /obj/item/lighter, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman)
+ allowed = list(/obj/item/analyzer, /obj/item/sensor_device, /obj/item/stack/medical, /obj/item/dnainjector, /obj/item/reagent_containers/dropper, /obj/item/reagent_containers/syringe, /obj/item/reagent_containers/hypospray, /obj/item/healthanalyzer, /obj/item/flashlight/pen, /obj/item/reagent_containers/glass/bottle, /obj/item/reagent_containers/glass/beaker, /obj/item/reagent_containers/pill, /obj/item/storage/pill_bottle, /obj/item/paper, /obj/item/melee/classic_baton/telescopic, /obj/item/toy, /obj/item/storage/fancy/cigarettes, /obj/item/lighter, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman)
armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 50, "rad" = 0, "fire" = 0, "acid" = 45)
hoodtype = /obj/item/clothing/head/hooded/winterhood/paramedic
@@ -1057,3 +1058,9 @@
desc = "Reminds you of someone, but you just can't put your finger on it..."
icon_state = "waldo_shirt"
item_state = "waldo_shirt"
+
+/obj/item/clothing/suit/samurai
+ name = "Samurai outfit"
+ desc = "An outfit used by traditional japanese warriors."
+ icon_state = "samurai"
+ item_state = "samurai"
diff --git a/code/modules/clothing/suits/toggles.dm b/code/modules/clothing/suits/toggles.dm
index 632d59187f..f496468371 100644
--- a/code/modules/clothing/suits/toggles.dm
+++ b/code/modules/clothing/suits/toggles.dm
@@ -5,9 +5,9 @@
var/obj/item/clothing/head/hooded/hood
var/hoodtype = /obj/item/clothing/head/hooded/winterhood //so the chaplain hoodie or other hoodies can override this
-/obj/item/clothing/suit/hooded/New()
+/obj/item/clothing/suit/hooded/Initialize()
+ . = ..()
hood = MakeHelmet()
- ..()
/obj/item/clothing/suit/hooded/Destroy()
. = ..()
@@ -48,7 +48,7 @@
/obj/item/clothing/suit/hooded/update_icon_state()
icon_state = "[initial(icon_state)]"
- if(ishuman(hood.loc))
+ if(ishuman(hood?.loc))
var/mob/living/carbon/human/H = hood.loc
if(H.head == hood)
icon_state += "_t"
@@ -131,8 +131,8 @@
//Hardsuit toggle code
/obj/item/clothing/suit/space/hardsuit/Initialize()
- helmet = MakeHelmet()
. = ..()
+ helmet = MakeHelmet()
/obj/item/clothing/suit/space/hardsuit/Destroy()
if(helmet)
diff --git a/code/modules/clothing/under/accessories.dm b/code/modules/clothing/under/accessories.dm
index cb173f3bde..ee7e4c48e1 100644
--- a/code/modules/clothing/under/accessories.dm
+++ b/code/modules/clothing/under/accessories.dm
@@ -369,3 +369,12 @@
icon_state = "plastics"
armor = list("melee" = 0, "bullet" = 0, "laser" = 20, "energy" = 10, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 20, "acid" = -40)
flags_inv = HIDEACCESSORY
+
+//necklace
+/obj/item/clothing/accessory/necklace
+ name = "necklace"
+ desc = "A necklace."
+ icon_state = "locket"
+ obj_flags = UNIQUE_RENAME
+ custom_materials = list(/datum/material/iron=100)
+ resistance_flags = FIRE_PROOF
diff --git a/code/modules/clothing/under/costume.dm b/code/modules/clothing/under/costume.dm
index 3e7bc755cb..f8292738ee 100644
--- a/code/modules/clothing/under/costume.dm
+++ b/code/modules/clothing/under/costume.dm
@@ -267,7 +267,7 @@
mutantrace_variation = STYLE_DIGITIGRADE|STYLE_NO_ANTHRO_ICON|USE_TAUR_CLIP_MASK
/obj/item/clothing/under/costume/christmas/croptop/green
- name = "green feminine christmas suit"
+ name = "green croptop christmas suit"
desc = "A simple green christmas suit. Smells minty!"
icon_state = "christmasfemaleg"
item_state = "christmasfemaleg"
@@ -329,3 +329,29 @@
desc = "cloud"
icon_state = "cloud"
can_adjust = FALSE
+
+/obj/item/clothing/under/costume/kimono
+ name = "Kimono"
+ desc = "A traditional piece of clothing from japan"
+ icon_state = "kimono"
+ item_state = "kimono"
+
+/obj/item/clothing/under/costume/kimono/black
+ name = "Black Kimono"
+ icon_state = "kimono_a"
+ item_state = "kimono_a"
+
+/obj/item/clothing/under/costume/kimono/kamishimo
+ name = "Kamishimo"
+ icon_state = "kamishimo"
+ item_state = "kamishimo"
+
+/obj/item/clothing/under/costume/kimono/fancy
+ name = "Fancy Kimono"
+ icon_state = "fancy_kimono"
+ item_state = "fancy_kimono"
+
+/obj/item/clothing/under/costume/kimono/sakura
+ name = "Sakura Kimono'"
+ icon_state = "sakura_kimono"
+ item_state = "sakura_kimono"
diff --git a/code/modules/clothing/under/jobs/Plasmaman/civilian_service.dm b/code/modules/clothing/under/jobs/Plasmaman/civilian_service.dm
index 5928819b16..082d783bea 100644
--- a/code/modules/clothing/under/jobs/Plasmaman/civilian_service.dm
+++ b/code/modules/clothing/under/jobs/Plasmaman/civilian_service.dm
@@ -57,7 +57,7 @@
desc = "An expensive piece of plasmaman envirosuit fashion. guaranteed to keep you cool while the station goes down in fierceful fires."
icon_state = "captain_envirosuit"
item_state = "captain_envirosuit"
- armor = list("melee" = 10, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 0, "fire" = 95, "acid" = 95)
+ armor = list("melee" = 10, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 0, "fire" = 95, "acid" = 95, "wound" = 15)
sensor_mode = SENSOR_COORDS
random_sensor = FALSE
diff --git a/code/modules/clothing/under/jobs/Plasmaman/engineering.dm b/code/modules/clothing/under/jobs/Plasmaman/engineering.dm
index 15eb189fa8..4850a605e7 100644
--- a/code/modules/clothing/under/jobs/Plasmaman/engineering.dm
+++ b/code/modules/clothing/under/jobs/Plasmaman/engineering.dm
@@ -3,7 +3,7 @@
desc = "An air-tight suit designed to be used by plasmamen exployed as engineers, the usual purple stripes being replaced by engineer's orange. It protects the user from fire and acid damage."
icon_state = "engineer_envirosuit"
item_state = "engineer_envirosuit"
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 10, "fire" = 95, "acid" = 95)
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 10, "fire" = 95, "acid" = 95, "wound" = 5)
/obj/item/clothing/under/plasmaman/engineering/ce
name = "chief engineer's plasma envirosuit"
diff --git a/code/modules/clothing/under/jobs/Plasmaman/medsci.dm b/code/modules/clothing/under/jobs/Plasmaman/medsci.dm
index 03d089c10d..52f817dcce 100644
--- a/code/modules/clothing/under/jobs/Plasmaman/medsci.dm
+++ b/code/modules/clothing/under/jobs/Plasmaman/medsci.dm
@@ -21,7 +21,7 @@
desc = "A plasmaman envirosuit designed for the research director to aid them in their job of directing research into the right direction."
icon_state = "rd_envirosuit"
item_state = "rd_envirosuit"
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 10, "bio" = 100, "rad" = 0, "fire" = 95, "acid" = 95)
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 10, "bio" = 100, "rad" = 0, "fire" = 95, "acid" = 95, "wound" = 5)
/obj/item/clothing/under/plasmaman/robotics
name = "robotics plasma envirosuit"
diff --git a/code/modules/clothing/under/jobs/Plasmaman/security.dm b/code/modules/clothing/under/jobs/Plasmaman/security.dm
index ddbda041f3..3330d72844 100644
--- a/code/modules/clothing/under/jobs/Plasmaman/security.dm
+++ b/code/modules/clothing/under/jobs/Plasmaman/security.dm
@@ -3,7 +3,7 @@
desc = "A plasmaman containment suit designed for security officers, offering a limited amount of extra protection."
icon_state = "security_envirosuit"
item_state = "security_envirosuit"
- armor = list("melee" = 10, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 0, "fire" = 95, "acid" = 95)
+ armor = list("melee" = 10, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 0, "fire" = 95, "acid" = 95, "wound" = 10)
sensor_mode = SENSOR_COORDS
random_sensor = FALSE
diff --git a/code/modules/clothing/under/jobs/civilian/civilian.dm b/code/modules/clothing/under/jobs/civilian/civilian.dm
index 4eb6a18258..5dffc8f88d 100644
--- a/code/modules/clothing/under/jobs/civilian/civilian.dm
+++ b/code/modules/clothing/under/jobs/civilian/civilian.dm
@@ -110,7 +110,7 @@
desc = "It's the official uniform of the station's janitor. It has minor protection from biohazards."
name = "janitor's jumpsuit"
icon_state = "janitor"
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 10, "rad" = 0, "fire" = 0, "acid" = 0)
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 10, "rad" = 0, "fire" = 0, "acid" = 0, "wound" = 5)
/obj/item/clothing/under/rank/civilian/janitor/skirt
name = "janitor's jumpskirt"
diff --git a/code/modules/clothing/under/jobs/engineering.dm b/code/modules/clothing/under/jobs/engineering.dm
index 3eaaa42620..5693468b3b 100644
--- a/code/modules/clothing/under/jobs/engineering.dm
+++ b/code/modules/clothing/under/jobs/engineering.dm
@@ -4,7 +4,7 @@
name = "chief engineer's jumpsuit"
icon_state = "chiefengineer"
item_state = "gy_suit"
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 10, "fire" = 80, "acid" = 40)
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 10, "fire" = 80, "acid" = 40, "wound" = 5)
resistance_flags = NONE
/obj/item/clothing/under/rank/engineering/chief_engineer/skirt
@@ -39,7 +39,7 @@
name = "engineer's jumpsuit"
icon_state = "engine"
item_state = "engi_suit"
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 10, "fire" = 60, "acid" = 20)
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 10, "fire" = 60, "acid" = 20, "wound" = 5)
resistance_flags = NONE
/obj/item/clothing/under/rank/engineering/engineer/hazard
diff --git a/code/modules/clothing/under/jobs/medical.dm b/code/modules/clothing/under/jobs/medical.dm
index c4eedf93a4..c66b972624 100644
--- a/code/modules/clothing/under/jobs/medical.dm
+++ b/code/modules/clothing/under/jobs/medical.dm
@@ -4,7 +4,7 @@
icon_state = "cmo"
item_state = "w_suit"
permeability_coefficient = 0.5
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 10, "rad" = 0, "fire" = 0, "acid" = 0)
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 10, "rad" = 0, "fire" = 0, "acid" = 0, "wound" = 5)
/obj/item/clothing/under/rank/medical/chief_medical_officer/skirt
name = "chief medical officer's jumpskirt"
@@ -30,7 +30,7 @@
icon_state = "genetics"
item_state = "w_suit"
permeability_coefficient = 0.5
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 10, "rad" = 0, "fire" = 0, "acid" = 0)
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 10, "rad" = 0, "fire" = 0, "acid" = 0, "wound" = 5)
/obj/item/clothing/under/rank/medical/geneticist/skirt
name = "geneticist's jumpskirt"
@@ -48,7 +48,7 @@
icon_state = "virology"
item_state = "w_suit"
permeability_coefficient = 0.5
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 10, "rad" = 0, "fire" = 0, "acid" = 0)
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 10, "rad" = 0, "fire" = 0, "acid" = 0, "wound" = 5)
/obj/item/clothing/under/rank/medical/virologist/skirt
name = "virologist's jumpskirt"
@@ -66,7 +66,7 @@
icon_state = "chemistry"
item_state = "w_suit"
permeability_coefficient = 0.5
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 10, "rad" = 0, "fire" = 50, "acid" = 65)
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 10, "rad" = 0, "fire" = 50, "acid" = 65, "wound" = 5)
/obj/item/clothing/under/rank/medical/chemist/skirt
name = "chemist's jumpskirt"
@@ -84,7 +84,7 @@
icon_state = "paramedic-dark"
item_state = "w_suit"
permeability_coefficient = 0.5
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 10, "rad" = 0, "fire" = 0, "acid" = 0)
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 10, "rad" = 0, "fire" = 0, "acid" = 0, "wound" = 5)
/obj/item/clothing/under/rank/medical/paramedic/light
desc = "It's made of a special fiber that provides minor protection against biohazards. It has a dark blue cross on the chest denoting that the wearer is a trained paramedic."
@@ -110,7 +110,7 @@
icon_state = "nursesuit"
item_state = "w_suit"
permeability_coefficient = 0.5
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 10, "rad" = 0, "fire" = 0, "acid" = 0)
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 10, "rad" = 0, "fire" = 0, "acid" = 0, "wound" = 5)
body_parts_covered = CHEST|GROIN|ARMS
fitted = NO_FEMALE_UNIFORM
can_adjust = FALSE
@@ -122,7 +122,7 @@
icon_state = "medical"
item_state = "w_suit"
permeability_coefficient = 0.5
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 10, "rad" = 0, "fire" = 0, "acid" = 0)
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 10, "rad" = 0, "fire" = 0, "acid" = 0, "wound" = 5)
/obj/item/clothing/under/rank/medical/doctor/blue
name = "blue medical scrubs"
diff --git a/code/modules/clothing/under/jobs/rnd.dm b/code/modules/clothing/under/jobs/rnd.dm
index f7bd6d5e33..03eb910736 100644
--- a/code/modules/clothing/under/jobs/rnd.dm
+++ b/code/modules/clothing/under/jobs/rnd.dm
@@ -3,7 +3,7 @@
name = "research director's vest suit"
icon_state = "director"
item_state = "lb_suit"
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 10, "bio" = 10, "rad" = 0, "fire" = 0, "acid" = 35)
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 10, "bio" = 10, "rad" = 0, "fire" = 0, "acid" = 35, "wound" = 5)
can_adjust = FALSE
/obj/item/clothing/under/rank/rnd/research_director/skirt
@@ -20,7 +20,7 @@
name = "research director's tan suit"
icon_state = "rdwhimsy"
item_state = "rdwhimsy"
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 10, "bio" = 10, "rad" = 0, "fire" = 0, "acid" = 0)
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 10, "bio" = 10, "rad" = 0, "fire" = 0, "acid" = 0, "wound" = 5)
can_adjust = TRUE
alt_covers_chest = TRUE
@@ -39,7 +39,7 @@
name = "research director's turtleneck"
icon_state = "rdturtle"
item_state = "p_suit"
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 10, "bio" = 10, "rad" = 0, "fire" = 0, "acid" = 0)
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 10, "bio" = 10, "rad" = 0, "fire" = 0, "acid" = 0, "wound" = 5)
can_adjust = TRUE
alt_covers_chest = TRUE
@@ -59,7 +59,7 @@
icon_state = "toxins"
item_state = "w_suit"
permeability_coefficient = 0.5
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0)
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0, "wound" = 5)
/obj/item/clothing/under/rank/rnd/scientist/skirt
name = "scientist's jumpskirt"
diff --git a/code/modules/clothing/under/miscellaneous.dm b/code/modules/clothing/under/miscellaneous.dm
index 997f10a379..85c1d0e114 100644
--- a/code/modules/clothing/under/miscellaneous.dm
+++ b/code/modules/clothing/under/miscellaneous.dm
@@ -66,7 +66,7 @@
gas_transfer_coefficient = 0.01
permeability_coefficient = 0.01
body_parts_covered = CHEST|GROIN|LEGS|FEET|ARMS|HANDS
- armor = list("melee" = 100, "bullet" = 100, "laser" = 100,"energy" = 100, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 100)
+ armor = list("melee" = 100, "bullet" = 100, "laser" = 100,"energy" = 100, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 100, "wound" = 1000) //wound defense at 100 wont stop wounds
cold_protection = CHEST | GROIN | LEGS | FEET | ARMS | HANDS
min_cold_protection_temperature = SPACE_SUIT_MIN_TEMP_PROTECT
heat_protection = CHEST|GROIN|LEGS|FEET|ARMS|HANDS
@@ -118,7 +118,6 @@
icon_state = "plasmaman"
item_state = "plasmaman"
armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 0, "fire" = 95, "acid" = 95)
- slowdown = 1
body_parts_covered = CHEST|GROIN|LEGS|FEET|ARMS|HANDS
mutantrace_variation = USE_TAUR_CLIP_MASK
can_adjust = FALSE
@@ -308,3 +307,21 @@
icon_state = "polyfemtankpantsu"
poly_states = 2
poly_colors = list("#808080", "#FF3535")
+
+/obj/item/clothing/under/misc/black_dress
+ name = "little black dress"
+ desc = "A small black dress"
+ icon_state = "littleblackdress_s"
+ item_state = "littleblackdress_s"
+
+/obj/item/clothing/under/misc/pinktutu
+ name = "pink tutu"
+ desc = "A pink tutu"
+ icon_state = "pinktutu_s"
+ item_state = "pinktutu_s"
+
+/obj/item/clothing/under/misc/bathrobe
+ name = "bathrobe"
+ desc = "A blue bathrobe."
+ icon_state = "bathrobe"
+ item_state = "bathrobe"
diff --git a/code/modules/clothing/under/syndicate.dm b/code/modules/clothing/under/syndicate.dm
index 6a6c38d26e..72af4e9572 100644
--- a/code/modules/clothing/under/syndicate.dm
+++ b/code/modules/clothing/under/syndicate.dm
@@ -4,7 +4,7 @@
icon_state = "syndicate"
item_state = "bl_suit"
has_sensor = NO_SENSORS
- armor = list("melee" = 10, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 40)
+ armor = list("melee" = 10, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 40, "wound" = 5)
alt_covers_chest = TRUE
/obj/item/clothing/under/syndicate/skirt
@@ -13,7 +13,7 @@
icon_state = "syndicate_skirt"
item_state = "bl_suit"
has_sensor = NO_SENSORS
- armor = list("melee" = 10, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 40)
+ armor = list("melee" = 10, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 40, "wound" = 5)
alt_covers_chest = TRUE
fitted = FEMALE_UNIFORM_TOP
mutantrace_variation = STYLE_DIGITIGRADE|STYLE_NO_ANTHRO_ICON
@@ -24,7 +24,7 @@
icon_state = "bloodred_pajamas"
item_state = "bl_suit"
dummy_thick = TRUE
- armor = list("melee" = 10, "bullet" = 10, "laser" = 10,"energy" = 10, "bomb" = 0, "bio" = 0, "rad" = 10, "fire" = 50, "acid" = 40)
+ armor = list("melee" = 10, "bullet" = 10, "laser" = 10,"energy" = 10, "bomb" = 0, "bio" = 0, "rad" = 10, "fire" = 50, "acid" = 40, "wound" = 10)
resistance_flags = FIRE_PROOF | ACID_PROOF
can_adjust = FALSE
@@ -33,21 +33,21 @@
desc = "Do operatives dream of nuclear sheep?"
icon_state = "bloodred_pajamas"
item_state = "bl_suit"
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 40)
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 40, "wound" = 5)
/obj/item/clothing/under/syndicate/tacticool
name = "tacticool turtleneck"
desc = "Just looking at it makes you want to buy an SKS, go into the woods, and -operate-."
icon_state = "tactifool"
item_state = "bl_suit"
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 40)
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 40, "wound" = 5)
/obj/item/clothing/under/syndicate/tacticool/skirt
name = "tacticool skirtleneck"
desc = "Just looking at it makes you want to buy an SKS, go into the woods, and -operate-."
icon_state = "tactifool_skirt"
item_state = "bl_suit"
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 40)
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 40, "wound" = 5)
fitted = FEMALE_UNIFORM_TOP
mutantrace_variation = STYLE_DIGITIGRADE|STYLE_NO_ANTHRO_ICON
@@ -57,7 +57,7 @@
icon_state = "tactifool"
item_state = "bl_suit"
has_sensor = TRUE
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0)
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0, "wound" = 5)
/obj/item/clothing/under/syndicate/sniper
name = "Tactical turtleneck suit"
@@ -81,7 +81,7 @@
desc = "Badly translated labels tell you to clean this in Vodka. Great for squatting in."
icon_state = "trackpants"
can_adjust = FALSE
- armor = list("melee" = 10, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0)
+ armor = list("melee" = 10, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0, "wound" = 5)
resistance_flags = NONE
/obj/item/clothing/under/syndicate/combat
@@ -96,7 +96,7 @@
desc = "Military grade tracksuits for frontline squatting."
icon_state = "rus_under"
can_adjust = FALSE
- armor = list("melee" = 5, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0)
+ armor = list("melee" = 5, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0, "wound" = 5)
resistance_flags = NONE
mutantrace_variation = STYLE_DIGITIGRADE|STYLE_NO_ANTHRO_ICON
@@ -106,7 +106,7 @@
icon_state = "syndicatebaseball"
item_state = "syndicatebaseball"
has_sensor = NO_SENSORS
- armor = list("melee" = 15, "bullet" = 5, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 40)
+ armor = list("melee" = 15, "bullet" = 5, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 40, "wound" = 10)
alt_covers_chest = TRUE
mutantrace_variation = USE_TAUR_CLIP_MASK
diff --git a/code/modules/emoji/emoji_parse.dm b/code/modules/emoji/emoji_parse.dm
index 33d32227e1..64c07f5a38 100644
--- a/code/modules/emoji/emoji_parse.dm
+++ b/code/modules/emoji/emoji_parse.dm
@@ -16,11 +16,16 @@
search = findtext(text, ":", pos + length(text[pos]))
if(search)
emoji = lowertext(copytext(text, pos + length(text[pos]), search))
- var/datum/asset/spritesheet/sheet = get_asset_datum(/datum/asset/spritesheet/goonchat)
+ var/isthisapath = (emoji[1] == "/") && text2path(emoji)
+ var/datum/asset/spritesheet/sheet = get_asset_datum(/datum/asset/spritesheet/chat)
var/tag = sheet.icon_tag("emoji-[emoji]")
if(tag)
parsed += "[tag]" //evil way of enforcing 16x16
pos = search + length(text[pos])
+ else if(ispath(isthisapath, /atom)) //path
+ var/atom/thisisanatom = isthisapath
+ parsed += "[icon2html(initial(thisisanatom.icon), world, initial(thisisanatom.icon_state))]"
+ pos = search + length(text[pos])
else
parsed += copytext(text, pos, search)
pos = search
diff --git a/code/modules/events/alien_infestation.dm b/code/modules/events/alien_infestation.dm
index 69e9a974eb..993577cb30 100644
--- a/code/modules/events/alien_infestation.dm
+++ b/code/modules/events/alien_infestation.dm
@@ -3,7 +3,7 @@
typepath = /datum/round_event/ghost_role/alien_infestation
weight = 5
gamemode_blacklist = list("dynamic")
- min_players = 10
+ min_players = 25
max_occurrences = 1
/datum/round_event/ghost_role/alien_infestation
diff --git a/code/modules/events/anomaly_bluespace.dm b/code/modules/events/anomaly_bluespace.dm
index 395b3b88a5..7f0dedaab6 100644
--- a/code/modules/events/anomaly_bluespace.dm
+++ b/code/modules/events/anomaly_bluespace.dm
@@ -4,7 +4,6 @@
max_occurrences = 1
weight = 5
- gamemode_blacklist = list("dynamic")
/datum/round_event/anomaly/anomaly_bluespace
startWhen = 3
diff --git a/code/modules/events/anomaly_flux.dm b/code/modules/events/anomaly_flux.dm
index a9a7ed50b9..8047976330 100644
--- a/code/modules/events/anomaly_flux.dm
+++ b/code/modules/events/anomaly_flux.dm
@@ -5,7 +5,6 @@
min_players = 10
max_occurrences = 5
weight = 20
- gamemode_blacklist = list("dynamic")
/datum/round_event/anomaly/anomaly_flux
startWhen = 10
diff --git a/code/modules/events/anomaly_grav.dm b/code/modules/events/anomaly_grav.dm
index cabd7face8..7d2bb33889 100644
--- a/code/modules/events/anomaly_grav.dm
+++ b/code/modules/events/anomaly_grav.dm
@@ -4,7 +4,6 @@
max_occurrences = 5
weight = 20
- gamemode_blacklist = list("dynamic")
/datum/round_event/anomaly/anomaly_grav
diff --git a/code/modules/events/anomaly_pyro.dm b/code/modules/events/anomaly_pyro.dm
index 8c8fbd6d36..350c8fc946 100644
--- a/code/modules/events/anomaly_pyro.dm
+++ b/code/modules/events/anomaly_pyro.dm
@@ -4,7 +4,6 @@
max_occurrences = 5
weight = 20
- gamemode_blacklist = list("dynamic")
/datum/round_event/anomaly/anomaly_pyro
startWhen = 3
diff --git a/code/modules/events/anomaly_vortex.dm b/code/modules/events/anomaly_vortex.dm
index 96d084873d..e2a4ceadf3 100644
--- a/code/modules/events/anomaly_vortex.dm
+++ b/code/modules/events/anomaly_vortex.dm
@@ -5,7 +5,6 @@
min_players = 20
max_occurrences = 2
weight = 5
- gamemode_blacklist = list("dynamic")
/datum/round_event/anomaly/anomaly_vortex
startWhen = 10
diff --git a/code/modules/events/brain_trauma.dm b/code/modules/events/brain_trauma.dm
index 3e8182a827..75c514774c 100644
--- a/code/modules/events/brain_trauma.dm
+++ b/code/modules/events/brain_trauma.dm
@@ -3,6 +3,13 @@
typepath = /datum/round_event/brain_trauma
weight = 25
+/datum/round_event_control/brain_trauma/canSpawnEvent(var/players_amt, var/gamemode)
+ var/list/enemy_roles = list("Medical Doctor","Chief Medical Officer","Paramedic")
+ for (var/mob/M in GLOB.alive_mob_list)
+ if(M.stat != DEAD && (M.mind?.assigned_role in enemy_roles))
+ return TRUE
+ return FALSE
+
/datum/round_event/brain_trauma
fakeable = FALSE
@@ -14,19 +21,21 @@
continue
if(!H.getorgan(/obj/item/organ/brain)) // If only I had a brain
continue
-
+ if(HAS_TRAIT(H,TRAIT_EXEMPT_HEALTH_EVENTS))
+ continue
+ if(!is_station_level(H.z))
+ continue
traumatize(H)
break
/datum/round_event/brain_trauma/proc/traumatize(mob/living/carbon/human/H)
var/resistance = pick(
65;TRAUMA_RESILIENCE_BASIC,
- 30;TRAUMA_RESILIENCE_SURGERY,
- 5;TRAUMA_RESILIENCE_LOBOTOMY)
+ 35;TRAUMA_RESILIENCE_SURGERY)
var/trauma_type = pickweight(list(
- BRAIN_TRAUMA_MILD = 60,
- BRAIN_TRAUMA_SEVERE = 30,
+ BRAIN_TRAUMA_MILD = 80,
+ BRAIN_TRAUMA_SEVERE = 10,
BRAIN_TRAUMA_SPECIAL = 10
))
diff --git a/code/modules/events/brand_intelligence.dm b/code/modules/events/brand_intelligence.dm
index da5b4c0cb2..f0e4bd4a53 100644
--- a/code/modules/events/brand_intelligence.dm
+++ b/code/modules/events/brand_intelligence.dm
@@ -5,7 +5,6 @@
min_players = 15
max_occurrences = 1
- gamemode_blacklist = list("dynamic")
/datum/round_event/brand_intelligence
announceWhen = 21
@@ -27,7 +26,6 @@
"How do I vore people?",
"ERP?",
"Not epic bros...")
- threat = 5
/datum/round_event/brand_intelligence/announce(fake)
diff --git a/code/modules/events/carp_migration.dm b/code/modules/events/carp_migration.dm
index 2c553fc8a7..d08e6267a0 100644
--- a/code/modules/events/carp_migration.dm
+++ b/code/modules/events/carp_migration.dm
@@ -5,7 +5,6 @@
min_players = 2
earliest_start = 10 MINUTES
max_occurrences = 6
- gamemode_blacklist = list("dynamic")
/datum/round_event/carp_migration
announceWhen = 3
diff --git a/code/modules/events/communications_blackout.dm b/code/modules/events/communications_blackout.dm
index 45fa1c8a01..cb62e0df22 100644
--- a/code/modules/events/communications_blackout.dm
+++ b/code/modules/events/communications_blackout.dm
@@ -2,7 +2,6 @@
name = "Communications Blackout"
typepath = /datum/round_event/communications_blackout
weight = 30
- gamemode_blacklist = list("dynamic")
/datum/round_event/communications_blackout
announceWhen = 1
diff --git a/code/modules/events/dust.dm b/code/modules/events/dust.dm
index 860685c787..eb7edcafbf 100644
--- a/code/modules/events/dust.dm
+++ b/code/modules/events/dust.dm
@@ -5,7 +5,6 @@
max_occurrences = 1000
earliest_start = 0 MINUTES
alert_observers = FALSE
- gamemode_blacklist = list("dynamic")
/datum/round_event/space_dust
startWhen = 1
@@ -29,4 +28,4 @@
fakeable = FALSE
/datum/round_event/sandstorm/tick()
- spawn_meteors(10, GLOB.meteorsC)
\ No newline at end of file
+ spawn_meteors(10, GLOB.meteorsC)
diff --git a/code/modules/events/electrical_storm.dm b/code/modules/events/electrical_storm.dm
index 5e5e318e3c..b850b4db62 100644
--- a/code/modules/events/electrical_storm.dm
+++ b/code/modules/events/electrical_storm.dm
@@ -5,7 +5,6 @@
min_players = 5
weight = 40
alert_observers = FALSE
- gamemode_blacklist = list("dynamic")
/datum/round_event/electrical_storm
var/lightsoutAmount = 1
diff --git a/code/modules/events/fake_virus.dm b/code/modules/events/fake_virus.dm
index 77520198e8..cebf1ed14b 100644
--- a/code/modules/events/fake_virus.dm
+++ b/code/modules/events/fake_virus.dm
@@ -6,7 +6,7 @@
/datum/round_event/fake_virus/start()
var/list/fake_virus_victims = list()
for(var/mob/living/carbon/human/H in shuffle(GLOB.player_list))
- if(!H.client || H.stat == DEAD || H.InCritical())
+ if(!H.client || H.stat == DEAD || H.InCritical() || HAS_TRAIT(H,TRAIT_EXEMPT_HEALTH_EVENTS))
continue
fake_virus_victims += H
diff --git a/code/modules/events/heart_attack.dm b/code/modules/events/heart_attack.dm
index b3bc571a4a..8c33e69107 100644
--- a/code/modules/events/heart_attack.dm
+++ b/code/modules/events/heart_attack.dm
@@ -4,7 +4,6 @@
weight = 20
max_occurrences = 2
min_players = 40 // To avoid shafting lowpop
- gamemode_blacklist = list("dynamic")
/datum/round_event/heart_attack/start()
var/list/heart_attack_contestants = list()
@@ -20,4 +19,4 @@
var/mob/living/carbon/human/winner = pickweight(heart_attack_contestants)
var/datum/disease/D = new /datum/disease/heart_failure()
winner.ForceContractDisease(D, FALSE, TRUE)
- announce_to_ghosts(winner)
\ No newline at end of file
+ announce_to_ghosts(winner)
diff --git a/code/modules/events/immovable_rod.dm b/code/modules/events/immovable_rod.dm
index 89511a7b15..06318df4f5 100644
--- a/code/modules/events/immovable_rod.dm
+++ b/code/modules/events/immovable_rod.dm
@@ -42,6 +42,7 @@ In my current plan for it, 'solid' will be defined as anything with density == 1
name = "immovable rod"
desc = "What the fuck is that?"
icon = 'icons/obj/objects.dmi'
+ movement_type = FLOATING
icon_state = "immrod"
throwforce = 100
move_force = INFINITY
diff --git a/code/modules/events/ion_storm.dm b/code/modules/events/ion_storm.dm
index b15e9d1f99..c75aff0c4a 100644
--- a/code/modules/events/ion_storm.dm
+++ b/code/modules/events/ion_storm.dm
@@ -3,7 +3,6 @@
/datum/round_event_control/ion_storm
name = "Ion Storm"
typepath = /datum/round_event/ion_storm
- gamemode_blacklist = list("dynamic")
weight = 15
min_players = 2
diff --git a/code/modules/events/major_dust.dm b/code/modules/events/major_dust.dm
index c594d7b3c0..d7d8f1aec8 100644
--- a/code/modules/events/major_dust.dm
+++ b/code/modules/events/major_dust.dm
@@ -2,7 +2,6 @@
name = "Major Space Dust"
typepath = /datum/round_event/meteor_wave/major_dust
weight = 8
- gamemode_blacklist = list("dynamic")
/datum/round_event/meteor_wave/major_dust
wave_name = "space dust"
diff --git a/code/modules/events/meteor_wave.dm b/code/modules/events/meteor_wave.dm
index 7763f9950d..e69af1df13 100644
--- a/code/modules/events/meteor_wave.dm
+++ b/code/modules/events/meteor_wave.dm
@@ -10,7 +10,6 @@
min_players = 15
max_occurrences = 3
earliest_start = 25 MINUTES
- gamemode_blacklist = list("dynamic")
/datum/round_event/meteor_wave
startWhen = 6
@@ -23,7 +22,7 @@
/datum/round_event/meteor_wave/setup()
announceWhen = 1
- startWhen = rand(90, 180) // Apparently it is by 2 seconds, so 90 is actually 180 seconds, and 180 is 360 seconds. So this is 3-6 minutes
+ startWhen = 150 // 5 minutes
if(GLOB.singularity_counter)
startWhen *= 1 - min(GLOB.singularity_counter * SINGULO_BEACON_DISTURBANCE, SINGULO_BEACON_MAX_DISTURBANCE)
endWhen = startWhen + 60
diff --git a/code/modules/events/nightmare.dm b/code/modules/events/nightmare.dm
index 6e5512a617..698f5130f1 100644
--- a/code/modules/events/nightmare.dm
+++ b/code/modules/events/nightmare.dm
@@ -2,7 +2,6 @@
name = "Spawn Nightmare"
typepath = /datum/round_event/ghost_role/nightmare
max_occurrences = 1
- gamemode_blacklist = list("dynamic")
min_players = 20
/datum/round_event/ghost_role/nightmare
diff --git a/code/modules/events/pirates.dm b/code/modules/events/pirates.dm
index f1803b03ee..4cbfb8ae9a 100644
--- a/code/modules/events/pirates.dm
+++ b/code/modules/events/pirates.dm
@@ -5,7 +5,7 @@
max_occurrences = 1
min_players = 10
earliest_start = 30 MINUTES
- gamemode_blacklist = list("nuclear","dynamic")
+ gamemode_blacklist = list("nuclear")
/datum/round_event_control/pirates/preRunEvent()
if (!SSmapping.empty_space)
@@ -25,7 +25,7 @@
ship_name = pick(strings(PIRATE_NAMES_FILE, "ship_names"))
/datum/round_event/pirates/announce(fake)
- priority_announce("A report has been downloaded and printed out at all communications consoles.", "Incoming Classified Message", "commandreport") // CITADEL EDIT metabreak
+ priority_announce("A business proposition has been downloaded and printed out at all communication consoles.", "Incoming Business Proposition", "commandreport")
if(fake)
return
threat_message = new
@@ -49,6 +49,7 @@
else
priority_announce("Trying to cheat us? You'll regret this!",sender_override = ship_name)
if(!shuttle_spawned)
+ priority_announce("You won't listen to reason? Then we'll take what's yours or die trying!",sender_override = ship_name)
spawn_shuttle()
/datum/round_event/pirates/start()
@@ -83,8 +84,7 @@
announce_to_ghosts(M)
else
announce_to_ghosts(spawner)
-
- priority_announce("A report has been downloaded and printed out at all communications consoles.", "Incoming Classified Message", "commandreport") //CITADEL EDIT also metabreak here too
+ priority_announce("Unidentified ship detected near the station.")
//Shuttle equipment
@@ -95,14 +95,11 @@
icon_state = "dominator"
density = TRUE
var/active = FALSE
- var/obj/item/gps/gps
var/credits_stored = 0
var/siphon_per_tick = 5
/obj/machinery/shuttle_scrambler/Initialize(mapload)
. = ..()
- gps = new/obj/item/gps/internal/pirate(src)
- gps.tracking = FALSE
update_icon()
/obj/machinery/shuttle_scrambler/process()
@@ -112,6 +109,7 @@
if(D)
var/siphoned = min(D.account_balance,siphon_per_tick)
D.adjust_money(-siphoned)
+ credits_stored += siphoned
interrupt_research()
else
return
@@ -120,7 +118,7 @@
/obj/machinery/shuttle_scrambler/proc/toggle_on(mob/user)
SSshuttle.registerTradeBlockade(src)
- gps.tracking = TRUE
+ AddComponent(/datum/component/gps, "Nautical Signal")
active = TRUE
to_chat(user,"You toggle [src] [active ? "on":"off"].")
to_chat(user,"The scrambling signal can be now tracked by GPS.")
@@ -130,7 +128,7 @@
if(!active)
if(alert(user, "Turning the scrambler on will make the shuttle trackable by GPS. Are you sure you want to do it?", "Scrambler", "Yes", "Cancel") == "Cancel")
return
- if(active || !user.canUseTopic(src))
+ if(active || !user.canUseTopic(src, BE_CLOSE))
return
toggle_on(user)
update_icon()
@@ -147,35 +145,31 @@
new /obj/effect/temp_visual/emp(get_turf(S))
/obj/machinery/shuttle_scrambler/proc/dump_loot(mob/user)
- new /obj/item/holochip(drop_location(), credits_stored)
- to_chat(user,"You retrieve the siphoned credits!")
- credits_stored = 0
+ if(credits_stored) // Prevents spamming empty holochips
+ new /obj/item/holochip(drop_location(), credits_stored)
+ to_chat(user,"You retrieve the siphoned credits!")
+ credits_stored = 0
+ else
+ to_chat(user,"There's nothing to withdraw.")
/obj/machinery/shuttle_scrambler/proc/send_notification()
priority_announce("Data theft signal detected, source registered on local gps units.")
/obj/machinery/shuttle_scrambler/proc/toggle_off(mob/user)
SSshuttle.clearTradeBlockade(src)
- gps.tracking = FALSE
active = FALSE
STOP_PROCESSING(SSobj,src)
-/obj/machinery/shuttle_scrambler/update_overlays()
- . = ..()
+/obj/machinery/shuttle_scrambler/update_icon_state()
if(active)
- var/mutable_appearance/M = mutable_appearance(icon, "dominator-overlay")
- M.color = "#00FFFF"
- . += M
+ icon_state = "dominator-blue"
+ else
+ icon_state = "dominator"
/obj/machinery/shuttle_scrambler/Destroy()
toggle_off()
- QDEL_NULL(gps)
return ..()
-/obj/item/gps/internal/pirate
- gpstag = "Nautical Signal"
- desc = "You can hear shanties over the static."
-
/obj/machinery/computer/shuttle/pirate
name = "pirate shuttle console"
shuttleId = "pirateship"
@@ -312,11 +306,10 @@
else
pad = locate() in range(4,src)
-/obj/machinery/computer/piratepad_control/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \
- datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state)
- ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+/obj/machinery/computer/piratepad_control/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
- ui = new(user, src, ui_key, "CargoHoldTerminal", name, ui_x, ui_y, master_ui, state)
+ ui = new(user, src, "CargoHoldTerminal", name)
ui.open()
/obj/machinery/computer/piratepad_control/ui_data(mob/user)
diff --git a/code/modules/events/portal_storm.dm b/code/modules/events/portal_storm.dm
index 457b5bd4ec..5ef30d0030 100644
--- a/code/modules/events/portal_storm.dm
+++ b/code/modules/events/portal_storm.dm
@@ -4,7 +4,6 @@
weight = 2
min_players = 15
earliest_start = 30 MINUTES
- gamemode_blacklist = list("dynamic")
/datum/round_event/portal_storm/syndicate_shocktroop
boss_types = list(/mob/living/simple_animal/hostile/syndicate/melee/space/stormtrooper = 2)
diff --git a/code/modules/events/processor_overload.dm b/code/modules/events/processor_overload.dm
index 22e475a8ef..6bedce6b4b 100644
--- a/code/modules/events/processor_overload.dm
+++ b/code/modules/events/processor_overload.dm
@@ -3,7 +3,6 @@
typepath = /datum/round_event/processor_overload
weight = 15
min_players = 20
- gamemode_blacklist = list("dynamic")
/datum/round_event/processor_overload
announceWhen = 1
diff --git a/code/modules/events/radiation_storm.dm b/code/modules/events/radiation_storm.dm
index 0a5bedb464..36e16bb2c9 100644
--- a/code/modules/events/radiation_storm.dm
+++ b/code/modules/events/radiation_storm.dm
@@ -2,7 +2,6 @@
name = "Radiation Storm"
typepath = /datum/round_event/radiation_storm
max_occurrences = 1
- gamemode_blacklist = list("dynamic")
/datum/round_event/radiation_storm
diff --git a/code/modules/events/spider_infestation.dm b/code/modules/events/spider_infestation.dm
index d1c327e0f7..23ce6ce730 100644
--- a/code/modules/events/spider_infestation.dm
+++ b/code/modules/events/spider_infestation.dm
@@ -2,7 +2,6 @@
name = "Spider Infestation"
typepath = /datum/round_event/spider_infestation
weight = 5
- gamemode_blacklist = list("dynamic")
max_occurrences = 1
min_players = 15
diff --git a/code/modules/events/travelling_trader.dm b/code/modules/events/travelling_trader.dm
index 08dae1b469..c7b982eda8 100644
--- a/code/modules/events/travelling_trader.dm
+++ b/code/modules/events/travelling_trader.dm
@@ -1,8 +1,8 @@
/datum/round_event_control/travelling_trader
name = "Travelling Trader"
typepath = /datum/round_event/travelling_trader
- weight = 10
- max_occurrences = 3
+ weight = 8
+ max_occurrences = 2
earliest_start = 0 MINUTES
/datum/round_event/travelling_trader
@@ -52,6 +52,14 @@
var/acceptance_speech = "This is exactly what I wanted! I shall be on my way now, thank you.!"
var/refusal_speech = "A given_item? I wanted a requested_item!" //what they say when refusing an item
var/active = TRUE
+ var/examine_text = list("You attempt to look directly at the being's face, but it's just a blur!")
+ move_resist = MOVE_FORCE_VERY_STRONG
+ mob_size = MOB_SIZE_LARGE
+ alpha = 200
+
+/mob/living/carbon/human/dummy/travelling_trader/examine(mob/user)
+ SEND_SIGNAL(src, COMSIG_PARENT_EXAMINE, user, examine_text)
+ return examine_text
/mob/living/carbon/human/dummy/travelling_trader/proc/setup_speech(var/input_speech, var/obj/item/given_item)
if(requested_item)
@@ -92,6 +100,8 @@
/mob/living/carbon/human/dummy/travelling_trader/Initialize()
..()
+ add_atom_colour("#570d6b", FIXED_COLOUR_PRIORITY) //make them purple (otherworldly!)
+ set_light(1, -0.7, "#AAD84B")
ADD_TRAIT(src,TRAIT_PIERCEIMMUNE, "trader_pierce_immune") //don't let people take their blood
equipOutfit(trader_outfit, TRUE)
for(var/obj/item/item in src.get_equipped_items())
@@ -159,15 +169,11 @@
trader_name = "Otherworldly Animal Specialist"
trader_outfit = /datum/outfit/job/doctor
initial_speech = "Greetings, lifeform. I am here to locate a special creature aboard your station."
- request_speech = "Find me the creature known as 'requested_item' and you shall be rewarded for your efforts."
+ request_speech = "Find me the creature known as 'requested_item' and hand it to me, preferably in a suitable container."
refusal_speech = "Do you think me to be a fool, lifeform? I know a requested_item when I see one."
- possible_wanted_items = list(/mob/living/simple_animal/pet/dog/corgi/Ian = 1,
- /mob/living/simple_animal/sloth/paperwork = 1,
- /mob/living/carbon/monkey/punpun = 1,
- /mob/living/simple_animal/pet/fox/Renault = 1,
- /mob/living/simple_animal/hostile/carp/cayenne = 1,
- /mob/living/simple_animal/pet/bumbles = 1,
- /mob/living/simple_animal/parrot/Poly = 1)
+ possible_wanted_items = list(/mob/living/simple_animal/pet/dog/corgi = 4,
+ /mob/living/carbon/monkey = 1,
+ /mob/living/simple_animal/mouse = 2)
possible_rewards = list(/mob/living/simple_animal/pet/dog/corgi/exoticcorgi = 1, //rewards are animals, friendly to only the person who handed the reward in!
/mob/living/simple_animal/cockroach = 1,
/mob/living/simple_animal/hostile/skeleton = 1,
@@ -184,13 +190,6 @@
mob/living/carbon/human/dummy/travelling_trader/animal_hunter/Initialize()
acceptance_speech = pick(list("This lifeform shall make for a great stew, thank you.", "This lifeform shall be of a true use to our cause, thank you.", "The lifeform is adequate. Goodbye.", "This lifeform shall make a great addition to my collection."))
- //make sure they only ask for animals that are still alive
- for(var/mob/living/animal in possible_wanted_items)
- if(!(animal in GLOB.mob_living_list))
- possible_wanted_items -= animal
- if(!possible_wanted_items)
- //all the pets are dead, so ask for a monkey, or sometimes a corgi (corgis are more annoying to get a hold of)
- possible_wanted_items = list(/mob/living/simple_animal/pet/dog/corgi = 1, /mob/living/carbon/monkey = 3)
..()
/mob/living/carbon/human/dummy/travelling_trader/animal_hunter/check_item(var/obj/item/supplied_item) //item is likely to be in contents of whats supplied
diff --git a/code/modules/events/vent_clog.dm b/code/modules/events/vent_clog.dm
index dc672cec45..cd7b23a577 100644
--- a/code/modules/events/vent_clog.dm
+++ b/code/modules/events/vent_clog.dm
@@ -3,7 +3,6 @@
typepath = /datum/round_event/vent_clog
weight = 10
max_occurrences = 3
- gamemode_blacklist = list("dynamic")
min_players = 25
/datum/round_event/vent_clog
diff --git a/code/modules/events/wizard/magicarp.dm b/code/modules/events/wizard/magicarp.dm
index 4d2e8e624c..57e2a2a051 100644
--- a/code/modules/events/wizard/magicarp.dm
+++ b/code/modules/events/wizard/magicarp.dm
@@ -30,7 +30,6 @@
icon_dead = "magicarp_dead"
icon_gib = "magicarp_gib"
ranged = 1
- threat = 4
retreat_distance = 2
minimum_distance = 0 //Between shots they can and will close in to nash
projectiletype = /obj/item/projectile/magic
@@ -52,7 +51,6 @@
color = "#00FFFF"
maxHealth = 75
health = 75
- threat = 7
/mob/living/simple_animal/hostile/carp/ranged/chaos/Shoot()
projectiletype = pick(allowed_projectile_types)
diff --git a/code/modules/events/wizard/shuffle.dm b/code/modules/events/wizard/shuffle.dm
index 3b5ea6b20a..18b8c8e21c 100644
--- a/code/modules/events/wizard/shuffle.dm
+++ b/code/modules/events/wizard/shuffle.dm
@@ -94,7 +94,7 @@
shuffle_inplace(mobs)
- var/obj/effect/proc_holder/spell/targeted/mind_transfer/swapper = new /obj/effect/proc_holder/spell/targeted/mind_transfer
+ var/obj/effect/proc_holder/spell/pointed/mind_transfer/swapper = new /obj/effect/proc_holder/spell/pointed/mind_transfer
while(mobs.len > 1)
var/mob/living/carbon/human/H = pick(mobs)
mobs -= H
diff --git a/code/modules/events/wormholes.dm b/code/modules/events/wormholes.dm
index 1b4716b407..0920c9ccb6 100644
--- a/code/modules/events/wormholes.dm
+++ b/code/modules/events/wormholes.dm
@@ -4,7 +4,6 @@
max_occurrences = 3
weight = 2
min_players = 2
- gamemode_blacklist = list("dynamic")
/datum/round_event/wormholes
diff --git a/code/modules/food_and_drinks/drinks/drinks.dm b/code/modules/food_and_drinks/drinks/drinks.dm
index 45ab6f6af1..a3ee8c5228 100644
--- a/code/modules/food_and_drinks/drinks/drinks.dm
+++ b/code/modules/food_and_drinks/drinks/drinks.dm
@@ -299,6 +299,31 @@
desc = "An insult to Duke Purple is an insult to the Space Queen! Any proper gentleman will fight you, if you sully this tea."
list_reagents = list(/datum/reagent/consumable/tea = 30)
+/obj/item/reagent_containers/food/drinks/mug/tea/red
+ name = "Dutchess Red tea"
+ icon_state = "tea"
+ desc = "Duchess Red's personal blend of red tea leaves and hot water. Great addition to any meal."
+ list_reagents = list(/datum/reagent/consumable/tea/red = 30)
+
+/obj/item/reagent_containers/food/drinks/mug/tea/green
+ name = "Prince Green tea"
+ icon_state = "tea"
+ desc = "Prince Green's brew of tea. The blend may be different from time to time, but Prince Green swears by it!"
+ list_reagents = list(/datum/reagent/consumable/tea/green = 30)
+
+/obj/item/reagent_containers/food/drinks/mug/tea/forest
+ name = "Royal Forest tea"
+ icon_state = "tea"
+ desc = "Tea fit for anyone with a sweet tooth like Royal Forest."
+ list_reagents = list(/datum/reagent/consumable/tea/forest = 30)
+
+/obj/item/reagent_containers/food/drinks/mug/tea/mush
+ name = "Rebel Mush tea"
+ icon_state = "tea"
+ desc = "Rebel Mush, a hallucinogenic tea to help people find their inner self."
+ list_reagents = list(/datum/reagent/consumable/tea/mush = 30)
+
+
/obj/item/reagent_containers/food/drinks/mug/coco
name = "Dutch hot coco"
desc = "Made in Space South America."
diff --git a/code/modules/food_and_drinks/drinks/drinks/bottle.dm b/code/modules/food_and_drinks/drinks/drinks/bottle.dm
index d87eb8fbc0..e31a9704df 100644
--- a/code/modules/food_and_drinks/drinks/drinks/bottle.dm
+++ b/code/modules/food_and_drinks/drinks/drinks/bottle.dm
@@ -87,7 +87,7 @@
hitsound = 'sound/weapons/bladeslice.ogg'
attack_verb = list("stabbed", "slashed", "attacked")
var/icon/broken_outline = icon('icons/obj/drinks.dmi', "broken")
- sharpness = IS_SHARP
+ sharpness = SHARP_EDGED
/obj/item/broken_bottle/Initialize()
. = ..()
diff --git a/code/modules/food_and_drinks/food/snacks.dm b/code/modules/food_and_drinks/food/snacks.dm
index 4b79dc1fd4..4d5f4e2dd0 100644
--- a/code/modules/food_and_drinks/food/snacks.dm
+++ b/code/modules/food_and_drinks/food/snacks.dm
@@ -97,9 +97,12 @@ All foods are distributed among various categories. Use common sense.
return
-/obj/item/reagent_containers/food/snacks/attack(mob/living/M, mob/living/user, def_zone)
+/obj/item/reagent_containers/food/snacks/attack(mob/living/M, mob/living/user, attackchain_flags = NONE, damage_multiplier = 1)
if(user.a_intent == INTENT_HARM)
return ..()
+ INVOKE_ASYNC(src, .proc/attempt_forcefeed, M, user)
+
+/obj/item/reagent_containers/food/snacks/proc/attempt_forcefeed(mob/living/M, mob/living/user)
if(!eatverb)
eatverb = pick("bite","chew","nibble","gnaw","gobble","chomp")
if(!reagents.total_volume) //Shouldn't be needed but it checks to see if it has anything left in it.
@@ -249,21 +252,9 @@ All foods are distributed among various categories. Use common sense.
to_chat(user, "You cannot slice [src] here! You need a table or at least a tray.")
return FALSE
- var/slices_lost = 0
- if (accuracy >= IS_SHARP_ACCURATE)
- user.visible_message( \
- "[user] slices [src].", \
- "You slice [src]." \
- )
- else
- user.visible_message( \
- "[user] inaccurately slices [src] with [W]!", \
- "You inaccurately slice [src] with your [W]!" \
- )
- slices_lost = rand(1,min(1,round(slices_num/2)))
-
+ user.visible_message("[user] slices [src].", "You slice [src].")
var/reagents_per_slice = reagents.total_volume/slices_num
- for(var/i=1 to (slices_num-slices_lost))
+ for(var/i=1 to slices_num)
var/obj/item/reagent_containers/food/snacks/slice = new slice_path (loc)
initialize_slice(slice, reagents_per_slice)
qdel(src)
diff --git a/code/modules/food_and_drinks/food/snacks/meat.dm b/code/modules/food_and_drinks/food/snacks/meat.dm
index fe70739ab5..4d287fdb86 100644
--- a/code/modules/food_and_drinks/food/snacks/meat.dm
+++ b/code/modules/food_and_drinks/food/snacks/meat.dm
@@ -162,6 +162,14 @@
tastes = list("brains" = 1, "meat" = 1)
foodtype = RAW | MEAT | TOXIC
+/obj/item/reagent_containers/food/snacks/meat/slab/human/mutant/ethereal
+ icon_state = "etherealmeat"
+ desc = "So shiny you feel like ingesting it might make you shine too"
+ filling_color = "#97ee63"
+ list_reagents = list(/datum/reagent/consumable/liquidelectricity = 3)
+ tastes = list("pure electricity" = 2, "meat" = 1)
+ foodtype = RAW | MEAT | TOXIC
+
/obj/item/reagent_containers/food/snacks/carpmeat/aquatic
name = "fillet"
desc = "A fillet of one of the local water dwelling species."
diff --git a/code/modules/food_and_drinks/food/snacks_cake.dm b/code/modules/food_and_drinks/food/snacks_cake.dm
index 1117dbc3d3..be07826a7f 100644
--- a/code/modules/food_and_drinks/food/snacks_cake.dm
+++ b/code/modules/food_and_drinks/food/snacks_cake.dm
@@ -133,7 +133,7 @@
slices_num = 5
bonus_reagents = list(/datum/reagent/consumable/nutriment = 3, /datum/reagent/consumable/nutriment/vitamin = 10)
tastes = list("cake" = 5, "sweetness" = 2, "unbearable sourness" = 2)
- foodtype = GRAIN | DAIRY | FRUIT | SUGAR
+ foodtype = GRAIN | DAIRY | FRUIT | SUGAR | ANTITOXIC
/obj/item/reagent_containers/food/snacks/cakeslice/lime
name = "lime cake slice"
@@ -141,7 +141,7 @@
icon_state = "limecake_slice"
filling_color = "#00FF00"
tastes = list("cake" = 5, "sweetness" = 2, "unbearable sourness" = 2)
- foodtype = GRAIN | DAIRY | FRUIT | SUGAR
+ foodtype = GRAIN | DAIRY | FRUIT | SUGAR | ANTITOXIC
/obj/item/reagent_containers/food/snacks/store/cake/lemon
name = "lemon cake"
diff --git a/code/modules/food_and_drinks/food/snacks_frozen.dm b/code/modules/food_and_drinks/food/snacks_frozen.dm
index 39c9c6c04f..32a89dbd58 100644
--- a/code/modules/food_and_drinks/food/snacks_frozen.dm
+++ b/code/modules/food_and_drinks/food/snacks_frozen.dm
@@ -103,7 +103,7 @@
icon_state = "lime_sc"
list_reagents = list(/datum/reagent/consumable/nutriment = 1, /datum/reagent/consumable/limejuice = 5)
tastes = list("ice" = 1, "water" = 1, "limes" = 5)
- foodtype = FRUIT
+ foodtype = FRUIT | ANTITOXIC
/obj/item/reagent_containers/food/snacks/snowcones/lemon
name = "lemon snowcone"
@@ -191,7 +191,7 @@
icon_state = "fruitsalad_sc"
list_reagents = list(/datum/reagent/consumable/nutriment = 1, /datum/reagent/consumable/lemonjuice = 5, /datum/reagent/consumable/limejuice = 5, /datum/reagent/consumable/orangejuice = 5)
tastes = list("ice" = 1, "water" = 1, "oranges" = 5, "limes" = 5, "lemons" = 5, "citrus" = 5, "salad" = 5)
- foodtype = FRUIT
+ foodtype = FRUIT | ANTITOXIC
/obj/item/reagent_containers/food/snacks/snowcones/pineapple
name = "pineapple snowcone"
diff --git a/code/modules/food_and_drinks/food/snacks_meat.dm b/code/modules/food_and_drinks/food/snacks_meat.dm
index 2a4139dbb9..05a0da2793 100644
--- a/code/modules/food_and_drinks/food/snacks_meat.dm
+++ b/code/modules/food_and_drinks/food/snacks_meat.dm
@@ -139,11 +139,22 @@
tastes = list("meat" = 1, "salmon" = 1)
foodtype = MEAT | ALCOHOL
+/obj/item/reagent_containers/food/snacks/rawmeatball
+ name = "raw meatball"
+ desc = "Raw mushy meat. Better cook this!"
+ icon_state = "rawmeatball"
+ cooked_type = /obj/item/reagent_containers/food/snacks/meatball
+ list_reagents = list(/datum/reagent/consumable/nutriment = 3)
+ filling_color = "#bd2020"
+ tastes = list("meat" = 1, "slime" = 1)
+ foodtype = MEAT | RAW
+
/obj/item/reagent_containers/food/snacks/meatball
name = "meatball"
desc = "MAMA MIA DAS A SPICY"
icon_state = "meatball"
list_reagents = list(/datum/reagent/consumable/nutriment = 4, /datum/reagent/consumable/nutriment/vitamin = 1)
+ bonus_reagents = list(/datum/reagent/consumable/nutriment = 1, /datum/reagent/consumable/nutriment/vitamin = 1)
filling_color = "#800000"
tastes = list("meat" = 1)
foodtype = MEAT
diff --git a/code/modules/food_and_drinks/food/snacks_other.dm b/code/modules/food_and_drinks/food/snacks_other.dm
index e0697cecea..b771a90907 100644
--- a/code/modules/food_and_drinks/food/snacks_other.dm
+++ b/code/modules/food_and_drinks/food/snacks_other.dm
@@ -232,7 +232,7 @@
list_reagents = list(/datum/reagent/consumable/nutriment = 1, /datum/reagent/toxin = 1, /datum/reagent/iron = 10, /datum/reagent/consumable/sugar = 5, /datum/reagent/medicine/omnizine = 2) //lollipop, but vitamins = toxins
filling_color = "#00800"
tastes = list("cobwebs" = 1, "sugar" = 2)
- foodtype = JUNKFOOD | SUGAR
+ foodtype = JUNKFOOD | SUGAR | ANTITOXIC
/obj/item/reagent_containers/food/snacks/tobiko
name = "tobiko"
@@ -451,7 +451,7 @@
var/mutable_appearance/head
var/headcolor = rgb(0, 0, 0)
tastes = list("candy" = 1)
- foodtype = JUNKFOOD | SUGAR
+ foodtype = JUNKFOOD | SUGAR | ANTITOXIC
/obj/item/reagent_containers/food/snacks/lollipop/Initialize()
. = ..()
@@ -756,4 +756,4 @@
bitesize = 2
name = "hot-cross bun"
desc = "The Cross represents the Assistants that died for your sins."
- icon_state = "hotcrossbun"
\ No newline at end of file
+ icon_state = "hotcrossbun"
diff --git a/code/modules/food_and_drinks/food/snacks_pastry.dm b/code/modules/food_and_drinks/food/snacks_pastry.dm
index d65907daea..13a46395ea 100644
--- a/code/modules/food_and_drinks/food/snacks_pastry.dm
+++ b/code/modules/food_and_drinks/food/snacks_pastry.dm
@@ -509,7 +509,7 @@
list_reagents = list(/datum/reagent/consumable/nutriment = 4, /datum/reagent/medicine/omnizine = 3)
cooked_type = null
tastes = list("meat" = 2, "dough" = 2, "laziness" = 1)
- foodtype = GRAIN
+ foodtype = GRAIN | ANTITOXIC
/obj/item/reagent_containers/food/snacks/dankpocket
name = "\improper Dank-pocket"
@@ -556,9 +556,11 @@
name = "exceptional plump helmet biscuit"
desc = "Microwave is taken by a fey mood! It has cooked an exceptional plump helmet biscuit!"
bonus_reagents = list(/datum/reagent/medicine/omnizine = 5, /datum/reagent/consumable/nutriment = 1, /datum/reagent/consumable/nutriment/vitamin = 1)
+ foodtype += ANTITOXIC
. = ..()
if(fey)
reagents.add_reagent(/datum/reagent/medicine/omnizine, 5)
+ foodtype += ANTITOXIC
/obj/item/reagent_containers/food/snacks/cracker
name = "cracker"
diff --git a/code/modules/food_and_drinks/food/snacks_pie.dm b/code/modules/food_and_drinks/food/snacks_pie.dm
index 03f4640718..24522503c1 100644
--- a/code/modules/food_and_drinks/food/snacks_pie.dm
+++ b/code/modules/food_and_drinks/food/snacks_pie.dm
@@ -113,6 +113,27 @@
tastes = list("pie" = 1, "meat" = 1)
foodtype = GRAIN | MEAT
+/obj/item/reagent_containers/food/snacks/pie/burek
+ name = "Burek"
+ icon = 'icons/obj/food/piecake.dmi'
+ icon_state = "burek"
+ desc = "If you know, you know."
+ slice_path = /obj/item/reagent_containers/food/snacks/pie/burekslice
+ slices_num = 4
+ bonus_reagents = list(/datum/reagent/consumable/nutriment = 4, /datum/reagent/consumable/nutriment/vitamin = 6)
+ list_reagents = list(/datum/reagent/consumable/nutriment= 20, /datum/reagent/consumable/nutriment/vitamin = 6)
+ bitesize = 12
+ tastes = list("meat" = 1, "oil" = 1)
+ foodtype = GRAIN | MEAT
+
+/obj/item/reagent_containers/food/snacks/pie/burekslice
+ name = "Burek Slice"
+ icon = 'icons/obj/food/piecake.dmi'
+ icon_state = "burekslice"
+ desc = "A slice of Burek, watch out for oil stains!"
+ tastes = list("meat" = 1, "oil" = 1)
+ foodtype = GRAIN | MEAT
+
/obj/item/reagent_containers/food/snacks/pie/tofupie
name = "tofu-pie"
@@ -150,8 +171,10 @@
name = "exceptional plump pie"
desc = "Microwave is taken by a fey mood! It has cooked an exceptional plump pie!"
bonus_reagents = list(/datum/reagent/consumable/nutriment = 1, /datum/reagent/medicine/omnizine = 5, /datum/reagent/consumable/nutriment/vitamin = 4)
+ foodtype += ANTITOXIC
if(fey)
reagents.add_reagent(/datum/reagent/medicine/omnizine, 5)
+ foodtype += ANTITOXIC
/obj/item/reagent_containers/food/snacks/pie/xemeatpie
diff --git a/code/modules/food_and_drinks/food/snacks_pizza.dm b/code/modules/food_and_drinks/food/snacks_pizza.dm
index f30c182963..75cf8ffb82 100644
--- a/code/modules/food_and_drinks/food/snacks_pizza.dm
+++ b/code/modules/food_and_drinks/food/snacks_pizza.dm
@@ -109,7 +109,7 @@
bonus_reagents = list(/datum/reagent/consumable/nutriment = 5, /datum/reagent/consumable/nutriment/vitamin = 5)
list_reagents = list(/datum/reagent/consumable/nutriment = 25, /datum/reagent/consumable/tomatojuice = 6, /datum/reagent/medicine/omnizine = 10, /datum/reagent/consumable/nutriment/vitamin = 5)
tastes = list("crust" = 1, "tomato" = 1, "cheese" = 1, "meat" = 1, "laziness" = 1)
- foodtype = GRAIN | VEGETABLES | DAIRY | MEAT | JUNKFOOD
+ foodtype = GRAIN | VEGETABLES | DAIRY | MEAT | JUNKFOOD | ANTITOXIC
/obj/item/reagent_containers/food/snacks/pizzaslice/donkpocket
name = "donkpocket pizza slice"
@@ -117,7 +117,7 @@
icon_state = "donkpocketpizzaslice"
filling_color = "#FFA500"
tastes = list("crust" = 1, "tomato" = 1, "cheese" = 1, "meat" = 1, "laziness" = 1)
- foodtype = GRAIN | VEGETABLES | DAIRY | MEAT | JUNKFOOD
+ foodtype = GRAIN | VEGETABLES | DAIRY | MEAT | JUNKFOOD | ANTITOXIC
/obj/item/reagent_containers/food/snacks/pizza/dank
name = "dank pizza"
@@ -127,7 +127,7 @@
bonus_reagents = list(/datum/reagent/consumable/nutriment = 2, /datum/reagent/consumable/nutriment/vitamin = 6)
list_reagents = list(/datum/reagent/consumable/nutriment = 25, /datum/reagent/consumable/doctor_delight = 5, /datum/reagent/consumable/tomatojuice = 6, /datum/reagent/consumable/nutriment/vitamin = 5)
tastes = list("crust" = 1, "tomato" = 1, "cheese" = 1, "meat" = 1)
- foodtype = GRAIN | VEGETABLES | FRUIT | DAIRY
+ foodtype = GRAIN | VEGETABLES | FRUIT | DAIRY | ANTITOXIC
/obj/item/reagent_containers/food/snacks/pizzaslice/dank
name = "dank pizza slice"
@@ -135,7 +135,7 @@
icon_state = "dankpizzaslice"
filling_color = "#2E8B57"
tastes = list("crust" = 1, "tomato" = 1, "cheese" = 1, "meat" = 1)
- foodtype = GRAIN | VEGETABLES | FRUIT | DAIRY
+ foodtype = GRAIN | VEGETABLES | FRUIT | DAIRY | ANTITOXIC
/obj/item/reagent_containers/food/snacks/pizza/sassysage
name = "sassysage pizza"
@@ -187,6 +187,7 @@
slice_path = /obj/item/reagent_containers/food/snacks/pizzaslice/arnold
bonus_reagents = list(/datum/reagent/consumable/nutriment = 30, /datum/reagent/consumable/nutriment/vitamin = 6, /datum/reagent/iron = 10, /datum/reagent/medicine/omnizine = 30)
tastes = list("crust" = 1, "tomato" = 1, "cheese" = 1, "pepperoni" = 2, "9 millimeter bullets" = 2)
+
/obj/item/reagent_containers/food/snacks/proc/try_break_off(mob/living/M, mob/living/user) //maybe i give you a pizza maybe i break off your arm
var/obj/item/bodypart/l_arm = user.get_bodypart(BODY_ZONE_L_ARM)
@@ -244,4 +245,4 @@
icon_state = "meatpizzaslice"
filling_color = "#A52A2A"
tastes = list("cardboard" = 1, "tomato" = 1, "cheese" = 1, "pepperoni" = 2)
- foodtype = GRAIN | VEGETABLES | DAIRY | MEAT
\ No newline at end of file
+ foodtype = GRAIN | VEGETABLES | DAIRY | MEAT
diff --git a/code/modules/food_and_drinks/food/snacks_salad.dm b/code/modules/food_and_drinks/food/snacks_salad.dm
index be7c3d25da..8c6eecb829 100644
--- a/code/modules/food_and_drinks/food/snacks_salad.dm
+++ b/code/modules/food_and_drinks/food/snacks_salad.dm
@@ -20,7 +20,7 @@
bonus_reagents = list(/datum/reagent/medicine/omnizine = 2, /datum/reagent/consumable/nutriment/vitamin = 6)
list_reagents = list(/datum/reagent/consumable/nutriment = 8, /datum/reagent/medicine/omnizine = 8, /datum/reagent/consumable/nutriment/vitamin = 6)
tastes = list("leaves" = 1)
- foodtype = VEGETABLES
+ foodtype = VEGETABLES | ANTITOXIC
/obj/item/reagent_containers/food/snacks/salad/herbsalad
name = "herb salad"
@@ -38,7 +38,7 @@
bonus_reagents = list(/datum/reagent/consumable/doctor_delight = 5, /datum/reagent/consumable/nutriment/vitamin = 4)
list_reagents = list(/datum/reagent/consumable/nutriment = 8, /datum/reagent/consumable/doctor_delight = 5, /datum/reagent/consumable/nutriment/vitamin = 2)
tastes = list("leaves" = 1, "potato" = 1, "meat" = 1, "valids" = 1)
- foodtype = VEGETABLES | MEAT | FRIED | JUNKFOOD | FRUIT
+ foodtype = VEGETABLES | MEAT | FRIED | JUNKFOOD | FRUIT | ANTITOXIC
/obj/item/reagent_containers/food/snacks/salad/oatmeal
name = "oatmeal"
@@ -133,7 +133,7 @@
trash = /obj/item/reagent_containers/glass/bowl
list_reagents = list(/datum/reagent/consumable/nutriment = 7, /datum/reagent/consumable/nutriment/vitamin = 5, /datum/reagent/medicine/earthsblood = 3, /datum/reagent/medicine/omnizine = 5, /datum/reagent/drug/happiness = 2)
tastes = list("hope" = 1)
- foodtype = VEGETABLES
+ foodtype = VEGETABLES | ANTITOXIC
/obj/item/reagent_containers/food/snacks/salad/gumbo
name = "black eyed gumbo"
diff --git a/code/modules/food_and_drinks/food/snacks_soup.dm b/code/modules/food_and_drinks/food/snacks_soup.dm
index ffafcc6b1e..eea7cdb87d 100644
--- a/code/modules/food_and_drinks/food/snacks_soup.dm
+++ b/code/modules/food_and_drinks/food/snacks_soup.dm
@@ -89,7 +89,7 @@
icon_state = "nettlesoup"
bonus_reagents = list(/datum/reagent/consumable/nutriment = 1, /datum/reagent/medicine/omnizine = 5, /datum/reagent/consumable/nutriment/vitamin = 5)
tastes = list("nettles" = 1)
- foodtype = VEGETABLES
+ foodtype = VEGETABLES | ANTITOXIC
/obj/item/reagent_containers/food/snacks/soup/mystery
name = "mystery soup"
diff --git a/code/modules/food_and_drinks/food/snacks_vend.dm b/code/modules/food_and_drinks/food/snacks_vend.dm
index 38f7ecf5b1..4e8687fc23 100644
--- a/code/modules/food_and_drinks/food/snacks_vend.dm
+++ b/code/modules/food_and_drinks/food/snacks_vend.dm
@@ -89,5 +89,15 @@
list_reagents = list(/datum/reagent/consumable/nutriment = 4, /datum/reagent/consumable/doctor_delight = 5)
filling_color = "#F5F5DC"
tastes = list("sweetness" = 3, "cake" = 1)
- foodtype = GRAIN | FRUIT | VEGETABLES
+ foodtype = GRAIN | FRUIT | VEGETABLES | ANTITOXIC
custom_price = PRICE_CHEAP
+
+/obj/item/reagent_containers/food/snacks/energybar
+ name = "High-power energy bars"
+ icon_state = "energybar"
+ desc = "An energy bar with a lot of punch, you probably shouldn't eat this if you're not an Ethereal."
+ trash = /obj/item/trash/energybar
+ list_reagents = list(/datum/reagent/consumable/nutriment = 3, /datum/reagent/consumable/liquidelectricity = 3)
+ filling_color = "#97ee63"
+ tastes = list("pure electricity" = 3, "fitness" = 2)
+ foodtype = TOXIC
diff --git a/code/modules/food_and_drinks/kitchen_machinery/smartfridge.dm b/code/modules/food_and_drinks/kitchen_machinery/smartfridge.dm
index 36e3b64487..35fa40e15d 100644
--- a/code/modules/food_and_drinks/kitchen_machinery/smartfridge.dm
+++ b/code/modules/food_and_drinks/kitchen_machinery/smartfridge.dm
@@ -12,6 +12,7 @@
idle_power_usage = 5
active_power_usage = 100
circuit = /obj/item/circuitboard/machine/smartfridge
+
var/max_n_of_items = 1500
var/allow_ai_retrieve = FALSE
var/list/initial_contents
@@ -38,12 +39,10 @@
if(in_range(user, src) || isobserver(user))
. += "The status display reads: This unit can hold a maximum of [max_n_of_items] items."
-/obj/machinery/smartfridge/power_change()
- ..()
- update_icon()
-
/obj/machinery/smartfridge/update_icon_state()
+ SSvis_overlays.remove_vis_overlay(src, managed_vis_overlays)
if(!stat)
+ SSvis_overlays.add_vis_overlay(src, icon, "smartfridge-light-mask", EMISSIVE_LAYER, EMISSIVE_PLANE, dir, alpha)
if(visible_contents)
switch(contents.len)
if(0)
@@ -66,9 +65,6 @@
********************/
/obj/machinery/smartfridge/attackby(obj/item/O, mob/user, params)
- if(user.a_intent == INTENT_HARM)
- return ..()
-
if(default_deconstruction_screwdriver(user, icon_state, icon_state, O))
cut_overlays()
if(panel_open)
@@ -87,46 +83,53 @@
updateUsrDialog()
return
- if(stat)
- updateUsrDialog()
- return FALSE
+ if(!stat)
- if(contents.len >= max_n_of_items)
- to_chat(user, "\The [src] is full!")
- return FALSE
-
- if(accept_check(O))
- load(O)
- user.visible_message("[user] has added \the [O] to \the [src].", "You add \the [O] to \the [src].")
- updateUsrDialog()
- if (visible_contents)
- update_icon()
- return TRUE
-
- if(istype(O, /obj/item/storage/bag))
- var/obj/item/storage/P = O
- var/loaded = 0
- for(var/obj/G in P.contents)
- if(contents.len >= max_n_of_items)
- break
- if(accept_check(G))
- load(G)
- loaded++
- updateUsrDialog()
-
- if(loaded)
- user.visible_message("[user] loads \the [src] with \the [O].", \
- "You [contents.len >= max_n_of_items ? "fill" : "load"] \the [src] with \the [O].")
- if(O.contents.len > 0)
- to_chat(user, "Some items are refused.")
- return TRUE
- else
- to_chat(user, "There is nothing in [O] to put in [src]!")
+ if(contents.len >= max_n_of_items)
+ to_chat(user, "\The [src] is full!")
return FALSE
- to_chat(user, "\The [src] smartly refuses [O].")
- updateUsrDialog()
- return FALSE
+ if(accept_check(O))
+ load(O)
+ user.visible_message("[user] adds \the [O] to \the [src].", "You add \the [O] to \the [src].")
+ updateUsrDialog()
+ if (visible_contents)
+ update_icon()
+ return TRUE
+
+ if(istype(O, /obj/item/storage/bag))
+ var/obj/item/storage/P = O
+ var/loaded = 0
+ for(var/obj/G in P.contents)
+ if(contents.len >= max_n_of_items)
+ break
+ if(accept_check(G))
+ load(G)
+ loaded++
+ updateUsrDialog()
+
+ if(loaded)
+ if(contents.len >= max_n_of_items)
+ user.visible_message("[user] loads \the [src] with \the [O].", \
+ "You fill \the [src] with \the [O].")
+ else
+ user.visible_message("[user] loads \the [src] with \the [O].", \
+ "You load \the [src] with \the [O].")
+ if(O.contents.len > 0)
+ to_chat(user, "Some items are refused.")
+ if (visible_contents)
+ update_icon()
+ return TRUE
+ else
+ to_chat(user, "There is nothing in [O] to put in [src]!")
+ return FALSE
+
+ if(user.a_intent != INTENT_HARM)
+ to_chat(user, "\The [src] smartly refuses [O].")
+ updateUsrDialog()
+ return FALSE
+ else
+ return ..()
@@ -151,16 +154,16 @@
return TRUE
///Really simple proc, just moves the object "O" into the hands of mob "M" if able, done so I could modify the proc a little for the organ fridge
-/obj/machinery/smartfridge/proc/dispense(obj/item/O, var/mob/M)
+/obj/machinery/smartfridge/proc/dispense(obj/item/O, mob/M)
if(!M.put_in_hands(O))
O.forceMove(drop_location())
adjust_item_drop_location(O)
-/obj/machinery/smartfridge/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state)
- ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+/obj/machinery/smartfridge/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
- ui = new(user, src, ui_key, "SmartVend", name, 440, 550, master_ui, state)
+ ui = new(user, src, "SmartVend", name)
ui.set_autoupdate(FALSE)
ui.open()
@@ -232,7 +235,7 @@
// ----------------------------
/obj/machinery/smartfridge/drying_rack
name = "drying rack"
- desc = "A wooden contraption, used to dry plant products, food and leather."
+ desc = "A wooden contraption, used to dry plant products, food and hide."
icon = 'icons/obj/hydroponics/equipment.dmi'
icon_state = "drying_rack"
use_power = IDLE_POWER_USE
@@ -277,6 +280,11 @@
return TRUE
return FALSE
+// /obj/machinery/smartfridge/drying_rack/powered() do we have this? no.
+// if(!anchored)
+// return FALSE
+// return ..()
+
/obj/machinery/smartfridge/drying_rack/power_change()
if(powered() && anchored)
stat &= ~NOPOWER
@@ -285,6 +293,10 @@
toggle_drying(TRUE)
update_icon()
+ // . = ..()
+ // if(!powered())
+ // toggle_drying(TRUE)
+
/obj/machinery/smartfridge/drying_rack/load() //For updating the filled overlay
..()
update_icon()
@@ -308,7 +320,7 @@
var/obj/item/reagent_containers/food/snacks/S = O
if(S.dried_type)
return TRUE
- if(istype(O, /obj/item/stack/sheet/wetleather/))
+ if(istype(O, /obj/item/stack/sheet/wetleather/)) //no wethide
return TRUE
return FALSE
@@ -386,19 +398,19 @@
/obj/machinery/smartfridge/extract/preloaded
initial_contents = list(/obj/item/slime_scanner = 2)
-// ------------------------- You think you're better than Chem, huh?
+// -------------------------
// Organ Surgery Smartfridge
-// ------------------------- Just wait till Tamiorgans
+// -------------------------
/obj/machinery/smartfridge/organ
name = "smart organ storage"
desc = "A refrigerated storage unit for organ storage."
- max_n_of_items = 25 //vastly lower to prevent processing too long
+ max_n_of_items = 20 //vastly lower to prevent processing too long
var/repair_rate = 0
/obj/machinery/smartfridge/organ/accept_check(obj/item/O)
- if(istype(O, /obj/item/organ))
+ if(isorgan(O) || isbodypart(O))
return TRUE
- if(istype(O, /obj/item/reagent_containers/syringe))
+ if(istype(O, /obj/item/reagent_containers/syringe)) //other medical things.
return TRUE
if(istype(O, /obj/item/reagent_containers/glass/bottle))
return TRUE
@@ -410,7 +422,7 @@
. = ..()
if(!.) //if the item loads, clear can_decompose
return
- if(istype(O, /obj/item/organ))
+ if(isorgan(O))
var/obj/item/organ/organ = O
organ.organ_flags |= ORGAN_FROZEN
@@ -426,12 +438,13 @@
return
O.applyOrganDamage(-repair_rate)
-/obj/machinery/smartfridge/organ/Exited(obj/item/organ/AM, atom/newLoc)
+/obj/machinery/smartfridge/organ/Exited(atom/movable/AM, atom/newLoc)
. = ..()
- if(istype(AM))
- AM.organ_flags &= ~ORGAN_FROZEN
+ if(isorgan(AM))
+ var/obj/item/organ/O = AM
+ O.organ_flags &= ~ORGAN_FROZEN
-/obj/machinery/smartfridge/organ/preloaded
+/obj/machinery/smartfridge/organ/preloaded //cit specific??????
initial_contents = list(
/obj/item/reagent_containers/medspray/synthtissue = 1,
/obj/item/reagent_containers/medspray/sterilizine = 1)
@@ -450,6 +463,15 @@
desc = "A refrigerated storage unit for medicine storage."
/obj/machinery/smartfridge/chemistry/accept_check(obj/item/O)
+ var/static/list/chemfridge_typecache = typecacheof(list(
+ /obj/item/reagent_containers/syringe,
+ /obj/item/reagent_containers/glass/bottle,
+ /obj/item/reagent_containers/glass/beaker,
+ /obj/item/reagent_containers/spray,
+ // /obj/item/reagent_containers/medigel,
+ /obj/item/reagent_containers/chem_pack
+ ))
+
if(istype(O, /obj/item/storage/pill_bottle))
if(O.contents.len)
for(var/obj/item/I in O)
@@ -463,7 +485,7 @@
return TRUE
if(!O.reagents || !O.reagents.reagent_list.len) // other empty containers not accepted
return FALSE
- if(istype(O, /obj/item/reagent_containers/syringe) || istype(O, /obj/item/reagent_containers/glass/bottle) || istype(O, /obj/item/reagent_containers/glass/beaker) || istype(O, /obj/item/reagent_containers/spray) || istype(O, /obj/item/reagent_containers/medspray) || istype(O, /obj/item/reagent_containers/chem_pack))
+ if(is_type_in_typecache(O, chemfridge_typecache))
return TRUE
return FALSE
@@ -487,6 +509,7 @@
/obj/item/reagent_containers/glass/bottle/cold = 1,
/obj/item/reagent_containers/glass/bottle/flu_virion = 1,
/obj/item/reagent_containers/glass/bottle/mutagen = 1,
+ /obj/item/reagent_containers/glass/bottle/sugar = 1,
/obj/item/reagent_containers/glass/bottle/plasma = 1,
/obj/item/reagent_containers/glass/bottle/synaptizine = 1,
/obj/item/reagent_containers/glass/bottle/formaldehyde = 1)
@@ -498,8 +521,8 @@
name = "disk compartmentalizer"
desc = "A machine capable of storing a variety of disks. Denoted by most as the DSU (disk storage unit)."
icon_state = "disktoaster"
- visible_contents = FALSE
pass_flags = PASSTABLE
+ visible_contents = FALSE
/obj/machinery/smartfridge/disks/accept_check(obj/item/O)
if(istype(O, /obj/item/disk/))
diff --git a/code/modules/food_and_drinks/recipes/drinks_recipes.dm b/code/modules/food_and_drinks/recipes/drinks_recipes.dm
index c1a17b87a4..972f9a8cc3 100644
--- a/code/modules/food_and_drinks/recipes/drinks_recipes.dm
+++ b/code/modules/food_and_drinks/recipes/drinks_recipes.dm
@@ -18,12 +18,6 @@
results = list(/datum/reagent/consumable/ethanol/bilk = 2)
required_reagents = list(/datum/reagent/consumable/milk = 1, /datum/reagent/consumable/ethanol/beer = 1)
-/datum/chemical_reaction/icetea
- name = "Iced Tea"
- id = /datum/reagent/consumable/icetea
- results = list(/datum/reagent/consumable/icetea = 4)
- required_reagents = list(/datum/reagent/consumable/ice = 1, /datum/reagent/consumable/tea = 3)
-
/datum/chemical_reaction/icecoffee
name = "Iced Coffee"
id = /datum/reagent/consumable/icecoffee
@@ -581,13 +575,6 @@
required_reagents = list(/datum/reagent/consumable/lemonjuice = 2, /datum/reagent/water = 2, /datum/reagent/consumable/sugar = 1, /datum/reagent/consumable/ice = 1)
mix_message = "You're suddenly reminded of home."
-/datum/chemical_reaction/arnold_palmer
- name = "Arnold Palmer"
- id = /datum/reagent/consumable/tea/arnold_palmer
- results = list(/datum/reagent/consumable/tea/arnold_palmer = 2)
- required_reagents = list(/datum/reagent/consumable/tea = 1, /datum/reagent/consumable/lemonade = 1)
- mix_message = "The smells of fresh green grass and sand traps waft through the air as the mixture turns a friendly yellow-orange."
-
/datum/chemical_reaction/chocolate_milk
name = "chocolate milk"
id = /datum/reagent/consumable/milk/chocolate_milk
@@ -756,12 +743,6 @@
results = list(/datum/reagent/consumable/pinkmilk = 2)
required_reagents = list(/datum/reagent/consumable/strawberryjuice = 1, /datum/reagent/consumable/milk = 1)
-/datum/chemical_reaction/pinktea
- name = "Strawberry Tea"
- id = /datum/reagent/consumable/pinktea
- results = list(/datum/reagent/consumable/pinktea = 5)
- required_reagents = list(/datum/reagent/consumable/strawberryjuice = 1, /datum/reagent/consumable/tea/arnold_palmer = 1, /datum/reagent/consumable/sugar = 1)
-
/datum/chemical_reaction/blank_paper
name = "Blank Paper"
id = /datum/reagent/consumable/ethanol/blank_paper
@@ -903,12 +884,6 @@
results = list(/datum/reagent/consumable/ethanol/mauna_loa = 5)
required_reagents = list(/datum/reagent/consumable/capsaicin = 2, /datum/reagent/consumable/ethanol/kahlua = 1, /datum/reagent/consumable/ethanol/bahama_mama = 2)
-/datum/chemical_reaction/catnip_tea
- name = "Catnip Tea"
- id = /datum/reagent/consumable/catnip_tea
- results = list(/datum/reagent/consumable/catnip_tea = 3)
- required_reagents = list(/datum/reagent/consumable/tea = 5, /datum/reagent/pax/catnip = 2)
-
/datum/chemical_reaction/commander_and_chief
name = "Commander and Chief"
id = /datum/reagent/consumable/ethanol/commander_and_chief
@@ -916,6 +891,76 @@
required_reagents = list(/datum/reagent/consumable/ethanol/alliescocktail = 50, /datum/reagent/consumable/ethanol/champagne = 20, /datum/reagent/consumable/doctor_delight = 10, /datum/reagent/consumable/ethanol/quintuple_sec = 10, /datum/reagent/consumable/ethanol/screwdrivercocktail = 10)
mix_message = "When your powers combine, I am Captain Pl-..."
+////////////////////////////////////////// Tea Base Drinks //////////////////////////////////////
+
+/datum/chemical_reaction/mush
+ name = "Mush Tea"
+ id = /datum/reagent/consumable/tea/mush
+ results = list(/datum/reagent/consumable/tea/mush = 3)
+ required_reagents = list(/datum/reagent/drug/mushroomhallucinogen = 3, /datum/reagent/consumable/tea = 3)
+
+/datum/chemical_reaction/foresttea1
+ name = "Forest Tea"
+ id = /datum/reagent/consumable/tea/forest
+ results = list(/datum/reagent/consumable/tea/forest = 3)
+ required_reagents = list(/datum/reagent/consumable/buzz_fuzz= 3, /datum/reagent/consumable/tea = 3)
+
+/datum/chemical_reaction/foresttea2
+ name = "Forest Tea"
+ id = /datum/reagent/consumable/tea/forest
+ results = list(/datum/reagent/consumable/tea/forest = 3)
+ required_reagents = list(/datum/reagent/consumable/honey = 1, /datum/reagent/consumable/tea = 3)
+
+/datum/chemical_reaction/redtea1
+ name = "Red Tea"
+ id = /datum/reagent/consumable/tea/red
+ results = list(/datum/reagent/consumable/tea/red = 3)
+ required_reagents = list(/datum/reagent/colorful_reagent/crayonpowder/red = 1, /datum/reagent/consumable/tea = 3)
+
+/datum/chemical_reaction/greentea1
+ name = "Green Tea"
+ id = /datum/reagent/consumable/tea/green
+ results = list(/datum/reagent/consumable/tea/green = 3)
+ required_reagents = list(/datum/reagent/colorful_reagent/crayonpowder/green = 1, /datum/reagent/consumable/tea = 3)
+
+/datum/chemical_reaction/redtea2
+ name = "Red Tea"
+ id = /datum/reagent/consumable/tea/red
+ results = list(/datum/reagent/consumable/tea/red = 3)
+ required_reagents = list(/datum/reagent/toxin/teapowder/red = 1, /datum/reagent/water = 3)
+
+/datum/chemical_reaction/greentea2
+ name = "Green Tea"
+ id = /datum/reagent/consumable/tea/green
+ results = list(/datum/reagent/consumable/tea/green = 3)
+ required_reagents = list(/datum/reagent/toxin/teapowder/green = 1, /datum/reagent/water = 3)
+
+/datum/chemical_reaction/arnold_palmer
+ name = "Arnold Palmer"
+ id = /datum/reagent/consumable/tea/arnold_palmer
+ results = list(/datum/reagent/consumable/tea/arnold_palmer = 2)
+ required_reagents = list(/datum/reagent/consumable/tea = 1, /datum/reagent/consumable/lemonade = 1)
+ mix_message = "The smells of fresh green grass and sand traps waft through the air as the mixture turns a friendly yellow-orange."
+
+/datum/chemical_reaction/icetea
+ name = "Iced Tea"
+ id = /datum/reagent/consumable/icetea
+ results = list(/datum/reagent/consumable/icetea = 4)
+ required_reagents = list(/datum/reagent/consumable/ice = 1, /datum/reagent/consumable/tea = 3)
+
+/datum/chemical_reaction/pinktea
+ name = "Strawberry Tea"
+ id = /datum/reagent/consumable/pinktea
+ results = list(/datum/reagent/consumable/pinktea = 5)
+ required_reagents = list(/datum/reagent/consumable/strawberryjuice = 1, /datum/reagent/consumable/tea/arnold_palmer = 1, /datum/reagent/consumable/sugar = 1)
+
+/datum/chemical_reaction/catnip_tea
+ name = "Catnip Tea"
+ id = /datum/reagent/consumable/catnip_tea
+ results = list(/datum/reagent/consumable/catnip_tea = 3)
+ required_reagents = list(/datum/reagent/consumable/tea = 5, /datum/reagent/pax/catnip = 2)
+
+
////////////////////////////////////////// Race Base Drinks //////////////////////////////////////
/datum/chemical_reaction/coldscales
diff --git a/code/modules/food_and_drinks/recipes/processor_recipes.dm b/code/modules/food_and_drinks/recipes/processor_recipes.dm
index 1e3afd1cf5..f75cf6ef3a 100644
--- a/code/modules/food_and_drinks/recipes/processor_recipes.dm
+++ b/code/modules/food_and_drinks/recipes/processor_recipes.dm
@@ -6,7 +6,7 @@
/datum/food_processor_process/meat
input = /obj/item/reagent_containers/food/snacks/meat/slab
- output = /obj/item/reagent_containers/food/snacks/meatball
+ output = /obj/item/reagent_containers/food/snacks/rawmeatball
/datum/food_processor_process/bacon
input = /obj/item/reagent_containers/food/snacks/meat/rawcutlet
diff --git a/code/modules/food_and_drinks/recipes/tablecraft/recipes_pies_sweets.dm b/code/modules/food_and_drinks/recipes/tablecraft/recipes_pies_sweets.dm
index 52becf81df..289c698b9a 100644
--- a/code/modules/food_and_drinks/recipes/tablecraft/recipes_pies_sweets.dm
+++ b/code/modules/food_and_drinks/recipes/tablecraft/recipes_pies_sweets.dm
@@ -130,6 +130,18 @@
result = /obj/item/reagent_containers/food/snacks/pie/dulcedebatata
subcategory = CAT_PIE
+/datum/crafting_recipe/food/burek
+ name = "Burek"
+ reqs = list(
+ /datum/reagent/consumable/blackpepper = 3,
+ /datum/reagent/consumable/sodiumchloride = 3,
+ /obj/item/reagent_containers/food/snacks/pizzabread = 2,
+ /obj/item/reagent_containers/food/snacks/meat/cutlet/plain = 6,
+ /obj/item/reagent_containers/food/snacks/butter = 1,
+ )
+ result = /obj/item/reagent_containers/food/snacks/pie/burek
+ subcategory = CAT_PIE
+
/datum/crafting_recipe/food/meatpie
name = "Meat pie"
reqs = list(
@@ -302,4 +314,4 @@
/obj/item/reagent_containers/food/snacks/spiderling = 1
)
result = /obj/item/reagent_containers/food/snacks/spiderlollipop
- subcategory = CAT_PIE
\ No newline at end of file
+ subcategory = CAT_PIE
diff --git a/code/modules/goonchat/browserOutput.dm b/code/modules/goonchat/browserOutput.dm
deleted file mode 100644
index ce27dccb74..0000000000
--- a/code/modules/goonchat/browserOutput.dm
+++ /dev/null
@@ -1,341 +0,0 @@
-/*********************************
-For the main html chat area
-*********************************/
-
-/// Should match the value set in the browser js
-#define MAX_COOKIE_LENGTH 5
-
-//Precaching a bunch of shit. Someone ship this out of here
-GLOBAL_DATUM_INIT(iconCache, /savefile, new("tmp/iconCache.sav")) //Cache of icons for the browser output
-
-//lazy renaming to chat_output, instead renamed to old chatOutput
-/**
- * The chatOutput datum exists to handle the goonchat browser.
- * On client, created on Client/New()
- */
-/datum/chatOutput
- /// The client that owns us.
- var/client/owner
- /// How many times client data has been checked
- var/total_checks = 0
- /// When to next clear the client data checks counter
- var/next_time_to_clear = 0
- /// Has the client loaded the browser output area?
- var/loaded = FALSE
- /// If they haven't loaded chat, this is where messages will go until they do
- var/list/messageQueue
- var/cookieSent = FALSE // Has the client sent a cookie for analysis
- var/broken = FALSE
- var/list/connectionHistory //Contains the connection history passed from chat cookie
- var/adminMusicVolume = 25 //This is for the Play Global Sound verb
-
-/datum/chatOutput/New(client/C)
- owner = C
- messageQueue = list()
- connectionHistory = list()
-
-/**
- * start: Tries to load the chat browser
- * Aborts if a problem is encountered.
- * Async because this is called from Client/New.
- */
-/datum/chatOutput/proc/start()
- set waitfor = FALSE
- //Check for existing chat
- if(!owner)
- return FALSE
-
- if(!winexists(owner, "browseroutput")) // Oh goddamnit.
- broken = TRUE
- message_admins("Couldn't start chat for [key_name_admin(owner)]!")
- . = FALSE
- alert(owner.mob, "Updated chat window does not exist. If you are using a custom skin file please allow the game to update.")
- return
-
- if(winget(owner, "browseroutput", "is-visible") == "true") //Already setup
- doneLoading()
-
- else //Not setup
- load()
-
- return TRUE
-
-/// Loads goonchat and sends assets.
-/datum/chatOutput/proc/load()
- set waitfor = FALSE
- if(!owner)
- return
-
- var/datum/asset/stuff = get_asset_datum(/datum/asset/group/goonchat)
- stuff.send(owner)
-
- owner << browse(file('code/modules/goonchat/browserassets/html/browserOutput.html'), "window=browseroutput")
-
-/// Interprets input from the client. Will send data back if required.
-/datum/chatOutput/Topic(href, list/href_list)
- if(usr.client != owner)
- return TRUE
-
- // Build arguments.
- // Arguments are in the form "param[paramname]=thing"
- var/list/params = list()
- for(var/key in href_list)
- if(length_char(key) > 7 && findtext(key, "param")) // 7 is the amount of characters in the basic param key template.
- var/param_name = copytext_char(key, 7, -1)
- var/item = href_list[key]
-
- params[param_name] = item
-
- var/data // Data to be sent back to the chat.
- switch(href_list["proc"])
- if("doneLoading")
- data = doneLoading(arglist(params))
-
- if("debug")
- data = debug(arglist(params))
-
- if("ping")
- data = ping(arglist(params))
-
- if("analyzeClientData")
- data = analyzeClientData(arglist(params))
-
- if("setMusicVolume")
- data = setMusicVolume(arglist(params))
- if("colorPresetPost") //User just swapped color presets in their goonchat preferences. Do we do anything else?
- switch(href_list["preset"])
- if("light")
- owner.force_white_theme()
- if("dark" || "normal")
- owner.force_dark_theme()
- // if("swaptodarkmode")
- // swaptodarkmode()
- // if("swaptolightmode")
- // swaptolightmode()
-
- if(data)
- ehjax_send(data = data)
-
-
-/// Called on chat output done-loading by JS.
-/datum/chatOutput/proc/doneLoading()
- if(loaded)
- return
-
- testing("Chat loaded for [owner.ckey]")
- loaded = TRUE
- showChat()
-
-
- for(var/message in messageQueue)
- // whitespace has already been handled by the original to_chat
- to_chat(owner, message, handle_whitespace=FALSE)
-
- messageQueue = null
- sendClientData()
-
- syncRegex()
-
- //do not convert to to_chat()
- SEND_TEXT(owner, "Failed to load fancy chat, reverting to old chat. Certain features won't work.")
-
-/// Hides the standard output and makes the browser visible.
-/datum/chatOutput/proc/showChat()
- winset(owner, "output", "is-visible=false")
- winset(owner, "browseroutput", "is-disabled=false;is-visible=true")
-
-/// Calls syncRegex on all currently owned chatOutput datums
-/proc/syncChatRegexes()
- for (var/user in GLOB.clients)
- var/client/C = user
- var/datum/chatOutput/Cchat = C.chatOutput
- if (Cchat && !Cchat.broken && Cchat.loaded)
- Cchat.syncRegex()
-
-/// Used to dynamically add regexes to the browser output. Currently only used by the IC filter.
-/datum/chatOutput/proc/syncRegex()
- var/list/regexes = list()
- /*
- if (config.ic_filter_regex)
- regexes["show_filtered_ic_chat"] = list(
- config.ic_filter_regex.name,
- "ig",
- "$1"
- )
- */
- if (regexes.len)
- ehjax_send(data = list("syncRegex" = regexes))
-
-/// Sends json encoded data to the browser.
-/datum/chatOutput/proc/ehjax_send(client/C = owner, window = "browseroutput", data)
- if(islist(data))
- data = json_encode(data)
- C << output("[data]", "[window]:ehjaxCallback")
-
-/**
- * Sends music data to the browser. If enabled by the browser, it will start playing.
- * Arguments:
- * music must be a https adress.
- * extra_data is a list. The keys "pitch", "start" and "end" are used.
- ** "pitch" determines the playback rate
- ** "start" determines the start time of the sound
- ** "end" determines when the musics stops playing
- */
-/datum/chatOutput/proc/sendMusic(music, pitch, list/extra_data) //someone remove pitch
- if(!findtext(music, GLOB.is_http_protocol))
- return
- var/list/music_data = list("adminMusic" = url_encode(url_encode(music)))
-
- if(extra_data?.len)
- music_data["musicRate"] = extra_data["pitch"] || pitch
- music_data["musicSeek"] = extra_data["start"]
- music_data["musicHalt"] = extra_data["end"]
-
- ehjax_send(data = music_data)
-
-/// Stops music playing throw the browser.
-/datum/chatOutput/proc/stopMusic()
- ehjax_send(data = "stopMusic")
-
-/// Setter for adminMusicVolume. Sanitizes the value to between 0 and 100.
-/datum/chatOutput/proc/setMusicVolume(volume = "")
- if(volume)
- adminMusicVolume = clamp(text2num(volume), 0, 100)
-
-/// Sends client connection details to the chat to handle and save
-/datum/chatOutput/proc/sendClientData()
- //Get dem deets
- var/list/deets = list("clientData" = list())
- deets["clientData"]["ckey"] = owner.ckey
- deets["clientData"]["ip"] = owner.address
- deets["clientData"]["compid"] = owner.computer_id
- var/data = json_encode(deets)
- ehjax_send(data = data)
-
-/// Called by client, sent data to investigate (cookie history so far)
-/datum/chatOutput/proc/analyzeClientData(cookie = "")
- //Spam check
- if(world.time > next_time_to_clear)
- next_time_to_clear = world.time + (3 SECONDS)
- total_checks = 0
-
- total_checks += 1
-
- if(total_checks > SPAM_TRIGGER_AUTOMUTE)
- message_admins("[key_name(owner)] kicked for goonchat topic spam")
- qdel(owner)
- return
-
- if(!cookie)
- return
-
- if(cookie != "none")
- var/list/connData = json_decode(cookie)
- if (connData && islist(connData) && connData.len > 0 && connData["connData"])
- connectionHistory = connData["connData"] //lol fuck
- var/list/found = new()
-
- if(connectionHistory.len > MAX_COOKIE_LENGTH)
- message_admins("[key_name(src.owner)] was kicked for an invalid ban cookie)")
- qdel(owner)
- return
-
- for(var/i in connectionHistory.len to 1 step -1)
- if(QDELETED(owner))
- //he got cleaned up before we were done
- return
- var/list/row = src.connectionHistory[i]
- if (!row || row.len < 3 || (!row["ckey"] || !row["compid"] || !row["ip"])) //Passed malformed history object
- return
- if (world.IsBanned(row["ckey"], row["ip"], row["compid"], real_bans_only=TRUE))
- found = row
- break
- CHECK_TICK
-
- //Uh oh this fucker has a history of playing on a banned account!!
- if (found.len > 0)
- message_admins("[key_name(src.owner)] has a cookie from a banned account! (Matched: [found["ckey"]], [found["ip"]], [found["compid"]])")
- log_admin_private("[key_name(owner)] has a cookie from a banned account! (Matched: [found["ckey"]], [found["ip"]], [found["compid"]])")
-
- cookieSent = TRUE
-
-/// Called by js client every 60 seconds
-/datum/chatOutput/proc/ping()
- return "pong"
-
-/// Called by js client on js error
-/datum/chatOutput/proc/debug(error)
- log_world("\[[time2text(world.realtime, "YYYY-MM-DD hh:mm:ss")]\] Client: [(src.owner.key ? src.owner.key : src.owner)] triggered JS error: [error]")
-
-/// Global chat proc. to_chat_immediate will circumvent SSchat and send data as soon as possible.
-/proc/to_chat_immediate(target, message, handle_whitespace = TRUE, trailing_newline = TRUE, confidential = FALSE)
- if(!target || !message)
- return
-
- if(target == world)
- target = GLOB.clients
-
- var/original_message = message
- if(handle_whitespace)
- message = replacetext(message, "\n", " ")
- message = replacetext(message, "\t", "[FOURSPACES][FOURSPACES]") //EIGHT SPACES IN TOTAL!!
- if(trailing_newline)
- message += " "
-
- if(islist(target))
- // Do the double-encoding outside the loop to save nanoseconds
- var/twiceEncoded = url_encode(url_encode(message))
- for(var/I in target)
- var/client/C = CLIENT_FROM_VAR(I) //Grab us a client if possible
-
- if (!C)
- continue
-
- //Send it to the old style output window.
- SEND_TEXT(C, original_message)
-
- if(!C.chatOutput || C.chatOutput.broken) // A player who hasn't updated his skin file.
- continue
-
- if(!C.chatOutput.loaded)
- //Client still loading, put their messages in a queue
- C.chatOutput.messageQueue += message
- continue
-
- C << output(twiceEncoded, "browseroutput:output")
- else
- var/client/C = CLIENT_FROM_VAR(target) //Grab us a client if possible
-
- if (!C)
- return
-
- //Send it to the old style output window.
- SEND_TEXT(C, original_message)
-
- if(!C.chatOutput || C.chatOutput.broken) // A player who hasn't updated his skin file.
- return
-
- if(!C.chatOutput.loaded)
- //Client still loading, put their messages in a queue
- C.chatOutput.messageQueue += message
- return
-
- // url_encode it TWICE, this way any UTF-8 characters are able to be decoded by the Javascript.
- C << output(url_encode(url_encode(message)), "browseroutput:output")
-
-/// Sends a text message to the target.
-/proc/to_chat(target, message, handle_whitespace = TRUE, trailing_newline = TRUE, confidential = FALSE)
- if(Master.current_runlevel == RUNLEVEL_INIT || !SSchat?.initialized)
- to_chat_immediate(target, message, handle_whitespace, trailing_newline, confidential)
- return
- SSchat.queue(target, message, handle_whitespace, trailing_newline, confidential)
-
-/// Dark mode light mode stuff. Yell at KMC if this breaks! (See darkmode.dm for documentation)
-/datum/chatOutput/proc/swaptolightmode()
- owner.force_white_theme()
-
-/// Light mode stuff. (See darkmode.dm for documentation)
-/datum/chatOutput/proc/swaptodarkmode()
- owner.force_dark_theme()
-
-#undef MAX_COOKIE_LENGTH
diff --git a/code/modules/goonchat/browserassets/css/browserOutput.css b/code/modules/goonchat/browserassets/css/browserOutput.css
deleted file mode 100644
index 93c733391e..0000000000
--- a/code/modules/goonchat/browserassets/css/browserOutput.css
+++ /dev/null
@@ -1,460 +0,0 @@
-/*****************************************
-*
-* GLOBAL STYLES
-*
-******************************************/
-html, body {
- padding: 0;
- margin: 0;
- height: 100%;
- color: #000000;
-}
-body {
- background: #E0E0E0; /*CIT CHANGE - darkens chatbox a lil*/
- font-family: Verdana, sans-serif;
- font-size: 13px;
- line-height: 1.2;
- overflow-x: hidden;
- overflow-y: scroll;
- word-wrap: break-word;
-}
-
-em {
- font-style: normal;
- font-weight: bold;
-}
-
-img {
- margin: 0;
- padding: 0;
- line-height: 1;
- -ms-interpolation-mode: nearest-neighbor;
- image-rendering: pixelated;
-}
-img.icon {
- height: 1em;
- min-height: 16px;
- width: auto;
- vertical-align: bottom;
-}
-
-
-.r:before { /* "repeated" badge class for combined messages */
- content: 'x';
-}
-.r {
- display: inline-block;
- min-width: 0.5em;
- font-size: 0.7em;
- padding: 0.2em 0.3em;
- line-height: 1;
- color: white;
- text-align: center;
- white-space: nowrap;
- vertical-align: middle;
- background-color: crimson;
- border-radius: 10px;
-}
-
-a {color: #0000ff;}
-a.visited {color: #ff00ff;}
-a:visited {color: #ff00ff;}
-a.popt {text-decoration: none;}
-
-/*****************************************
-*
-* OUTPUT NOT RELATED TO ACTUAL MESSAGES
-*
-******************************************/
-#loading {
- position: fixed;
- width: 300px;
- height: 150px;
- text-align: center;
- left: 50%;
- top: 50%;
- margin: -75px 0 0 -150px;
-}
-#loading i {display: block; padding-bottom: 3px;}
-
-#messages {
- font-size: 13px;
- padding: 3px;
- margin: 0;
- word-wrap: break-word;
-}
-#newMessages {
- position: fixed;
- display: block;
- bottom: 0;
- right: 0;
- padding: 8px;
- background: #d0d0d0;
- text-decoration: none;
- font-variant: small-caps;
- font-size: 1.1em;
- font-weight: bold;
- color: #333;
-}
-#newMessages:hover {background: #ccc;}
-#newMessages i {vertical-align: middle; padding-left: 3px;}
-#ping {
- position: fixed;
- top: 0;
- right: 135px;
- width: 45px;
- background: #d0d0d0;
- height: 30px;
- padding: 8px 0 2px 0;
-}
-#ping i {display: block; text-align: center;}
-#ping .ms {
- display: block;
- text-align: center;
- font-size: 8pt;
- padding-top: 2px;
-}
-#userBar {
- position: fixed;
- top: 0;
- right: 0;
-}
-#userBar .subCell {
- background: #d0d0d0;
- height: 30px;
- padding: 5px 0;
- display: block;
- color: #333;
- text-decoration: none;
- line-height: 28px;
- border-top: 1px solid #b4b4b4;
-}
-#userBar .subCell:hover {background: #ccc;}
-#userBar .toggle {
- width: 45px;
- background: #ccc;
- border-top: 0;
- float: right;
- text-align: center;
-}
-#userBar .sub {clear: both; display: none; width: 180px;}
-#userBar .sub.scroll {overflow-y: scroll;}
-#userBar .sub.subCell {padding: 3px 0 3px 8px; line-height: 30px; font-size: 0.9em; clear: both;}
-#userBar .sub span {
- display: block;
- line-height: 30px;
- float: left;
-}
-#userBar .sub i {
- display: block;
- padding: 0 5px;
- font-size: 1.1em;
- width: 22px;
- text-align: center;
- line-height: 30px;
- float: right;
-}
-#userBar .sub input {
- position: absolute;
- padding: 7px 5px;
- width: 121px;
- line-height: 30px;
- float: left;
-}
-#userBar .topCell {border-top: 0;}
-
-/* POPUPS */
-.popup {
- position: fixed;
- top: 50%;
- left: 50%;
- background: #d0d0d0;
-}
-.popup .close {
- position: absolute;
- background: #aaa;
- top: 0;
- right: 0;
- color: #333;
- text-decoration: none;
- z-index: 2;
- padding: 0 10px;
- height: 30px;
- line-height: 30px;
-}
-.popup .close:hover {background: #999;}
-.popup .head {
- background: #999;
- color: #d0d0d0;
- padding: 0 10px;
- height: 30px;
- line-height: 30px;
- text-transform: uppercase;
- font-size: 0.9em;
- font-weight: bold;
- border-bottom: 2px solid green;
-}
-.popup input {border: 1px solid #999; background: #fff; margin: 0; padding: 5px; outline: none; color: #333;}
-.popup input[type=text]:hover, .popup input[type=text]:active, .popup input[type=text]:focus {border-color: green;}
-.popup input[type=submit] {padding: 5px 10px; background: #999; color: #d0d0d0; text-transform: uppercase; font-size: 0.9em; font-weight: bold;}
-.popup input[type=submit]:hover, .popup input[type=submit]:focus, .popup input[type=submit]:active {background: #aaa; cursor: pointer;}
-
-.changeFont {padding: 10px;}
-.changeFont a {display: block; text-decoration: none; padding: 3px; color: #333;}
-.changeFont a:hover {background: #ccc;}
-
-.highlightPopup {padding: 10px; text-align: center;}
-.highlightPopup input[type=text] {display: block; width: 215px; text-align: left; margin-top: 5px;}
-.highlightPopup input.highlightColor {background-color: #FFFF00;}
-.highlightPopup input.highlightTermSubmit {margin-top: 5px;}
-
-/* ADMIN CONTEXT MENU */
-.contextMenu {
- background-color: #d0d0d0;
- position: fixed;
- margin: 2px;
- width: 150px;
-}
-.contextMenu a {
- display: block;
- padding: 2px 5px;
- text-decoration: none;
- color: #333;
-}
-
-.contextMenu a:hover {
- background-color: #ccc;
-}
-
-/* ADMIN FILTER MESSAGES MENU */
-.filterMessages {padding: 5px;}
-.filterMessages div {padding: 2px 0;}
-.filterMessages input {}
-.filterMessages label {}
-
-.icon-stack {height: 1em; line-height: 1em; width: 1em; vertical-align: middle; margin-top: -2px;}
-
-
-/*****************************************
-*
-* OUTPUT ACTUALLY RELATED TO MESSAGES
-*
-******************************************/
-
-/* MOTD */
-.motd {color: #638500; font-family: Verdana, sans-serif;}
-.motd h1, .motd h2, .motd h3, .motd h4, .motd h5, .motd h6 {color: #638500; text-decoration: underline;}
-.motd a, .motd a:link, .motd a:visited, .motd a:active, .motd a:hover {color: #638500;}
-
-/* ADD HERE FOR BOLD */
-.bold, .name, .prefix, .ooc, .looc, .adminooc, .admin, .medal, .yell {font-weight: bold;}
-
-/* ADD HERE FOR ITALIC */
-.italic, .italics, .emote {font-style: italic;}
-
-/* OUTPUT COLORS */
-.highlight {background: yellow;}
-
-h1, h2, h3, h4, h5, h6 {color: #0000ff;font-family: Georgia, Verdana, sans-serif;}
-h1.alert, h2.alert {color: #000000;}
-
-em {font-style: normal; font-weight: bold;}
-
-.ooc {color: #002eb8; font-weight: bold;}
-.looc {color: #6699CC; font-weight: bold;}
-.antagooc {color: #b8002e; font-weight: bold;}
-.adminobserverooc {color: #0099cc; font-weight: bold;}
-.adminooc {color: #700038; font-weight: bold;}
-
-.adminsay {color: #FF4500}
-.admin {color: #386aff; font-weight: bold;}
-
-.name { font-weight: bold;}
-
-.say {}
-.deadsay {color: #5c00e6;}
-.binarysay {color: #20c20e; background-color: #000000; display: block;}
-.binarysay a {color: #00ff00;}
-.binarysay a:active, .binarysay a:visited {color: #88ff88;}
-.radio {color: #008000;}
-.sciradio {color: #993399;}
-.comradio {color: #948f02;}
-.secradio {color: #a30000;}
-.medradio {color: #337296;}
-.engradio {color: #fb5613;}
-.suppradio {color: #a8732b;}
-.servradio {color: #6eaa2c;}
-.syndradio {color: #6d3f40;}
-.centcomradio {color: #686868;}
-.aiprivradio {color: #ff00ff;}
-.redteamradio {color: #ff0000;}
-.blueteamradio {color: #0000ff;}
-
-.yell { font-weight: bold;}
-
-.alert {color: #ff0000;}
-h1.alert, h2.alert {color: #000000;}
-
-.emote { font-style: italic;}
-.selecteddna {color: #ffffff; background-color: #001B1B}
-
-.attack {color: #ff0000;}
-.disarm {color: #990000;}
-.passive {color: #660000;}
-
-.userdanger {color: #ff0000; font-weight: bold; font-size: 185%;}
-.danger {color: #ff0000;}
-.warning {color: #ff0000; font-style: italic;}
-.alertwarning {color: #FF0000; font-weight: bold}
-.boldwarning {color: #ff0000; font-style: italic; font-weight: bold}
-.announce {color: #228b22; font-weight: bold;}
-.boldannounce {color: #ff0000; font-weight: bold;}
-.greenannounce {color: #00ff00; font-weight: bold;}
-.rose {color: #ff5050;}
-.info {color: #0000CC;}
-.notice {color: #000099;}
-.tinynotice {color: #6685f5; font-style: italic; font-size: 85%;}
-.smallnotice {color: #6685f5; font-style: italic; font-size: 90%;}
-.boldnotice {color: #000099; font-weight: bold;}
-.adminnotice {color: #0000ff;}
-.adminhelp {color: #ff0000; font-weight: bold;}
-.unconscious {color: #0000ff; font-weight: bold;}
-.suicide {color: #ff5050; font-style: italic;}
-.green {color: #03ff39;}
-.red {color: #FF0000;}
-.pink {color: #FF69Bf;}
-.blue {color: #0000FF;}
-.nicegreen {color: #14a833;}
-.userlove {color: #FF1493; font-style: italic; font-weight: bold; text-shadow: 0 0 6px #ff6dbc;}
-.love {color: #ff006a; font-style: italic; text-shadow: 0 0 6px #ff6d6d;}
-.shadowling {color: #3b2769;}
-.cult {color: #960000;}
-
-.cultitalic {color: #960000; font-style: italic;}
-.cultbold {color: #960000; font-style: italic; font-weight: bold;}
-.cultboldtalic {color: #960000; font-weight: bold; font-size: 185%;}
-
-.cultlarge {color: #960000; font-weight: bold; font-size: 185%;}
-.narsie {color: #960000; font-weight: bold; font-size: 925%;}
-.narsiesmall {color: #960000; font-weight: bold; font-size: 370%;}
-.colossus {color: #7F282A; font-size: 310%;}
-.hierophant {color: #660099; font-weight: bold; font-style: italic;}
-.hierophant_warning {color: #660099; font-style: italic;}
-.purple {color: #5e2d79;}
-.holoparasite {color: #35333a;}
-
-.revennotice {color: #1d2953;}
-.revenboldnotice {color: #1d2953; font-weight: bold;}
-.revenbignotice {color: #1d2953; font-weight: bold; font-size: 185%;}
-.revenminor {color: #823abb}
-.revenwarning {color: #760fbb; font-style: italic;}
-.revendanger {color: #760fbb; font-weight: bold; font-size: 185%;}
-.umbra {color: #5000A0;}
-.umbra_emphasis {color: #5000A0; font-weight: bold; font-style: italic;}
-.umbra_large {color: #5000A0; font-size: 185%; font-weight: bold; font-style: italic;}
-
-.deconversion_message {color: #5000A0; font-size: 185%; font-style: italic;}
-
-.brass {color: #BE8700;}
-.heavy_brass {color: #BE8700; font-weight: bold; font-style: italic;}
-.large_brass {color: #BE8700; font-size: 185%;}
-.big_brass {color: #BE8700; font-size: 185%; font-weight: bold; font-style: italic;}
-.ratvar {color: #BE8700; font-size: 370%; font-weight: bold; font-style: italic;}
-.alloy {color: #42474D;}
-.heavy_alloy {color: #42474D; font-weight: bold; font-style: italic;}
-.nezbere_large {color: #42474D; font-size: 185%; font-weight: bold; font-style: italic;}
-.nezbere {color: #42474D; font-weight: bold; font-style: italic;}
-.nezbere_small {color: #42474D;}
-.sevtug_large {color: #AF0AAF; font-size: 185%; font-weight: bold; font-style: italic;}
-.sevtug {color: #AF0AAF; font-weight: bold; font-style: italic;}
-.sevtug_small {color: #AF0AAF;}
-.inathneq_large {color: #1E8CE1; font-size: 185%; font-weight: bold; font-style: italic;}
-.inathneq {color: #1E8CE1; font-weight: bold; font-style: italic;}
-.inathneq_small {color: #1E8CE1;}
-.nzcrentr_large {color: #DAAA18; font-size: 185%; font-weight: bold; font-style: italic;}
-.nzcrentr {color: #DAAA18; font-weight: bold; font-style: italic;}
-.nzcrentr_small {color: #DAAA18;}
-.neovgre_large {color: #6E001A; font-size: 185%; font-weight: bold; font-style: italic;}
-.neovgre {color: #6E001A; font-weight: bold; font-style: italic;}
-.neovgre_small {color: #6E001A;}
-
-.newscaster {color: #800000;}
-.ghostalert {color: #5c00e6; font-style: italic; font-weight: bold;}
-
-.alien {color: #543354;}
-.noticealien {color: #00c000;}
-.alertalien {color: #00c000; font-weight: bold;}
-.changeling {color: #800080; font-style: italic;}
-
-.spider {color: #4d004d; font-weight: bold; font-size: 185%;}
-
-.interface {color: #330033;}
-
-.sans {font-family: "Comic Sans MS", cursive, sans-serif;}
-.papyrus {font-family: "Papyrus", cursive, sans-serif;}
-.robot {font-family: "Courier New", cursive, sans-serif;}
-
-.command_headset {font-weight: bold; font-size: 160%;}
-.small {font-size: 60%;}
-.big {font-size: 185%;}
-.reallybig {font-size: 245%;}
-.extremelybig {font-size: 310%;}
-.greentext {color: #00FF00; font-size: 185%;}
-.redtext {color: #FF0000; font-size: 185%;}
-.clown {color: #FF69Bf; font-size: 160%; font-family: "Comic Sans MS", cursive, sans-serif; font-weight: bold;}
-.his_grace {color: #15D512; font-family: "Courier New", cursive, sans-serif; font-style: italic;}
-.spooky {color: #FF6100;}
-.velvet {color: #660015; font-weight: bold; animation: velvet 5000ms infinite;}
-
-.lethal {color: #bf3d3d; font-weight: bold;}
-.stun {color: #0f81bc; font-weight: bold;}
-.ion {color: #d084d6; font-weight: bold;}
-.xray {color: #32c025; font-weight: bold;}
-
-@keyframes velvet {
- 0% { color: #400020; }
- 40% { color: #FF0000; }
- 50% { color: #FF8888; }
- 60% { color: #FF0000; }
- 100% { color: #400020; }
-}
-
-.hypnophrase {color: #202020; font-weight: bold; animation: hypnocolor 1500ms infinite;}
-@keyframes hypnocolor {
- 0% { color: #202020; }
- 25% { color: #4b02ac; }
- 50% { color: #9f41f1; }
- 75% { color: #541c9c; }
- 100% { color: #7adbf3; }
-}
-
-.phobia {color: #dd0000; font-weight: bold; animation: phobia 750ms infinite;}
-@keyframes phobia {
- 0% { color: #f75a5a; }
- 50% { color: #dd0000; }
- 100% { color: #f75a5a; }
-}
-
-
-.icon {height: 1em; width: auto;}
-
-.memo {color: #638500; text-align: center;}
-.memoedit {text-align: center; font-size: 125%;}
-.abductor {color: #800080; font-style: italic;}
-.mind_control {color: #A00D6F; font-size: 100%; font-weight: bold; font-style: italic;}
-.slime {color: #00CED1;}
-.drone {color: #848482;}
-.monkey {color: #975032;}
-.swarmer {color: #2C75FF;}
-.resonate {color: #298F85;}
-
-.monkeyhive {color: #774704;}
-.monkeylead {color: #774704; font-size: 125%;}
-
-.connectionClosed, .fatalError {background: red; color: white; padding: 5px;}
-.connectionClosed.restored {background: green;}
-.internal.boldnshit {color: #000099; font-weight: bold;}
-
-/* HELPER CLASSES */
-.text-normal {font-weight: normal; font-style: normal;}
-.hidden {display: none; visibility: hidden;}
diff --git a/code/modules/goonchat/browserassets/css/browserOutput_dark.css b/code/modules/goonchat/browserassets/css/browserOutput_dark.css
deleted file mode 100644
index 3377bcf72c..0000000000
--- a/code/modules/goonchat/browserassets/css/browserOutput_dark.css
+++ /dev/null
@@ -1,159 +0,0 @@
-html, body {color: #E0E0E0;}
-body {
- background: #171717;
- font-color: #E0E0E0;
- scrollbar-face-color:#1A1A1A;
- scrollbar-track-color:#171717;
- scrollbar-highlight-color:#171717;
-}
-
-a {color: #397ea5;}
-a.visited {color: #7c00e6;}
-a:visited {color: #7c00e6;}
-
-#newMessages {
- background: #242424;
- color: #E0E0E0;
-}
-#newMessages:hover {background: #272727;}
-
-#ping {background: #272727;}
-
-#userBar .subCell {
- background: #272727;
- color: #E0E0E0;
- border-top: 1px solid #171717;
-}
-#userBar .subCell:hover {background: #272727;}
-#userBar .toggle {background: #272727;}
-
-/* MOTD */
-.motd {color: #E0E0E0;}
-.motd h1, .motd h2, .motd h3, .motd h4, .motd h5, .motd h6 {color: #E0E0E0;}
-.motd a, .motd a:link, .motd a:visited, .motd a:active, .motd a:hover {color: #E0E0E0;}
-
-h1, h2, h3, h4, h5, h6 {color: #E0E0E0;}
-h1.alert, h2.alert {color: #E0E0E0;}
-
-.ooc {color: #cca300;}
-.looc {color: #d8b555;}
-.antagooc {color: #ce254f;}
-.adminobserverooc {color: #0099cc;}
-.adminooc {color: #3d5bc3;}
-
-.admin {color: #5975da;}
-
-.deadsay {color: #e2c1ff;}
-.radio {color: #1ecc43;}
-.sciradio {color: #c68cfa;}
-.comradio {color: #5177ff;}
-.secradio {color: #dd3535;}
-.medradio {color: #57b8f0;}
-.engradio {color: #f37746;}
-.suppradio {color: #b88646;}
-.servradio {color: #6ca729;}
-.syndradio {color: #8f4a4b;}
-.centcomradio {color: #2681a5;}
-.aiprivradio {color: #d65d95;}
-.redteamradio {color: #ff4444;}
-.blueteamradio {color: #3434fd;}
-
-.alert {color: #d82020;}
-h1.alert, h2.alert {color: #99aab5;}
-
-.attack {color: #e01c1c;}
-.disarm {color: #b42525;}
-.passive {color: #a00f0f;}
-
-.userdanger {color: #c51e1e;}
-.danger {color: #c51e1e;}
-.warning {color: #c51e1e;}
-.alertwarning {color: #c51e1e;}
-.boldwarning {color: #c51e1e;}
-.announce {color: #c51e1e;}
-.boldannounce {color: #c51e1e;}
-.greenannounce {color: #059223;}
-.info {color: #6685f5;}
-.notice {color: #6685f5;}
-.boldnotice {color: #6685f5;}
-.adminnotice {color: #6685f5;}
-.adminhelp {color: #ff0000;}
-.unconscious {color: #E0E0E0;}
-.red {color: #FF0000;}
-.pink {color: #ff70c1;}
-.blue {color: #215cff;}
-.green {color: #059223;}
-.nicegreen {color: #059223;}
-.userlove {color: #ff42a6; text-shadow: 0 0 6px #82365e;}
-.love {color: #ff4591; text-shadow: 0 0 6px #994449;}
-.shadowling {color: #8e8a99;}
-.cult {color: #aa1c1c;}
-
-.cultitalic {color: #aa1c1c;}
-.cultbold {color: #aa1c1c;}
-.cultboldtalic {color: #aa1c1c;}
-
-.cultlarge {color: #aa1c1c;}
-.narsie {color: #aa1c1c;}
-.narsiesmall {color: #aa1c1c;}
-.hierophant {color: #b441ee;}
-.hierophant_warning {color: #c56bf1;}
-.purple {color: #9956d3;}
-.holoparasite {color: #88809c;}
-
-.revennotice {color: #3645aa;}
-.revenboldnotice {color: #3645aa;}
-.revenbignotice {color: #3645aa;}
-.revenminor {color: #823ddd;}
-.revenwarning {color: #8911d9;}
-.revendanger {color: #8911d9;}
-.umbra {color: #7c00e6;}
-.umbra_emphasis {color: #7c00e6;}
-.umbra_large {color: #7c00e6;}
-
-.deconversion_message {color: #a947ff;}
-
-.alloy {color: #545b64;}
-.heavy_alloy {color: #545b64;}
-.nezbere_large {color: #545b64;}
-.nezbere {color: #545b64;}
-.nezbere_small {color: #545b64;}
-.inathneq_large {color: #1d7dc7;}
-.inathneq {color: #1d7dc7;}
-.inathneq_small {color: #1d7dc7;}
-.neovgre_large {color: #7c0622;}
-.neovgre {color: #7c0622;}
-.neovgre_small {color: #7c0622;}
-
-.newscaster {color: #c05d5d;}
-.ghostalert {color: #6600ff;}
-
-.alien {color: #855d85;}
-.noticealien {color: #059223;}
-.alertalien {color: #059223;}
-.changeling {color: #059223;}
-
-.spider {color: #8800ff;}
-
-.interface {color: #750e75;}
-
-.greentext {color: #059223;}
-.redtext {color: #c51e1e;}
-.clown {color: #ff70c1;}
-.velvet {color: #660015;}
-@keyframes velvet {
- 0% { color: #890020; }
- 40% { color: #c51e1e; }
- 50% { color: #FF8888; }
- 60% { color: #c51e1e; }
- 100% { color: #890020; }
-}
-
-.abductor {color: #c204c2;}
-.mind_control {color: #df3da9;}
-.drone {color: #979795;}
-
-.monkeyhive {color: #a56408;}
-.monkeylead {color: #af6805;}
-
-.internal.boldnshit {color: #3d5bc3;}
diff --git a/code/modules/goonchat/browserassets/css/browserOutput_light.css b/code/modules/goonchat/browserassets/css/browserOutput_light.css
deleted file mode 100644
index b7036c74d8..0000000000
--- a/code/modules/goonchat/browserassets/css/browserOutput_light.css
+++ /dev/null
@@ -1,14 +0,0 @@
-body {background: #F1F1F1;}
-
-#newMessages {background: #ddd;}
-#ping {background: #ddd;}
-
-#userBar .subCell {background: #ddd;}
-
-/* POPUPS */
-.popup {background: #ddd;}
-.popup .head {color: #ddd;}
-.popup input[type=submit] {color: #ddd;}
-
-/* ADMIN CONTEXT MENU */
-.contextMenu {background-color: #ddd;}
diff --git a/code/modules/goonchat/browserassets/html/browserOutput.html b/code/modules/goonchat/browserassets/html/browserOutput.html
deleted file mode 100644
index ce51cd8de8..0000000000
--- a/code/modules/goonchat/browserassets/html/browserOutput.html
+++ /dev/null
@@ -1,58 +0,0 @@
-
-
-
- Chat
-
-
-
-
-
-
-
-
-
-
-
-
-
- Loading...
- If this takes longer than 30 seconds, it will automatically reload a maximum of 5 times.
- If it still doesn't work, use the bug report button at the top right of the window.
-
"
- for(var/V in categories[cat])
- var/datum/design/D = V
- dat += "[D.name]: Make"
- if(cat in timesFiveCategories)
- dat += "x5"
- if(ispath(D.build_path, /obj/item/stack))
- dat += "x10"
- dat += "([CEILING(D.materials[SSmaterials.GetMaterialRef(/datum/material/biomass)]/efficiency, 1)]) "
- dat += "
"
- else
- dat += "
No container inside, please insert container.
"
-
- var/datum/browser/popup = new(user, "biogen", name, 350, 520)
- popup.set_content(dat)
- popup.open()
-
/obj/machinery/biogenerator/AltClick(mob/living/user)
. = ..()
- if(istype(user) && user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK))
+ if(user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK) && can_interact(user))
detach(user)
-/obj/machinery/biogenerator/proc/activate()
- if (usr.stat != CONSCIOUS)
+/**
+ * activate: Activates biomass processing and converts all inserted grown products into biomass
+ *
+ * Arguments:
+ * * user The mob starting the biomass processing
+ */
+/obj/machinery/biogenerator/proc/activate(mob/user)
+ if(user.stat != CONSCIOUS)
return
- if (src.stat != NONE) //NOPOWER etc
+ if(stat != NONE)
return
if(processing)
- to_chat(usr, "The biogenerator is in the process of working.")
+ to_chat(user, "The biogenerator is in the process of working.")
return
var/S = 0
- var/total = 0
for(var/obj/item/reagent_containers/food/snacks/grown/I in contents)
S += 5
- var/nutri_amount = I.reagents.get_reagent_amount(/datum/reagent/consumable/nutriment)
- if(nutri_amount < 0.1)
- total += 1*productivity
+ if(I.reagents.get_reagent_amount(/datum/reagent/consumable/nutriment) < 0.1)
+ points += 1 * productivity
else
- total += nutri_amount*10*productivity
+ points += I.reagents.get_reagent_amount(/datum/reagent/consumable/nutriment) * 10 * productivity
qdel(I)
- points += round(total)
if(S)
processing = TRUE
update_icon()
- updateUsrDialog()
- playsound(src.loc, 'sound/machines/blender.ogg', 50, 1)
- use_power(S*30)
- sleep(S+15/productivity)
+ playsound(loc, 'sound/machines/blender.ogg', 50, TRUE)
+ use_power(S * 30)
+ sleep(S + 15 / productivity)
+ if(QDELETED(src)) //let's not.
+ return
processing = FALSE
update_icon()
- else
- menustat = "void"
/obj/machinery/biogenerator/proc/check_cost(list/materials, multiplier = 1, remove_points = TRUE)
if(materials.len != 1 || materials[1] != SSmaterials.GetMaterialRef(/datum/material/biomass))
return FALSE
- var/cost = CEILING(materials[SSmaterials.GetMaterialRef(/datum/material/biomass)]*multiplier/efficiency, 1)
- if (cost > points)
- menustat = "nopoints"
+ if (materials[SSmaterials.GetMaterialRef(/datum/material/biomass)]*multiplier/efficiency > points)
return FALSE
else
if(remove_points)
- points -= cost
+ points -= materials[SSmaterials.GetMaterialRef(/datum/material/biomass)]*multiplier/efficiency
update_icon()
- updateUsrDialog()
return TRUE
/obj/machinery/biogenerator/proc/check_container_volume(list/reagents, multiplier = 1)
@@ -262,7 +205,6 @@
sum_reagents *= multiplier
if(beaker.reagents.total_volume + sum_reagents > beaker.reagents.maximum_volume)
- menustat = "nobeakerspace"
return FALSE
return TRUE
@@ -284,6 +226,7 @@
var/i = amount
while(i > 0)
if(!check_container_volume(D.make_reagents))
+ say("Warning: Attached container does not have enough free capacity!")
return .
if(!check_cost(D.materials))
return .
@@ -293,51 +236,100 @@
beaker.reagents.add_reagent(R, D.make_reagents[R])
. = 1
--i
-
- menustat = "complete"
update_icon()
return .
/obj/machinery/biogenerator/proc/detach(mob/living/user)
if(beaker)
- user.put_in_hands(beaker)
+ if(can_interact(user))
+ user.put_in_hands(beaker)
+ else
+ beaker.drop_location(get_turf(src))
beaker = null
update_icon()
-/obj/machinery/biogenerator/Topic(href, href_list)
- if(..() || panel_open)
+/obj/machinery/biogenerator/ui_status(mob/user)
+ if(stat & BROKEN || panel_open)
+ return UI_CLOSE
+ return ..()
+
+/obj/machinery/biogenerator/ui_assets(mob/user)
+ return list(
+ get_asset_datum(/datum/asset/spritesheet/research_designs),
+ )
+
+/obj/machinery/biogenerator/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "Biogenerator", name)
+ ui.open()
+
+/obj/machinery/biogenerator/ui_data(mob/user)
+ var/list/data = list()
+ data["beaker"] = beaker ? TRUE : FALSE
+ data["biomass"] = points
+ data["processing"] = processing
+ if(locate(/obj/item/reagent_containers/food/snacks/grown) in contents)
+ data["can_process"] = TRUE
+ else
+ data["can_process"] = FALSE
+ return data
+
+/obj/machinery/biogenerator/ui_static_data(mob/user)
+ var/list/data = list()
+ data["categories"] = list()
+
+ var/categories = show_categories.Copy()
+ for(var/V in categories)
+ categories[V] = list()
+ for(var/V in stored_research.researched_designs)
+ var/datum/design/D = SSresearch.techweb_design_by_id(V)
+ for(var/C in categories)
+ if(C in D.category)
+ categories[C] += D
+
+ for(var/category in categories)
+ var/list/cat = list(
+ "name" = category,
+ "items" = (category == selected_cat ? list() : null))
+ for(var/item in categories[category])
+ var/datum/design/D = item
+ cat["items"] += list(list(
+ "id" = D.id,
+ "name" = D.name,
+ "cost" = D.materials[SSmaterials.GetMaterialRef(/datum/material/biomass)]/efficiency,
+ ))
+ data["categories"] += list(cat)
+
+ return data
+
+/obj/machinery/biogenerator/ui_act(action, list/params)
+ if(..())
return
- usr.set_machine(src)
-
- if(href_list["activate"])
- activate()
- updateUsrDialog()
-
- else if(href_list["detach"])
- detach(usr)
- updateUsrDialog()
-
- else if(href_list["create"])
- var/amount = (text2num(href_list["amount"]))
- //Can't be outside these (if you change this keep a sane limit)
- amount = clamp(amount, 1, 50)
- var/id = href_list["create"]
- if(!stored_research.researched_designs.Find(id))
- //naughty naughty
- stack_trace("ID did not map to a researched datum [id]")
- return
-
- //Get design by id (or may return error design)
- var/datum/design/D = SSresearch.techweb_design_by_id(id)
- //Valid design datum, amount and the datum is not the error design, lets proceed
- if(D && amount && !istype(D, /datum/design/error_design))
- create_product(D, amount)
- //This shouldnt happen normally but href forgery is real
- else
- stack_trace("ID could not be turned into a valid techweb design datum [id]")
- updateUsrDialog()
-
- else if(href_list["menu"])
- menustat = "menu"
- updateUsrDialog()
+ switch(action)
+ if("activate")
+ activate(usr)
+ return TRUE
+ if("detach")
+ detach(usr)
+ return TRUE
+ if("create")
+ var/amount = text2num(params["amount"])
+ amount = clamp(amount, 1, 10)
+ if(!amount)
+ return
+ var/id = params["id"]
+ if(!stored_research.researched_designs.Find(id))
+ stack_trace("ID did not map to a researched datum [id]")
+ return
+ var/datum/design/D = SSresearch.techweb_design_by_id(id)
+ if(D && !istype(D, /datum/design/error_design))
+ create_product(D, amount)
+ else
+ stack_trace("ID could not be turned into a valid techweb design datum [id]")
+ return
+ return TRUE
+ if("select")
+ selected_cat = params["category"]
+ return TRUE
diff --git a/code/modules/hydroponics/gene_modder.dm b/code/modules/hydroponics/gene_modder.dm
index 4e545c13ee..a0c273613f 100644
--- a/code/modules/hydroponics/gene_modder.dm
+++ b/code/modules/hydroponics/gene_modder.dm
@@ -3,9 +3,9 @@
desc = "An advanced device designed to manipulate plant genetic makeup."
icon = 'icons/obj/hydroponics/equipment.dmi'
icon_state = "dnamod"
- density = TRUE
circuit = /obj/item/circuitboard/machine/plantgenes
- pass_flags = PASSTABLE
+ pass_flags = PASSTABLE | LETPASSTHROW
+ flags_1 = DEFAULT_RICOCHET_1
var/obj/item/seeds/seed
var/obj/item/disk/plantgene/disk
diff --git a/code/modules/hydroponics/grown/ambrosia.dm b/code/modules/hydroponics/grown/ambrosia.dm
index e84025208a..b8480a1901 100644
--- a/code/modules/hydroponics/grown/ambrosia.dm
+++ b/code/modules/hydroponics/grown/ambrosia.dm
@@ -49,6 +49,7 @@
seed = /obj/item/seeds/ambrosia/deus
name = "ambrosia deus branch"
desc = "Eating this makes you feel immortal!"
+ foodtype = VEGETABLES | ANTITOXIC
icon_state = "ambrosiadeus"
filling_color = "#008B8B"
wine_power = 50
diff --git a/code/modules/hydroponics/grown/banana.dm b/code/modules/hydroponics/grown/banana.dm
index 0411a80443..81318f8fe1 100644
--- a/code/modules/hydroponics/grown/banana.dm
+++ b/code/modules/hydroponics/grown/banana.dm
@@ -27,6 +27,12 @@
juice_results = list(/datum/reagent/consumable/banana = 0)
distill_reagent = /datum/reagent/consumable/ethanol/bananahonk
+/obj/item/reagent_containers/food/snacks/grown/banana/generate_trash(atom/location)
+ . = ..()
+ var/obj/item/grown/bananapeel/peel = .
+ if(istype(peel))
+ peel.grind_results = list(/datum/reagent/consumable/banana_peel = seed.potency * 0.2)
+
/obj/item/reagent_containers/food/snacks/grown/banana/suicide_act(mob/user)
user.visible_message("[user] is aiming [src] at [user.p_them()]self! It looks like [user.p_theyre()] trying to commit suicide!")
playsound(loc, 'sound/items/bikehorn.ogg', 50, 1, -1)
diff --git a/code/modules/hydroponics/grown/cannabis.dm b/code/modules/hydroponics/grown/cannabis.dm
index 621e79fb77..e4fe7fcdbc 100644
--- a/code/modules/hydroponics/grown/cannabis.dm
+++ b/code/modules/hydroponics/grown/cannabis.dm
@@ -14,9 +14,7 @@
icon_dead = "cannabis-dead" // Same for the dead icon
genes = list(/datum/plant_gene/trait/repeated_harvest)
mutatelist = list(/obj/item/seeds/cannabis/rainbow,
- /obj/item/seeds/cannabis/death,
- /obj/item/seeds/cannabis/white,
- /obj/item/seeds/cannabis/ultimate)
+ /obj/item/seeds/cannabis/death)
reagents_add = list(/datum/reagent/drug/space_drugs = 0.15, /datum/reagent/toxin/lipolicide = 0.35) // gives u the munchies
@@ -27,7 +25,7 @@
species = "megacannabis"
plantname = "Rainbow Weed"
product = /obj/item/reagent_containers/food/snacks/grown/cannabis/rainbow
- mutatelist = list()
+ mutatelist = list(/obj/item/seeds/cannabis/ultimate)
reagents_add = list(/datum/reagent/toxin/mindbreaker = 0.15, /datum/reagent/toxin/lipolicide = 0.35)
rarity = 40
@@ -38,7 +36,7 @@
species = "blackcannabis"
plantname = "Deathweed"
product = /obj/item/reagent_containers/food/snacks/grown/cannabis/death
- mutatelist = list()
+ mutatelist = list(/obj/item/seeds/cannabis/white)
reagents_add = list(/datum/reagent/toxin/cyanide = 0.35, /datum/reagent/drug/space_drugs = 0.15, /datum/reagent/toxin/lipolicide = 0.15)
rarity = 40
@@ -106,6 +104,7 @@
seed = /obj/item/seeds/cannabis/death
name = "death cannabis leaf"
desc = "Looks a bit dark. Oh well."
+ foodtype = VEGETABLES | TOXIC
icon_state = "blackcannabis"
wine_power = 40
@@ -113,6 +112,7 @@
seed = /obj/item/seeds/cannabis/white
name = "white cannabis leaf"
desc = "It feels smooth and nice to the touch."
+ foodtype = VEGETABLES | ANTITOXIC
icon_state = "whitecannabis"
wine_power = 10
diff --git a/code/modules/hydroponics/grown/chili.dm b/code/modules/hydroponics/grown/chili.dm
index 1f60afe655..001a90b441 100644
--- a/code/modules/hydroponics/grown/chili.dm
+++ b/code/modules/hydroponics/grown/chili.dm
@@ -81,7 +81,8 @@
wine_power = 50
/obj/item/reagent_containers/food/snacks/grown/ghost_chili/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
- if( ismob(loc) )
+ . = ..()
+ if(ishuman(loc))
held_mob = loc
START_PROCESSING(SSobj, src)
diff --git a/code/modules/hydroponics/grown/citrus.dm b/code/modules/hydroponics/grown/citrus.dm
index 6ac7bbcfcb..e199c2c6b1 100644
--- a/code/modules/hydroponics/grown/citrus.dm
+++ b/code/modules/hydroponics/grown/citrus.dm
@@ -29,30 +29,11 @@
seed = /obj/item/seeds/lime
name = "lime"
desc = "It's so sour, your face will twist."
+ foodtype = FRUIT | ANTITOXIC
icon_state = "lime"
filling_color = "#00FF00"
juice_results = list(/datum/reagent/consumable/limejuice = 0)
-// Electric Lime
-/obj/item/seeds/lime/electric
- name = "pack of electric lime seeds"
- desc = "Electrically sour seeds."
- icon_state = "seed-electriclime"
- species = "electric lime"
- plantname = "Electric Lime Tree"
- growing_icon = 'icons/obj/hydroponics/growing_fruits.dmi'
- icon_grow = "lime-grow"
- icon_dead = "lime-dead"
- icon_harvest = "lime-harvest"
- product = /obj/item/reagent_containers/food/snacks/grown/citrus/lime/electric
- genes = list(/datum/plant_gene/trait/repeated_harvest, /datum/plant_gene/trait/cell_charge, /datum/plant_gene/trait/glow/green)
-
-/obj/item/reagent_containers/food/snacks/grown/citrus/lime/electric
- seed = /obj/item/seeds/lime/electric
- name = "electric lime"
- desc = "It's so sour, you'll be shocked!"
- icon_state = "electriclime"
-
// Orange
/obj/item/seeds/orange
name = "pack of orange seeds"
diff --git a/code/modules/hydroponics/grown/garlic.dm b/code/modules/hydroponics/grown/garlic.dm
index 4184b85008..2cc3f41860 100644
--- a/code/modules/hydroponics/grown/garlic.dm
+++ b/code/modules/hydroponics/grown/garlic.dm
@@ -9,6 +9,9 @@
potency = 25
growthstages = 3
growing_icon = 'icons/obj/hydroponics/growing_vegetables.dmi'
+ icon_grow = "garlic-grow"
+ icon_harvest = "garlic-harvest"
+ icon_dead = "garlic-dead"
reagents_add = list(/datum/reagent/consumable/garlic = 0.15, /datum/reagent/consumable/nutriment = 0.1)
/obj/item/reagent_containers/food/snacks/grown/garlic
diff --git a/code/modules/hydroponics/grown/grass_carpet.dm b/code/modules/hydroponics/grown/grass_carpet.dm
index 3b5159465c..a74850f3be 100644
--- a/code/modules/hydroponics/grown/grass_carpet.dm
+++ b/code/modules/hydroponics/grown/grass_carpet.dm
@@ -51,6 +51,7 @@
icon_grow = "fairygrass-grow"
icon_dead = "fairygrass-dead"
genes = list(/datum/plant_gene/trait/repeated_harvest, /datum/plant_gene/trait/glow/blue)
+ mutatelist = list (/obj/item/seeds/grass/carpet)
reagents_add = list(/datum/reagent/consumable/nutriment = 0.02, /datum/reagent/hydrogen = 0.05, /datum/reagent/drug/space_drugs = 0.15)
/obj/item/reagent_containers/food/snacks/grown/grass/fairy
@@ -99,7 +100,7 @@
species = "carpet"
plantname = "Carpet"
product = /obj/item/reagent_containers/food/snacks/grown/grass/carpet
- mutatelist = list()
+ mutatelist = list(/obj/item/seeds/grass/fairy)
rarity = 10
/obj/item/reagent_containers/food/snacks/grown/grass/carpet
diff --git a/code/modules/hydroponics/grown/tea_coffee.dm b/code/modules/hydroponics/grown/tea_coffee.dm
index de27d1eed7..223b2c7bce 100644
--- a/code/modules/hydroponics/grown/tea_coffee.dm
+++ b/code/modules/hydroponics/grown/tea_coffee.dm
@@ -44,7 +44,7 @@
filling_color = "#4582B4"
grind_results = list(/datum/reagent/toxin/teapowder = 0, /datum/reagent/medicine/salglu_solution = 0)
-// Kitty drugs
+// Catnip
/obj/item/seeds/tea/catnip
name = "pack of catnip seeds"
icon_state = "seed-catnip"
@@ -52,6 +52,9 @@
species = "catnip"
plantname = "Catnip Plant"
growthstages = 3
+ icon_grow = "catnip-grow"
+ icon_harvest = "catnip-harvest"
+ icon_dead = "tea-dead"
product = /obj/item/reagent_containers/food/snacks/grown/tea/catnip
reagents_add = list(/datum/reagent/pax/catnip = 0.1, /datum/reagent/consumable/nutriment/vitamin = 0.06, /datum/reagent/toxin/teapowder = 0.3)
rarity = 50
diff --git a/code/modules/hydroponics/hydroitemdefines.dm b/code/modules/hydroponics/hydroitemdefines.dm
index 9b5983c8e9..f6be9db9a2 100644
--- a/code/modules/hydroponics/hydroitemdefines.dm
+++ b/code/modules/hydroponics/hydroitemdefines.dm
@@ -82,7 +82,7 @@
custom_materials = list(/datum/material/iron = 15000)
attack_verb = list("chopped", "torn", "cut")
hitsound = 'sound/weapons/bladeslice.ogg'
- sharpness = IS_SHARP
+ sharpness = SHARP_EDGED
/obj/item/hatchet/Initialize()
. = ..()
@@ -103,6 +103,7 @@
throwforce = 5
throw_speed = 2
throw_range = 3
+ attack_speed = CLICK_CD_MELEE
w_class = WEIGHT_CLASS_BULKY
flags_1 = CONDUCT_1
armour_penetration = 20
@@ -125,9 +126,12 @@
playsound(src,pick('sound/misc/desceration-01.ogg','sound/misc/desceration-02.ogg','sound/misc/desceration-01.ogg') ,50, 1, -1)
return (BRUTELOSS)
-/obj/item/scythe/pre_attack(atom/A, mob/living/user, params)
+/obj/item/scythe/pre_attack(atom/A, mob/living/user, params, attackchain_flags, damage_multiplier)
+ . = ..()
+ if(. & STOP_ATTACK_PROC_CHAIN)
+ return
if(swiping || !istype(A, /obj/structure/spacevine) || get_turf(A) == get_turf(user))
- return ..()
+ return
else
var/turf/user_turf = get_turf(user)
var/dir_to_target = get_dir(user_turf, get_turf(A))
@@ -138,11 +142,12 @@
var/turf/T = get_step(user_turf, turn(dir_to_target, i))
for(var/obj/structure/spacevine/V in T)
if(user.Adjacent(V))
- melee_attack_chain(user, V)
+ melee_attack_chain(user, V, attackchain_flags = ATTACK_IGNORE_CLICKDELAY)
stam_gain += 5 //should be hitcost
swiping = FALSE
stam_gain += 2 //Initial hitcost
user.adjustStaminaLoss(-stam_gain)
+ user.DelayNextAction()
// *************************************
// Nutrient defines for hydroponics
@@ -192,4 +197,4 @@
/obj/item/reagent_containers/glass/bottle/killer/pestkiller
name = "bottle of pest spray"
desc = "Contains a pesticide."
- list_reagents = list(/datum/reagent/toxin/pestkiller = 50)
\ No newline at end of file
+ list_reagents = list(/datum/reagent/toxin/pestkiller = 50)
diff --git a/code/modules/hydroponics/hydroponics.dm b/code/modules/hydroponics/hydroponics.dm
index 06179d1087..a208f2de3c 100644
--- a/code/modules/hydroponics/hydroponics.dm
+++ b/code/modules/hydroponics/hydroponics.dm
@@ -30,7 +30,7 @@
var/self_sufficiency_req = 20 //Required total dose to make a self-sufficient hydro tray. 1:1 with earthsblood.
var/self_sufficiency_progress = 0
var/self_sustaining = FALSE //If the tray generates nutrients and water on its own
-
+ var/canirrigate = TRUE //tin
/obj/machinery/hydroponics/constructable
name = "hydroponics tray"
@@ -847,12 +847,13 @@
if (!anchored)
to_chat(user, "Anchor the tray first!")
return
- using_irrigation = !using_irrigation
- O.play_tool_sound(src)
- user.visible_message("[user] [using_irrigation ? "" : "dis"]connects [src]'s irrigation hoses.", \
- "You [using_irrigation ? "" : "dis"]connect [src]'s irrigation hoses.")
- for(var/obj/machinery/hydroponics/h in range(1,src))
- h.update_icon()
+ if(canirrigate)
+ using_irrigation = !using_irrigation
+ O.play_tool_sound(src)
+ user.visible_message("[user] [using_irrigation ? "" : "dis"]connects [src]'s irrigation hoses.", \
+ "You [using_irrigation ? "" : "dis"]connect [src]'s irrigation hoses.")
+ for(var/obj/machinery/hydroponics/h in range(1,src))
+ h.update_icon()
else if(istype(O, /obj/item/shovel/spade))
if(!myseed && !weedlevel)
@@ -910,11 +911,14 @@
harvest = 0
lastproduce = age
if(istype(myseed, /obj/item/seeds/replicapod))
- to_chat(user, "You harvest from the [myseed.plantname].")
+ if(user)//runtimes
+ to_chat(user, "You harvest from the [myseed.plantname].")
else if(myseed.getYield() <= 0)
- to_chat(user, "You fail to harvest anything useful!")
+ if(user)
+ to_chat(user, "You fail to harvest anything useful!")
else
- to_chat(user, "You harvest [myseed.getYield()] items from the [myseed.plantname].")
+ if(user)
+ to_chat(user, "You harvest [myseed.getYield()] items from the [myseed.plantname].")
if(!myseed.get_gene(/datum/plant_gene/trait/repeated_harvest))
qdel(myseed)
myseed = null
diff --git a/code/modules/hydroponics/seed_extractor.dm b/code/modules/hydroponics/seed_extractor.dm
index 63b96632e6..71701d9637 100644
--- a/code/modules/hydroponics/seed_extractor.dm
+++ b/code/modules/hydroponics/seed_extractor.dm
@@ -1,3 +1,18 @@
+/**
+ * Finds and extracts seeds from an object
+ *
+ * Checks if the object is such that creates a seed when extracted. Used by seed
+ * extractors or posably anything that would create seeds in some way. The seeds
+ * are dropped either at the extractor, if it exists, or where the original object
+ * was and it qdel's the object
+ *
+ * Arguments:
+ * * O - Object containing the seed, can be the loc of the dumping of seeds
+ * * t_max - Amount of seed copies to dump, -1 is ranomized
+ * * extractor - Seed Extractor, used as the dumping loc for the seeds and seed multiplier
+ * * user - checks if we can remove the object from the inventory
+ * *
+ */
/proc/seedify(obj/item/O, t_max, obj/machinery/seed_extractor/extractor, mob/living/user)
var/t_amount = 0
var/list/seeds = list()
@@ -46,20 +61,22 @@
icon_state = "sextractor"
density = TRUE
circuit = /obj/item/circuitboard/machine/seed_extractor
- var/piles = list()
+ /// Associated list of seeds, they are all weak refs. We check the len to see how many refs we have for each
+ // seed
+ var/list/piles = list()
var/max_seeds = 1000
var/seed_multiplier = 1
/obj/machinery/seed_extractor/RefreshParts()
for(var/obj/item/stock_parts/matter_bin/B in component_parts)
- max_seeds = 1000 * B.rating
+ max_seeds = initial(max_seeds) * B.rating
for(var/obj/item/stock_parts/manipulator/M in component_parts)
- seed_multiplier = M.rating
+ seed_multiplier = initial(seed_multiplier) * M.rating
/obj/machinery/seed_extractor/examine(mob/user)
. = ..()
if(in_range(user, src) || isobserver(user))
- . += "The status display reads: Extracting [seed_multiplier] seed(s) per piece of produce. Machine can store up to [max_seeds] seeds."
+ . += "The status display reads: Extracting [seed_multiplier] seed(s) per piece of produce. Machine can store up to [max_seeds]% seeds."
/obj/machinery/seed_extractor/attackby(obj/item/O, mob/user, params)
@@ -102,78 +119,26 @@
else
return ..()
-/datum/seed_pile
- var/name = ""
- var/lifespan = 0 //Saved stats
- var/endurance = 0
- var/maturation = 0
- var/production = 0
- var/yield = 0
- var/potency = 0
- var/amount = 0
+/**
+ * Generate seed string
+ *
+ * Creates a string based of the traits of a seed. We use this string as a bucket for all
+ * seeds that match as well as the key the ui uses to get the seed. We also use the key
+ * for the data shown in the ui. Javascript parses this string to display
+ *
+ * Arguments:
+ * * O - seed to generate the string from
+ */
+/obj/machinery/seed_extractor/proc/generate_seed_string(obj/item/seeds/O)
+ return "name=[O.name];lifespan=[O.lifespan];endurance=[O.endurance];maturation=[O.maturation];production=[O.production];yield=[O.yield];potency=[O.potency];instability=0"
-/datum/seed_pile/New(var/name, var/life, var/endur, var/matur, var/prod, var/yie, var/poten, var/am = 1)
- src.name = name
- src.lifespan = life
- src.endurance = endur
- src.maturation = matur
- src.production = prod
- src.yield = yie
- src.potency = poten
- src.amount = am
-
-/obj/machinery/seed_extractor/ui_interact(mob/user)
- . = ..()
- if (stat)
- return FALSE
-
- var/dat = "Stored seeds: "
-
- if (contents.len == 0)
- dat += "No seeds"
- else
- dat += "
Name
Lifespan
Endurance
Maturation
Production
Yield
Potency
Stock
"
- for (var/datum/seed_pile/O in piles)
- dat += "
"
- var/datum/browser/popup = new(user, "seed_ext", name, 700, 400)
- popup.set_content(dat)
- popup.open()
- return
-
-/obj/machinery/seed_extractor/Topic(var/href, var/list/href_list)
- if(..())
- return
- usr.set_machine(src)
-
- href_list["li"] = text2num(href_list["li"])
- href_list["en"] = text2num(href_list["en"])
- href_list["ma"] = text2num(href_list["ma"])
- href_list["pr"] = text2num(href_list["pr"])
- href_list["yi"] = text2num(href_list["yi"])
- href_list["pot"] = text2num(href_list["pot"])
-
- for (var/datum/seed_pile/N in piles)//Find the pile we need to reduce...
- if (href_list["name"] == N.name && href_list["li"] == N.lifespan && href_list["en"] == N.endurance && href_list["ma"] == N.maturation && href_list["pr"] == N.production && href_list["yi"] == N.yield && href_list["pot"] == N.potency)
- if(N.amount <= 0)
- return
- N.amount = max(N.amount - 1, 0)
- if (N.amount <= 0)
- piles -= N
- qdel(N)
- break
-
- for (var/obj/T in contents)//Now we find the seed we need to vend
- var/obj/item/seeds/O = T
- if (O.plantname == href_list["name"] && O.lifespan == href_list["li"] && O.endurance == href_list["en"] && O.maturation == href_list["ma"] && O.production == href_list["pr"] && O.yield == href_list["yi"] && O.potency == href_list["pot"])
- O.forceMove(drop_location())
- break
-
- src.updateUsrDialog()
- return
+/** Add Seeds Proc.
+ *
+ * Adds the seeds to the contents and to an associated list that pregenerates the data
+ * needed to go to the ui handler
+ *
+ **/
/obj/machinery/seed_extractor/proc/add_seed(obj/item/seeds/O)
if(contents.len >= 999)
to_chat(usr, "\The [src] is full.")
@@ -188,10 +153,47 @@
if(!M.transferItemToLoc(O, src))
return FALSE
- . = TRUE
- for (var/datum/seed_pile/N in piles)
- if (O.plantname == N.name && O.lifespan == N.lifespan && O.endurance == N.endurance && O.maturation == N.maturation && O.production == N.production && O.yield == N.yield && O.potency == N.potency)
- ++N.amount
- return
+ var/seed_string = generate_seed_string(O)
+ if(piles[seed_string])
+ piles[seed_string] += WEAKREF(O)
+ else
+ piles[seed_string] = list(WEAKREF(O))
+
+ . = TRUE
+
+/obj/machinery/seed_extractor/ui_state(mob/user)
+ return GLOB.notcontained_state
+
+/obj/machinery/seed_extractor/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "SeedExtractor", name)
+ ui.open()
+
+/obj/machinery/seed_extractor/ui_data()
+ var/list/V = list()
+ for(var/key in piles)
+ if(piles[key])
+ var/len = length(piles[key])
+ if(len)
+ V[key] = len
+
+ . = list()
+ .["seeds"] = V
+
+/obj/machinery/seed_extractor/ui_act(action, params)
+ if(..())
+ return
+
+ switch(action)
+ if("select")
+ var/item = params["item"]
+ if(piles[item] && length(piles[item]) > 0)
+ var/datum/weakref/WO = piles[item][1]
+ var/obj/item/seeds/O = WO.resolve()
+ if(O)
+ piles[item] -= WO
+ O.forceMove(drop_location())
+ . = TRUE
+ //to_chat(usr, "[src] clanks to life briefly before vending [prize.equipment_name]!")
- piles += new /datum/seed_pile(O.plantname, O.lifespan, O.endurance, O.maturation, O.production, O.yield, O.potency)
diff --git a/code/modules/hydroponics/seeds.dm b/code/modules/hydroponics/seeds.dm
index 5e49a32a23..c7314bf180 100644
--- a/code/modules/hydroponics/seeds.dm
+++ b/code/modules/hydroponics/seeds.dm
@@ -190,6 +190,31 @@ obj/item/seeds/proc/is_gene_forbidden(typepath)
parent.update_tray(user)
return result
+/obj/item/seeds/proc/harvest_userless()
+ var/obj/machinery/hydroponics/parent = loc //for ease of access
+ var/t_amount = 0
+ var/list/result = list()
+ var/output_loc = parent.loc
+ var/product_name
+ while(t_amount < getYield())
+ var/obj/item/reagent_containers/food/snacks/grown/t_prod = new product(output_loc, src)
+ if(parent.myseed.plantname != initial(parent.myseed.plantname))
+ t_prod.name = lowertext(parent.myseed.plantname)
+ if(productdesc)
+ t_prod.desc = productdesc
+ t_prod.seed.name = parent.myseed.name
+ t_prod.seed.desc = parent.myseed.desc
+ t_prod.seed.plantname = parent.myseed.plantname
+ result.Add(t_prod) // User gets a consumable
+ if(!t_prod)
+ return
+ t_amount++
+ product_name = parent.myseed.plantname
+ if(getYield() >= 1)
+ SSblackbox.record_feedback("tally", "food_harvested", getYield(), product_name)
+ parent.investigate_log("autmoatic harvest of [getYield()] of [src], with seed traits [english_list(genes)] and reagents_add [english_list(reagents_add)] and potency [potency].", INVESTIGATE_BOTANY)
+ parent.update_tray()
+ return result
/obj/item/seeds/proc/prepare_result(var/obj/item/reagent_containers/food/snacks/grown/T)
if(!T.reagents)
diff --git a/code/modules/instruments/songs/_song.dm b/code/modules/instruments/songs/_song.dm
index 463398d2f2..a0d96658e6 100644
--- a/code/modules/instruments/songs/_song.dm
+++ b/code/modules/instruments/songs/_song.dm
@@ -2,6 +2,12 @@
#define MUSIC_MAXLINES 1000
#define MUSIC_MAXLINECHARS 300
+/**
+ * # Song datum
+ *
+ * These are the actual backend behind instruments.
+ * They attach to an atom and provide the editor + playback functionality.
+ */
/datum/song
/// Name of the song
var/name = "Untitled"
@@ -15,6 +21,9 @@
/// delay between notes in deciseconds
var/tempo = 5
+ /// How far we can be heard
+ var/instrument_range = 15
+
/// Are we currently playing?
var/playing = FALSE
@@ -53,17 +62,24 @@
/////////////////// Playing variables ////////////////
/**
- * Only used in synthesized playback - The chords we compiled. Non assoc list of lists:
- * list(list(key1, key2, key3..., tempo_divisor), list(key1, key2..., tempo_divisor), ...)
- * tempo_divisor always exists
- * if key1 (and so if there's no keys) doesn't exist it's a rest
+ * Build by compile_chords()
+ * Must be rebuilt on instrument switch.
* Compilation happens when we start playing and is cleared after we finish playing.
+ * Format: list of chord lists, with chordlists having (key1, key2, key3, tempodiv)
*/
var/list/compiled_chords
+ /// Current section of a long chord we're on, so we don't need to make a billion chords, one for every unit ticklag.
+ var/elapsed_delay
+ /// Amount of delay to wait before playing the next chord
+ var/delay_by
+ /// Current chord we're on.
+ var/current_chord
/// Channel as text = current volume percentage but it's 0 to 100 instead of 0 to 1.
var/list/channels_playing = list()
/// List of channels that aren't being used, as text. This is to prevent unnecessary freeing and reallocations from SSsounds/SSinstruments.
var/list/channels_idle = list()
+ /// Person playing us
+ var/mob/user_playing
//////////////////////////////////////////////////////
/// Last world.time we checked for who can hear us
@@ -72,8 +88,6 @@
var/list/hearing_mobs
/// If this is enabled, some things won't be strictly cleared when they usually are (liked compiled_chords on play stop)
var/debug_mode = FALSE
- /// Last time we processed decay
- var/last_process_decay
/// Max sound channels to occupy
var/max_sound_channels = CHANNELS_PER_INSTRUMENT
/// Current channels, so we can save a length() call.
@@ -113,7 +127,7 @@
var/cached_exponential_dropoff = 1.045
/////////////////////////////////////////////////////////////////////////
-/datum/song/New(atom/parent, list/instrument_ids)
+/datum/song/New(atom/parent, list/instrument_ids, new_range)
SSinstruments.on_song_new(src)
lines = list()
tempo = sanitize_tempo(tempo)
@@ -125,6 +139,8 @@
hearing_mobs = list()
volume = clamp(volume, min_volume, max_volume)
update_sustain()
+ if(new_range)
+ instrument_range = new_range
/datum/song/Destroy()
stop_playing()
@@ -135,12 +151,15 @@
parent = null
return ..()
+/**
+ * Checks and stores which mobs can hear us. Terminates sounds for mobs that leave our range.
+ */
/datum/song/proc/do_hearcheck()
last_hearcheck = world.time
var/list/old = hearing_mobs.Copy()
hearing_mobs.len = 0
var/turf/source = get_turf(parent)
- for(var/mob/M in get_hearers_in_view(15, source))
+ for(var/mob/M in get_hearers_in_view(instrument_range, source))
if(!(M?.client?.prefs?.toggles & SOUND_INSTRUMENTS))
continue
hearing_mobs[M] = get_dist(M, source)
@@ -148,10 +167,15 @@
for(var/i in exited)
terminate_sound_mob(i)
-/// I can either be a datum, id, or path (if the instrument has no id).
+/**
+ * Sets our instrument, caching anything necessary for faster accessing. Accepts an ID, typepath, or instantiated instrument datum.
+ */
/datum/song/proc/set_instrument(datum/instrument/I)
+ terminate_all_sounds()
+ var/old_legacy
if(using_instrument)
using_instrument.songs_using -= src
+ old_legacy = (using_instrument.instrument_flags & INSTRUMENT_LEGACY)
using_instrument = null
cached_samples = null
cached_legacy_ext = null
@@ -162,7 +186,7 @@
if(istype(I))
using_instrument = I
I.songs_using += src
- var/instrument_legacy = CHECK_BITFIELD(I.instrument_flags, INSTRUMENT_LEGACY)
+ var/instrument_legacy = (I.instrument_flags & INSTRUMENT_LEGACY)
if(instrument_legacy)
cached_legacy_ext = I.legacy_instrument_ext
cached_legacy_dir = I.legacy_instrument_path
@@ -170,23 +194,37 @@
else
cached_samples = I.samples
legacy = FALSE
+ if(isnull(old_legacy) || (old_legacy != instrument_legacy))
+ if(playing)
+ compile_chords()
-/// THIS IS A BLOCKING CALL.
+/**
+ * Attempts to start playing our song.
+ */
/datum/song/proc/start_playing(mob/user)
if(playing)
return
if(!using_instrument?.ready())
to_chat(user, "An error has occured with [src]. Please reset the instrument.")
return
+ compile_chords()
+ if(!length(compiled_chords))
+ to_chat(user, "Song is empty.")
+ return
playing = TRUE
- updateDialog()
+ updateDialog(user_playing)
//we can not afford to runtime, since we are going to be doing sound channel reservations and if we runtime it means we have a channel allocation leak.
//wrap the rest of the stuff to ensure stop_playing() is called.
- last_process_decay = world.time
+ do_hearcheck()
+ elapsed_delay = 0
+ delay_by = 0
+ current_chord = 1
+ user_playing = user
START_PROCESSING(SSinstruments, src)
- . = do_play_lines(user)
- stop_playing()
+/**
+ * Stops playing, terminating all sounds if in synthesized mode. Clears hearing_mobs.
+ */
/datum/song/proc/stop_playing()
if(!playing)
return
@@ -196,42 +234,93 @@
STOP_PROCESSING(SSinstruments, src)
terminate_all_sounds(TRUE)
hearing_mobs.len = 0
- updateDialog()
+ user_playing = null
-/// THIS IS A BLOCKING CALL.
-/datum/song/proc/do_play_lines(user)
- if(!playing)
+/**
+ * Processes our song.
+ */
+/datum/song/proc/process_song(wait)
+ if(!length(compiled_chords) || should_stop_playing(user_playing))
+ stop_playing()
return
- do_hearcheck()
- if(legacy)
- do_play_lines_legacy(user)
- else
- do_play_lines_synthesized(user)
+ var/list/chord = compiled_chords[current_chord]
+ if(++elapsed_delay >= delay_by)
+ play_chord(chord)
+ elapsed_delay = 0
+ delay_by = tempodiv_to_delay(chord[length(chord)])
+ current_chord++
+ if(current_chord > length(compiled_chords))
+ if(repeat)
+ repeat--
+ current_chord = 1
+ return
+ else
+ stop_playing()
+ return
+/**
+ * Converts a tempodiv to ticks to elapse before playing the next chord, taking into account our tempo.
+ */
+/datum/song/proc/tempodiv_to_delay(tempodiv)
+ if(!tempodiv)
+ tempodiv = 1 // no division by 0. some song converters tend to use 0 for when it wants to have no div, for whatever reason.
+ return max(1, round((tempo/tempodiv) / world.tick_lag, 1))
+
+/**
+ * Compiles chords.
+ */
+/datum/song/proc/compile_chords()
+ legacy? compile_legacy() : compile_synthesized()
+
+/**
+ * Plays a chord.
+ */
+/datum/song/proc/play_chord(list/chord)
+ // last value is timing information
+ for(var/i in 1 to (length(chord) - 1))
+ legacy? playkey_legacy(chord[i][1], chord[i][2], chord[i][3], user_playing) : playkey_synth(chord[i], user_playing)
+
+/**
+ * Checks if we should halt playback.
+ */
/datum/song/proc/should_stop_playing(mob/user)
return QDELETED(parent) || !using_instrument || !playing
+/**
+ * Sanitizes tempo to a value that makes sense and fits the current world.tick_lag.
+ */
/datum/song/proc/sanitize_tempo(new_tempo)
new_tempo = abs(new_tempo)
return clamp(round(new_tempo, world.tick_lag), world.tick_lag, 5 SECONDS)
+/**
+ * Gets our beats per minute based on our tempo.
+ */
/datum/song/proc/get_bpm()
return 600 / tempo
+/**
+ * Sets our tempo from a beats-per-minute, sanitizing it to a valid number first.
+ */
/datum/song/proc/set_bpm(bpm)
tempo = sanitize_tempo(600 / bpm)
-/// Updates the window for our user. Override in subtypes.
-/datum/song/proc/updateDialog(mob/user = usr)
+/**
+ * Updates the window for our users. Override down the line.
+ */
+/datum/song/proc/updateDialog(mob/user)
ui_interact(user)
/datum/song/process(wait)
if(!playing)
return PROCESS_KILL
- var/delay = world.time - last_process_decay
- process_decay(delay)
- last_process_decay = world.time
+ // it's expected this ticks at every world.tick_lag. if it lags, do not attempt to catch up.
+ process_song(world.tick_lag)
+ process_decay(world.tick_lag)
+/**
+ * Updates our cached linear/exponential falloff stuff, saving calculations down the line.
+ */
/datum/song/proc/update_sustain()
// Exponential is easy
cached_exponential_dropoff = sustain_exponential_dropoff
@@ -241,21 +330,33 @@
var/volume_decrease_per_decisecond = volume_diff / target_duration
cached_linear_dropoff = volume_decrease_per_decisecond
+/**
+ * Setter for setting output volume.
+ */
/datum/song/proc/set_volume(volume)
src.volume = clamp(volume, max(0, min_volume), min(100, max_volume))
update_sustain()
updateDialog()
+/**
+ * Setter for setting how low the volume has to get before a note is considered "dead" and dropped
+ */
/datum/song/proc/set_dropoff_volume(volume)
sustain_dropoff_volume = clamp(volume, INSTRUMENT_MIN_SUSTAIN_DROPOFF, 100)
update_sustain()
updateDialog()
+/**
+ * Setter for setting exponential falloff factor.
+ */
/datum/song/proc/set_exponential_drop_rate(drop)
sustain_exponential_dropoff = clamp(drop, INSTRUMENT_EXP_FALLOFF_MIN, INSTRUMENT_EXP_FALLOFF_MAX)
update_sustain()
updateDialog()
+/**
+ * Setter for setting linear falloff duration.
+ */
/datum/song/proc/set_linear_falloff_duration(duration)
sustain_linear_duration = clamp(duration, 0.1, INSTRUMENT_MAX_TOTAL_SUSTAIN)
update_sustain()
@@ -277,10 +378,8 @@
// subtype for handheld instruments, like violin
/datum/song/handheld
-/datum/song/handheld/updateDialog(mob/user = usr)
- if(user.machine != src)
- return
- parent.ui_interact(user)
+/datum/song/handheld/updateDialog(mob/user)
+ parent.ui_interact(user || usr)
/datum/song/handheld/should_stop_playing(mob/user)
. = ..()
@@ -292,10 +391,8 @@
// subtype for stationary structures, like pianos
/datum/song/stationary
-/datum/song/stationary/updateDialog(mob/user = usr)
- if(user.machine != src)
- return
- parent.ui_interact(user)
+/datum/song/stationary/updateDialog(mob/user)
+ parent.ui_interact(user || usr)
/datum/song/stationary/should_stop_playing(mob/user)
. = ..()
@@ -303,3 +400,19 @@
return TRUE
var/obj/structure/musician/M = parent
return M.should_stop_playing(user)
+
+/datum/song/holoparasite
+ var/mob/living/simple_animal/hostile/guardian/stand
+
+/datum/song/holoparasite/New(atom/parent, list/instrument_ids)
+ . = ..()
+ stand = istype(parent, /mob/living/simple_animal/hostile/guardian) && parent
+
+/datum/song/holoparasite/updateDialog()
+ stand.ui_interact(src)
+
+/datum/song/holoparasite/should_stop_playing(mob/user)
+ return FALSE
+
+/datum/song/holoparasite/check_can_use(mob/user)
+ return (user == stand)
diff --git a/code/modules/instruments/songs/editor.dm b/code/modules/instruments/songs/editor.dm
index d9595797d7..e385eed142 100644
--- a/code/modules/instruments/songs/editor.dm
+++ b/code/modules/instruments/songs/editor.dm
@@ -82,7 +82,6 @@
var/datum/browser/popup = new(user, "instrument", parent?.name || "instrument", 700, 500)
popup.set_content(dat.Join(""))
- popup.set_title_image(user.browse_rsc_icon(parent.icon, parent.icon_state))
popup.open()
/datum/song/proc/ParseSong(text)
@@ -109,8 +108,11 @@
linenum++
updateDialog(usr) // make sure updates when complete
+/datum/song/proc/check_can_use(mob/user)
+ return user.canUseTopic(parent, TRUE, FALSE, FALSE, FALSE)
+
/datum/song/Topic(href, href_list)
- if(!usr.canUseTopic(parent, TRUE, FALSE, FALSE, FALSE))
+ if(!check_can_use(usr))
usr << browse(null, "window=instrument")
usr.unset_machine()
return
diff --git a/code/modules/instruments/songs/play_legacy.dm b/code/modules/instruments/songs/play_legacy.dm
index fa64656ebc..eee9be3cc7 100644
--- a/code/modules/instruments/songs/play_legacy.dm
+++ b/code/modules/instruments/songs/play_legacy.dm
@@ -1,48 +1,52 @@
-/// Playing legacy instruments - None of the "advanced" like sound reservations and decay are invoked.
-/datum/song/proc/do_play_lines_legacy(mob/user)
- while(repeat >= 0)
- var/cur_oct[7]
- var/cur_acc[7]
- for(var/i = 1 to 7)
- cur_oct[i] = 3
- cur_acc[i] = "n"
+/**
+ * Compiles our lines into "chords" with filenames for legacy playback. This makes there have to be a bit of lag at the beginning of the song, but repeats will not have to parse it again, and overall playback won't be impacted by as much lag.
+ */
+/datum/song/proc/compile_legacy()
+ if(!length(src.lines))
+ return
+ var/list/lines = src.lines //cache for hyepr speed!
+ compiled_chords = list()
+ var/list/octaves = list(3, 3, 3, 3, 3, 3, 3)
+ var/list/accents = list("n", "n", "n", "n", "n", "n", "n")
+ for(var/line in lines)
+ var/list/chords = splittext(lowertext(line), ",")
+ for(var/chord in chords)
+ var/list/compiled_chord = list()
+ var/tempodiv = 1
+ var/list/notes_tempodiv = splittext(chord, "/")
+ var/len = length(notes_tempodiv)
+ if(len >= 2)
+ tempodiv = text2num(notes_tempodiv[2])
+ if(len) //some dunkass is going to do ,,,, to make 3 rests instead of ,/1 because there's no standardization so let's be prepared for that.
+ var/list/notes = splittext(notes_tempodiv[1], "-")
+ for(var/note in notes)
+ if(length(note) == 0)
+ continue
+ // 1-7, A-G
+ var/key = text2ascii(note) - 96
+ if((key < 1) || (key > 7))
+ continue
+ for(var/i in 2 to length(note))
+ var/oct_acc = copytext(note, i, i + 1)
+ var/num = text2num(oct_acc)
+ if(!num) //it's an accidental
+ accents[key] = oct_acc //if they misspelled it/fucked up that's on them lmao, no safety checks.
+ else //octave
+ octaves[key] = clamp(num, octave_min, octave_max)
+ compiled_chord[++compiled_chord.len] = list(key, accents[key], octaves[key])
+ compiled_chord += tempodiv //this goes last
+ if(length(compiled_chord))
+ compiled_chords[++compiled_chords.len] = compiled_chord
- for(var/line in lines)
- for(var/beat in splittext(lowertext(line), ","))
- if(should_stop_playing(user))
- return
- var/list/notes = splittext(beat, "/")
- if(length(notes)) //because some jack-butts are going to do ,,,, to symbolize 3 rests instead of something reasonable like ,/1.
- for(var/note in splittext(notes[1], "-"))
- if(length(note) == 0)
- continue
- var/cur_note = text2ascii(note) - 96
- if(cur_note < 1 || cur_note > 7)
- continue
- for(var/i=2 to length(note))
- var/ni = copytext(note,i,i+1)
- if(!text2num(ni))
- if(ni == "#" || ni == "b" || ni == "n")
- cur_acc[cur_note] = ni
- else if(ni == "s")
- cur_acc[cur_note] = "#" // so shift is never required
- else
- cur_oct[cur_note] = text2num(ni)
- playnote_legacy(cur_note, cur_acc[cur_note], cur_oct[cur_note])
- if(notes.len >= 2 && text2num(notes[2]))
- sleep(sanitize_tempo(tempo / text2num(notes[2])))
- else
- sleep(tempo)
- if(should_stop_playing(user))
- return
- repeat--
- updateDialog()
- repeat = 0
-
-// note is a number from 1-7 for A-G
-// acc is either "b", "n", or "#"
-// oct is 1-8 (or 9 for C)
-/datum/song/proc/playnote_legacy(note, acc as text, oct)
+/**
+ * Proc to play a legacy note. Just plays the sound to hearing mobs (and does hearcheck if necessary), no fancy channel/sustain/management.
+ *
+ * Arguments:
+ * * note is a number from 1-7 for A-G
+ * * acc is either "b", "n", or "#"
+ * * oct is 1-8 (or 9 for C)
+ */
+/datum/song/proc/playkey_legacy(note, acc as text, oct, mob/user)
// handle accidental -> B<>C of E<>F
if(acc == "b" && (note == 3 || note == 6)) // C or F
if(note == 3)
diff --git a/code/modules/instruments/songs/play_synthesized.dm b/code/modules/instruments/songs/play_synthesized.dm
index 5e7c5652a0..4df54f5e6b 100644
--- a/code/modules/instruments/songs/play_synthesized.dm
+++ b/code/modules/instruments/songs/play_synthesized.dm
@@ -1,27 +1,7 @@
-/datum/song/proc/do_play_lines_synthesized(mob/user)
- compile_lines()
- while(repeat >= 0)
- if(should_stop_playing(user))
- return
- var/warned = FALSE
- for(var/_chord in compiled_chords)
- if(should_stop_playing(user))
- return
- var/list/chord = _chord
- var/tempodiv = chord[chord.len]
- for(var/i in 1 to chord.len - 1)
- var/key = chord[i]
- if(!playkey_synth(key))
- if(!warned)
- warned = TRUE
- to_chat(user, "Your instrument has ran out of channels. You might be playing your song too fast or be setting sustain to too high of a value. This warning will be suppressed for the rest of this cycle.")
- sleep(sanitize_tempo(tempo / (tempodiv || 1)))
- repeat--
- updateDialog()
- repeat = 0
-
-/// C-Db2-A-A4/2,A-B#4-C/3,/4,A,A-B-C as an example
-/datum/song/proc/compile_lines()
+/**
+ * Compiles our lines into "chords" with numbers. This makes there have to be a bit of lag at the beginning of the song, but repeats will not have to parse it again, and overall playback won't be impacted by as much lag.
+ */
+/datum/song/proc/compile_synthesized()
if(!length(src.lines))
return
var/list/lines = src.lines //cache for hyepr speed!
@@ -57,10 +37,12 @@
compiled_chord += tempodiv //this goes last
if(length(compiled_chord))
compiled_chords[++compiled_chords.len] = compiled_chord
- CHECK_TICK
- return compiled_chords
-/datum/song/proc/playkey_synth(key)
+/**
+ * Plays a specific numerical key from our instrument to anyone who can hear us.
+ * Does a hearing check if enough time has passed.
+ */
+/datum/song/proc/playkey_synth(key, mob/user)
if(can_noteshift)
key = clamp(key + note_shift, key_min, key_max)
if((world.time - MUSICIAN_HEARCHECK_MINDELAY) > last_hearcheck)
@@ -83,6 +65,9 @@
M.playsound_local(get_turf(parent), null, volume, FALSE, K.frequency, INSTRUMENT_DISTANCE_NO_FALLOFF, channel, null, copy, distance_multiplier = INSTRUMENT_DISTANCE_FALLOFF_BUFF)
// Could do environment and echo later but not for now
+/**
+ * Stops all sounds we are "responsible" for. Only works in synthesized mode.
+ */
/datum/song/proc/terminate_all_sounds(clear_channels = TRUE)
for(var/i in hearing_mobs)
terminate_sound_mob(i)
@@ -93,10 +78,16 @@
using_sound_channels = 0
SSsounds.free_datum_channels(src)
+/**
+ * Stops all sounds we are responsible for in a given person. Only works in synthesized mode.
+ */
/datum/song/proc/terminate_sound_mob(mob/M)
for(var/channel in channels_playing)
M.stop_sound_channel(text2num(channel))
+/**
+ * Pops a channel we have reserved so we don't have to release and re-request them from SSsounds every time we play a note. This is faster.
+ */
/datum/song/proc/pop_channel()
if(length(channels_idle)) //just pop one off of here if we have one available
. = text2num(channels_idle[1])
@@ -108,6 +99,12 @@
if(!isnull(.))
using_sound_channels++
+/**
+ * Decays our channels and updates their volumes to mobs who can hear us.
+ *
+ * Arguments:
+ * * wait_ds - the deciseconds we should decay by. This is to compensate for any lag, as otherwise songs would get pretty nasty during high time dilation.
+ */
/datum/song/proc/process_decay(wait_ds)
var/linear_dropoff = cached_linear_dropoff * wait_ds
var/exponential_dropoff = cached_exponential_dropoff ** wait_ds
diff --git a/code/modules/integrated_electronics/core/assemblies.dm b/code/modules/integrated_electronics/core/assemblies.dm
index 7c9f811c34..903ff13fa8 100644
--- a/code/modules/integrated_electronics/core/assemblies.dm
+++ b/code/modules/integrated_electronics/core/assemblies.dm
@@ -651,11 +651,6 @@
icon_state = "setup_small_pda"
desc = "It's a case, for building small electronics with. This one resembles a PDA."
-/obj/item/electronic_assembly/dildo
- name = "type-g electronic assembly"
- icon_state = "setup_dildo_medium"
- desc = "It's a case, for building small electronics with. This one has a phallic design."
-
/obj/item/electronic_assembly/small
name = "electronic device"
icon_state = "setup_device"
@@ -687,11 +682,6 @@
icon_state = "setup_device_box"
desc = "It's a case, for building tiny-sized electronics with. This one has a boxy design."
-/obj/item/electronic_assembly/small/dildo
- name = "type-f electronic device"
- icon_state = "setup_dildo_small"
- desc = "It's a case, for building tiny-sized electronics with. This one has a phallic design."
-
/obj/item/electronic_assembly/medium
name = "electronic mechanism"
icon_state = "setup_medium"
@@ -732,12 +722,6 @@
icon_state = "setup_medium_radio"
desc = "It's a case, for building medium-sized electronics with. This one resembles an old radio."
-/obj/item/electronic_assembly/medium/dildo
- name = "type-g electronic mechanism"
- icon_state = "setup_dildo_large"
- desc = "It's a case, for building medium-sized electronics with. This one has a phallic design."
-
-
/obj/item/electronic_assembly/large
name = "electronic machine"
icon_state = "setup_large"
diff --git a/code/modules/jobs/job_types/_job.dm b/code/modules/jobs/job_types/_job.dm
index bd36218211..0feceaf1a8 100644
--- a/code/modules/jobs/job_types/_job.dm
+++ b/code/modules/jobs/job_types/_job.dm
@@ -46,6 +46,7 @@
var/minimal_player_age = 0
var/outfit = null
+ var/plasma_outfit = null //the outfit given to plasmamen
var/exp_requirements = 0
@@ -79,6 +80,9 @@
if(mind_traits)
for(var/t in mind_traits)
ADD_TRAIT(H.mind, t, JOB_TRAIT)
+ if(/datum/quirk/paraplegic in blacklisted_quirks)
+ H.regenerate_limbs() //if you can't be a paraplegic, attempt to regenerate limbs to stop amputated limb selection
+ H.set_resting(FALSE, TRUE) //they probably shouldn't be on the floor because they had no legs then suddenly had legs
/datum/job/proc/announce(mob/living/carbon/human/H)
if(head_announce)
diff --git a/code/modules/jobs/job_types/atmospheric_technician.dm b/code/modules/jobs/job_types/atmospheric_technician.dm
index 1962b8e8a9..bff56d1d16 100644
--- a/code/modules/jobs/job_types/atmospheric_technician.dm
+++ b/code/modules/jobs/job_types/atmospheric_technician.dm
@@ -12,6 +12,7 @@
exp_type = EXP_TYPE_CREW
outfit = /datum/outfit/job/atmos
+ plasma_outfit = /datum/outfit/plasmaman/atmospherics
access = list(ACCESS_ENGINE, ACCESS_ENGINE_EQUIP, ACCESS_TECH_STORAGE, ACCESS_MAINT_TUNNELS,
ACCESS_EXTERNAL_AIRLOCKS, ACCESS_CONSTRUCTION, ACCESS_ATMOSPHERICS, ACCESS_MINERAL_STOREROOM)
diff --git a/code/modules/jobs/job_types/bartender.dm b/code/modules/jobs/job_types/bartender.dm
index e5cd015460..8290adbbd7 100644
--- a/code/modules/jobs/job_types/bartender.dm
+++ b/code/modules/jobs/job_types/bartender.dm
@@ -11,6 +11,7 @@
exp_type_department = EXP_TYPE_SERVICE // This is so the jobs menu can work properly
outfit = /datum/outfit/job/bartender
+ plasma_outfit = /datum/outfit/plasmaman/bar
access = list(ACCESS_HYDROPONICS, ACCESS_BAR, ACCESS_KITCHEN, ACCESS_MORGUE, ACCESS_WEAPONS, ACCESS_MINERAL_STOREROOM)
minimal_access = list(ACCESS_BAR, ACCESS_MINERAL_STOREROOM)
diff --git a/code/modules/jobs/job_types/botanist.dm b/code/modules/jobs/job_types/botanist.dm
index 65f3e7ca48..4c91f87791 100644
--- a/code/modules/jobs/job_types/botanist.dm
+++ b/code/modules/jobs/job_types/botanist.dm
@@ -10,6 +10,7 @@
selection_color = "#bbe291"
outfit = /datum/outfit/job/botanist
+ plasma_outfit = /datum/outfit/plasmaman/botany
access = list(ACCESS_HYDROPONICS, ACCESS_BAR, ACCESS_KITCHEN, ACCESS_MORGUE, ACCESS_MINERAL_STOREROOM)
minimal_access = list(ACCESS_HYDROPONICS, ACCESS_MORGUE, ACCESS_MINERAL_STOREROOM)
diff --git a/code/modules/jobs/job_types/captain.dm b/code/modules/jobs/job_types/captain.dm
index 3733658c33..047a07062d 100644
--- a/code/modules/jobs/job_types/captain.dm
+++ b/code/modules/jobs/job_types/captain.dm
@@ -17,6 +17,7 @@
outfit = /datum/outfit/job/captain
+ plasma_outfit = /datum/outfit/plasmaman/captain
access = list() //See get_access()
minimal_access = list() //See get_access()
diff --git a/code/modules/jobs/job_types/cargo_technician.dm b/code/modules/jobs/job_types/cargo_technician.dm
index 840af56a0e..1f87a5265d 100644
--- a/code/modules/jobs/job_types/cargo_technician.dm
+++ b/code/modules/jobs/job_types/cargo_technician.dm
@@ -10,6 +10,7 @@
selection_color = "#ca8f55"
outfit = /datum/outfit/job/cargo_tech
+ plasma_outfit = /datum/outfit/plasmaman/cargo
access = list(ACCESS_MAINT_TUNNELS, ACCESS_MAILSORTING, ACCESS_CARGO, ACCESS_CARGO_BOT, ACCESS_MINING,
ACCESS_MINING_STATION, ACCESS_MINERAL_STOREROOM)
diff --git a/code/modules/jobs/job_types/chaplain.dm b/code/modules/jobs/job_types/chaplain.dm
index 5f66519365..a9e891a303 100644
--- a/code/modules/jobs/job_types/chaplain.dm
+++ b/code/modules/jobs/job_types/chaplain.dm
@@ -10,6 +10,7 @@
selection_color = "#dddddd"
outfit = /datum/outfit/job/chaplain
+ plasma_outfit = /datum/outfit/plasmaman/chaplain
access = list(ACCESS_MORGUE, ACCESS_CHAPEL_OFFICE, ACCESS_CREMATORIUM, ACCESS_THEATRE)
minimal_access = list(ACCESS_MORGUE, ACCESS_CHAPEL_OFFICE, ACCESS_CREMATORIUM, ACCESS_THEATRE)
diff --git a/code/modules/jobs/job_types/chemist.dm b/code/modules/jobs/job_types/chemist.dm
index b2699071e3..6d4204d041 100644
--- a/code/modules/jobs/job_types/chemist.dm
+++ b/code/modules/jobs/job_types/chemist.dm
@@ -12,6 +12,7 @@
exp_requirements = 60
outfit = /datum/outfit/job/chemist
+ plasma_outfit = /datum/outfit/plasmaman/chemist
access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_SURGERY, ACCESS_CHEMISTRY, ACCESS_VIROLOGY, ACCESS_GENETICS, ACCESS_CLONING, ACCESS_MINERAL_STOREROOM)
minimal_access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_CHEMISTRY, ACCESS_MINERAL_STOREROOM)
diff --git a/code/modules/jobs/job_types/chief_engineer.dm b/code/modules/jobs/job_types/chief_engineer.dm
index d8df767a9f..18be8c9835 100644
--- a/code/modules/jobs/job_types/chief_engineer.dm
+++ b/code/modules/jobs/job_types/chief_engineer.dm
@@ -17,6 +17,7 @@
exp_type_department = EXP_TYPE_ENGINEERING
outfit = /datum/outfit/job/ce
+ plasma_outfit = /datum/outfit/plasmaman/ce
access = list(ACCESS_ENGINE, ACCESS_ENGINE_EQUIP, ACCESS_TECH_STORAGE, ACCESS_MAINT_TUNNELS,
ACCESS_EXTERNAL_AIRLOCKS, ACCESS_ATMOSPHERICS, ACCESS_EVA,
diff --git a/code/modules/jobs/job_types/chief_medical_officer.dm b/code/modules/jobs/job_types/chief_medical_officer.dm
index 9cbc15ca28..627a7a2ca1 100644
--- a/code/modules/jobs/job_types/chief_medical_officer.dm
+++ b/code/modules/jobs/job_types/chief_medical_officer.dm
@@ -17,6 +17,7 @@
exp_type_department = EXP_TYPE_MEDICAL
outfit = /datum/outfit/job/cmo
+ plasma_outfit = /datum/outfit/plasmaman/cmo
access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_GENETICS, ACCESS_CLONING, ACCESS_HEADS, ACCESS_MINERAL_STOREROOM,
ACCESS_CHEMISTRY, ACCESS_VIROLOGY, ACCESS_CMO, ACCESS_SURGERY, ACCESS_RC_ANNOUNCE,
diff --git a/code/modules/jobs/job_types/clown.dm b/code/modules/jobs/job_types/clown.dm
index 7ad7148614..dc2f60434c 100644
--- a/code/modules/jobs/job_types/clown.dm
+++ b/code/modules/jobs/job_types/clown.dm
@@ -10,6 +10,7 @@
selection_color = "#dddddd"
outfit = /datum/outfit/job/clown
+ plasma_outfit = /datum/outfit/plasmaman/clown
access = list(ACCESS_THEATRE)
minimal_access = list(ACCESS_THEATRE)
diff --git a/code/modules/jobs/job_types/cook.dm b/code/modules/jobs/job_types/cook.dm
index 5a5916cb7e..666ee8f036 100644
--- a/code/modules/jobs/job_types/cook.dm
+++ b/code/modules/jobs/job_types/cook.dm
@@ -11,6 +11,7 @@
var/cooks = 0 //Counts cooks amount
outfit = /datum/outfit/job/cook
+ plasma_outfit = /datum/outfit/plasmaman/chef
access = list(ACCESS_HYDROPONICS, ACCESS_BAR, ACCESS_KITCHEN, ACCESS_MORGUE, ACCESS_MINERAL_STOREROOM)
minimal_access = list(ACCESS_KITCHEN, ACCESS_MORGUE, ACCESS_MINERAL_STOREROOM)
diff --git a/code/modules/jobs/job_types/curator.dm b/code/modules/jobs/job_types/curator.dm
index 47bfd4914a..254fc15bd4 100644
--- a/code/modules/jobs/job_types/curator.dm
+++ b/code/modules/jobs/job_types/curator.dm
@@ -10,6 +10,7 @@
selection_color = "#dddddd"
outfit = /datum/outfit/job/curator
+ plasma_outfit = /datum/outfit/plasmaman/curator
access = list(ACCESS_LIBRARY)
minimal_access = list(ACCESS_LIBRARY, ACCESS_CONSTRUCTION, ACCESS_MINING_STATION)
diff --git a/code/modules/jobs/job_types/detective.dm b/code/modules/jobs/job_types/detective.dm
index e5afe7e1b3..65724765e1 100644
--- a/code/modules/jobs/job_types/detective.dm
+++ b/code/modules/jobs/job_types/detective.dm
@@ -14,6 +14,7 @@
exp_type = EXP_TYPE_CREW
outfit = /datum/outfit/job/detective
+ plasma_outfit = /datum/outfit/plasmaman/detective
access = list(ACCESS_SEC_DOORS, ACCESS_FORENSICS_LOCKERS, ACCESS_MORGUE, ACCESS_MAINT_TUNNELS, ACCESS_COURT, ACCESS_BRIG, ACCESS_WEAPONS, ACCESS_MINERAL_STOREROOM)
minimal_access = list(ACCESS_SEC_DOORS, ACCESS_FORENSICS_LOCKERS, ACCESS_MORGUE, ACCESS_MAINT_TUNNELS, ACCESS_COURT, ACCESS_BRIG, ACCESS_WEAPONS, ACCESS_MINERAL_STOREROOM)
@@ -23,7 +24,7 @@
mind_traits = list(TRAIT_LAW_ENFORCEMENT_METABOLISM)
display_order = JOB_DISPLAY_ORDER_DETECTIVE
- blacklisted_quirks = list(/datum/quirk/mute, /datum/quirk/nonviolent, /datum/quirk/paraplegic)
+ blacklisted_quirks = list(/datum/quirk/mute, /datum/quirk/brainproblems, /datum/quirk/nonviolent, /datum/quirk/paraplegic, /datum/quirk/monophobia)
threat = 1
/datum/outfit/job/detective
diff --git a/code/modules/jobs/job_types/geneticist.dm b/code/modules/jobs/job_types/geneticist.dm
index a40ca0fca3..5ff1bebfbf 100644
--- a/code/modules/jobs/job_types/geneticist.dm
+++ b/code/modules/jobs/job_types/geneticist.dm
@@ -12,6 +12,7 @@
exp_requirements = 60
outfit = /datum/outfit/job/geneticist
+ plasma_outfit = /datum/outfit/plasmaman/genetics
access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_CHEMISTRY, ACCESS_GENETICS, ACCESS_CLONING, ACCESS_RESEARCH, ACCESS_XENOBIOLOGY, ACCESS_ROBOTICS, ACCESS_MINERAL_STOREROOM, ACCESS_TECH_STORAGE)
minimal_access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_GENETICS, ACCESS_CLONING, ACCESS_RESEARCH, ACCESS_MINERAL_STOREROOM)
diff --git a/code/modules/jobs/job_types/head_of_personnel.dm b/code/modules/jobs/job_types/head_of_personnel.dm
index 8015c19c36..41fb4b99da 100644
--- a/code/modules/jobs/job_types/head_of_personnel.dm
+++ b/code/modules/jobs/job_types/head_of_personnel.dm
@@ -17,6 +17,7 @@
exp_type_department = EXP_TYPE_SERVICE
outfit = /datum/outfit/job/hop
+ plasma_outfit = /datum/outfit/plasmaman/hop
access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_COURT, ACCESS_WEAPONS,
ACCESS_MEDICAL, ACCESS_ENGINE, ACCESS_CHANGE_IDS, ACCESS_AI_UPLOAD, ACCESS_EVA, ACCESS_HEADS,
diff --git a/code/modules/jobs/job_types/head_of_security.dm b/code/modules/jobs/job_types/head_of_security.dm
index 69ed63a514..cfd8d7f6c0 100644
--- a/code/modules/jobs/job_types/head_of_security.dm
+++ b/code/modules/jobs/job_types/head_of_security.dm
@@ -17,6 +17,8 @@
exp_type_department = EXP_TYPE_SECURITY
outfit = /datum/outfit/job/hos
+ plasma_outfit = /datum/outfit/plasmaman/hos
+
mind_traits = list(TRAIT_LAW_ENFORCEMENT_METABOLISM)
access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_BRIG, ACCESS_ARMORY, ACCESS_COURT, ACCESS_WEAPONS, ACCESS_ENTER_GENPOP, ACCESS_LEAVE_GENPOP,
@@ -31,7 +33,7 @@
paycheck_department = ACCOUNT_SEC
display_order = JOB_DISPLAY_ORDER_HEAD_OF_SECURITY
- blacklisted_quirks = list(/datum/quirk/mute, /datum/quirk/brainproblems, /datum/quirk/nonviolent, /datum/quirk/paraplegic, /datum/quirk/insanity)
+ blacklisted_quirks = list(/datum/quirk/mute, /datum/quirk/brainproblems, /datum/quirk/nonviolent, /datum/quirk/paraplegic, /datum/quirk/blindness, /datum/quirk/monophobia, /datum/quirk/insanity)
threat = 3
/datum/outfit/job/hos
diff --git a/code/modules/jobs/job_types/janitor.dm b/code/modules/jobs/job_types/janitor.dm
index 2f6d6f0e32..c62c2e5b26 100644
--- a/code/modules/jobs/job_types/janitor.dm
+++ b/code/modules/jobs/job_types/janitor.dm
@@ -10,6 +10,7 @@
selection_color = "#bbe291"
outfit = /datum/outfit/job/janitor
+ plasma_outfit = /datum/outfit/plasmaman/janitor
access = list(ACCESS_JANITOR, ACCESS_MAINT_TUNNELS, ACCESS_MINERAL_STOREROOM)
minimal_access = list(ACCESS_JANITOR, ACCESS_MAINT_TUNNELS, ACCESS_MINERAL_STOREROOM)
diff --git a/code/modules/jobs/job_types/lawyer.dm b/code/modules/jobs/job_types/lawyer.dm
index 1a7499800b..17c376a5de 100644
--- a/code/modules/jobs/job_types/lawyer.dm
+++ b/code/modules/jobs/job_types/lawyer.dm
@@ -11,6 +11,7 @@
var/lawyers = 0 //Counts lawyer amount
outfit = /datum/outfit/job/lawyer
+ plasma_outfit = /datum/outfit/plasmaman/bar //yes, this is correct, there's no 'lawyer' plasmeme outfit
access = list(ACCESS_LAWYER, ACCESS_COURT, ACCESS_SEC_DOORS)
minimal_access = list(ACCESS_LAWYER, ACCESS_COURT, ACCESS_SEC_DOORS)
diff --git a/code/modules/jobs/job_types/medical_doctor.dm b/code/modules/jobs/job_types/medical_doctor.dm
index 5ec4b83b2f..d6a763acfe 100644
--- a/code/modules/jobs/job_types/medical_doctor.dm
+++ b/code/modules/jobs/job_types/medical_doctor.dm
@@ -10,6 +10,7 @@
selection_color = "#74b5e0"
outfit = /datum/outfit/job/doctor
+ plasma_outfit = /datum/outfit/plasmaman/medical
access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_SURGERY, ACCESS_CHEMISTRY, ACCESS_GENETICS, ACCESS_CLONING, ACCESS_VIROLOGY, ACCESS_MINERAL_STOREROOM)
minimal_access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_SURGERY, ACCESS_CLONING, ACCESS_MINERAL_STOREROOM)
diff --git a/code/modules/jobs/job_types/mime.dm b/code/modules/jobs/job_types/mime.dm
index 4ba2489ab2..e00b3a1e29 100644
--- a/code/modules/jobs/job_types/mime.dm
+++ b/code/modules/jobs/job_types/mime.dm
@@ -10,6 +10,7 @@
selection_color = "#dddddd"
outfit = /datum/outfit/job/mime
+ plasma_outfit = /datum/outfit/plasmaman/mime
access = list(ACCESS_THEATRE)
minimal_access = list(ACCESS_THEATRE)
diff --git a/code/modules/jobs/job_types/research_director.dm b/code/modules/jobs/job_types/research_director.dm
index 7128fbe2c7..33f7df8260 100644
--- a/code/modules/jobs/job_types/research_director.dm
+++ b/code/modules/jobs/job_types/research_director.dm
@@ -17,6 +17,7 @@
exp_type = EXP_TYPE_CREW
outfit = /datum/outfit/job/rd
+ plasma_outfit = /datum/outfit/plasmaman/rd
access = list(ACCESS_RD, ACCESS_HEADS, ACCESS_TOX, ACCESS_GENETICS, ACCESS_MORGUE,
ACCESS_TOX_STORAGE, ACCESS_TELEPORTER, ACCESS_SEC_DOORS,
diff --git a/code/modules/jobs/job_types/roboticist.dm b/code/modules/jobs/job_types/roboticist.dm
index f5ae93bb6a..aa52b353df 100644
--- a/code/modules/jobs/job_types/roboticist.dm
+++ b/code/modules/jobs/job_types/roboticist.dm
@@ -12,6 +12,7 @@
exp_type = EXP_TYPE_CREW
outfit = /datum/outfit/job/roboticist
+ plasma_outfit = /datum/outfit/plasmaman/robotics
access = list(ACCESS_ROBOTICS, ACCESS_TOX, ACCESS_TOX_STORAGE, ACCESS_TECH_STORAGE, ACCESS_MORGUE, ACCESS_RESEARCH, ACCESS_MINERAL_STOREROOM, ACCESS_XENOBIOLOGY, ACCESS_GENETICS)
minimal_access = list(ACCESS_ROBOTICS, ACCESS_TECH_STORAGE, ACCESS_MORGUE, ACCESS_RESEARCH, ACCESS_MINERAL_STOREROOM)
diff --git a/code/modules/jobs/job_types/scientist.dm b/code/modules/jobs/job_types/scientist.dm
index 476f740b9d..a851b333fe 100644
--- a/code/modules/jobs/job_types/scientist.dm
+++ b/code/modules/jobs/job_types/scientist.dm
@@ -12,6 +12,7 @@
exp_type = EXP_TYPE_CREW
outfit = /datum/outfit/job/scientist
+ plasma_outfit = /datum/outfit/plasmaman/science
access = list(ACCESS_ROBOTICS, ACCESS_TOX, ACCESS_TOX_STORAGE, ACCESS_RESEARCH, ACCESS_XENOBIOLOGY, ACCESS_MINERAL_STOREROOM, ACCESS_TECH_STORAGE, ACCESS_GENETICS)
minimal_access = list(ACCESS_TOX, ACCESS_TOX_STORAGE, ACCESS_RESEARCH, ACCESS_XENOBIOLOGY, ACCESS_MINERAL_STOREROOM)
diff --git a/code/modules/jobs/job_types/security_officer.dm b/code/modules/jobs/job_types/security_officer.dm
index bc6f6a94c7..bc83eb752d 100644
--- a/code/modules/jobs/job_types/security_officer.dm
+++ b/code/modules/jobs/job_types/security_officer.dm
@@ -14,6 +14,7 @@
exp_type = EXP_TYPE_CREW
outfit = /datum/outfit/job/security
+ plasma_outfit = /datum/outfit/plasmaman/security
access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_BRIG, ACCESS_COURT, ACCESS_MAINT_TUNNELS, ACCESS_MORGUE, ACCESS_WEAPONS, ACCESS_ENTER_GENPOP, ACCESS_LEAVE_GENPOP, ACCESS_FORENSICS_LOCKERS, ACCESS_MINERAL_STOREROOM)
minimal_access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_BRIG, ACCESS_COURT, ACCESS_WEAPONS, ACCESS_ENTER_GENPOP, ACCESS_LEAVE_GENPOP, ACCESS_MINERAL_STOREROOM) // See /datum/job/officer/get_access()
@@ -23,7 +24,7 @@
mind_traits = list(TRAIT_LAW_ENFORCEMENT_METABOLISM)
display_order = JOB_DISPLAY_ORDER_SECURITY_OFFICER
- blacklisted_quirks = list(/datum/quirk/mute, /datum/quirk/nonviolent, /datum/quirk/paraplegic)
+ blacklisted_quirks = list(/datum/quirk/mute, /datum/quirk/brainproblems, /datum/quirk/nonviolent, /datum/quirk/paraplegic, /datum/quirk/blindness, /datum/quirk/monophobia)
threat = 2
/datum/job/officer/get_access()
diff --git a/code/modules/jobs/job_types/shaft_miner.dm b/code/modules/jobs/job_types/shaft_miner.dm
index ebf7ba0f1f..04d3fb53b8 100644
--- a/code/modules/jobs/job_types/shaft_miner.dm
+++ b/code/modules/jobs/job_types/shaft_miner.dm
@@ -12,6 +12,7 @@
outfit = /datum/outfit/job/miner
+ plasma_outfit = /datum/outfit/plasmaman/mining
access = list(ACCESS_MAINT_TUNNELS, ACCESS_MAILSORTING, ACCESS_CARGO, ACCESS_CARGO_BOT, ACCESS_MINING,
ACCESS_MINING_STATION, ACCESS_MINERAL_STOREROOM)
diff --git a/code/modules/jobs/job_types/station_engineer.dm b/code/modules/jobs/job_types/station_engineer.dm
index d3f5db7dbb..2396728ad8 100644
--- a/code/modules/jobs/job_types/station_engineer.dm
+++ b/code/modules/jobs/job_types/station_engineer.dm
@@ -12,6 +12,7 @@
exp_type = EXP_TYPE_CREW
outfit = /datum/outfit/job/engineer
+ plasma_outfit = /datum/outfit/plasmaman/engineering
access = list(ACCESS_ENGINE, ACCESS_ENGINE_EQUIP, ACCESS_TECH_STORAGE, ACCESS_MAINT_TUNNELS,
ACCESS_EXTERNAL_AIRLOCKS, ACCESS_CONSTRUCTION, ACCESS_ATMOSPHERICS, ACCESS_TCOMSAT, ACCESS_MINERAL_STOREROOM)
diff --git a/code/modules/jobs/job_types/virologist.dm b/code/modules/jobs/job_types/virologist.dm
index 790e828931..3e9b3ba06c 100644
--- a/code/modules/jobs/job_types/virologist.dm
+++ b/code/modules/jobs/job_types/virologist.dm
@@ -12,6 +12,7 @@
exp_requirements = 60
outfit = /datum/outfit/job/virologist
+ plasma_outfit = /datum/outfit/plasmaman/viro
access = list(ACCESS_MEDICAL, ACCESS_MORGUE, ACCESS_SURGERY, ACCESS_CHEMISTRY, ACCESS_VIROLOGY, ACCESS_GENETICS, ACCESS_CLONING, ACCESS_MINERAL_STOREROOM)
minimal_access = list(ACCESS_MEDICAL, ACCESS_VIROLOGY, ACCESS_MINERAL_STOREROOM)
diff --git a/code/modules/jobs/job_types/warden.dm b/code/modules/jobs/job_types/warden.dm
index 5762731f62..c909342d6f 100644
--- a/code/modules/jobs/job_types/warden.dm
+++ b/code/modules/jobs/job_types/warden.dm
@@ -14,6 +14,7 @@
exp_type = EXP_TYPE_CREW
outfit = /datum/outfit/job/warden
+ plasma_outfit = /datum/outfit/plasmaman/warden
access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_BRIG, ACCESS_ARMORY, ACCESS_COURT, ACCESS_MAINT_TUNNELS, ACCESS_MORGUE, ACCESS_WEAPONS, ACCESS_ENTER_GENPOP, ACCESS_LEAVE_GENPOP, ACCESS_FORENSICS_LOCKERS, ACCESS_MINERAL_STOREROOM)
minimal_access = list(ACCESS_SECURITY, ACCESS_SEC_DOORS, ACCESS_BRIG, ACCESS_ARMORY, ACCESS_COURT, ACCESS_WEAPONS, ACCESS_ENTER_GENPOP, ACCESS_LEAVE_GENPOP, ACCESS_MINERAL_STOREROOM) // See /datum/job/warden/get_access()
@@ -24,7 +25,7 @@
mind_traits = list(TRAIT_LAW_ENFORCEMENT_METABOLISM)
display_order = JOB_DISPLAY_ORDER_WARDEN
- blacklisted_quirks = list(/datum/quirk/mute, /datum/quirk/nonviolent, /datum/quirk/paraplegic)
+ blacklisted_quirks = list(/datum/quirk/mute, /datum/quirk/brainproblems, /datum/quirk/nonviolent, /datum/quirk/paraplegic, /datum/quirk/blindness, /datum/quirk/monophobia)
threat = 2
/datum/job/warden/get_access()
diff --git a/code/modules/language/language.dm b/code/modules/language/language.dm
index 536bf06e8d..42b439ba03 100644
--- a/code/modules/language/language.dm
+++ b/code/modules/language/language.dm
@@ -35,7 +35,7 @@
return TRUE
/datum/language/proc/get_icon()
- var/datum/asset/spritesheet/sheet = get_asset_datum(/datum/asset/spritesheet/goonchat)
+ var/datum/asset/spritesheet/sheet = get_asset_datum(/datum/asset/spritesheet/chat)
return sheet.icon_tag("language-[icon_state]")
/datum/language/proc/get_random_name(gender, name_count=2, syllable_count=4, syllable_divisor=2)
diff --git a/code/modules/language/language_holder.dm b/code/modules/language/language_holder.dm
index 6e3d27f2b8..c1677117e9 100644
--- a/code/modules/language/language_holder.dm
+++ b/code/modules/language/language_holder.dm
@@ -276,7 +276,8 @@ Key procs
/datum/language/draconic = list(LANGUAGE_ATOM))
/datum/language_holder/lizard/ash
- selected_language = /datum/language/draconic
+ understood_languages = list(/datum/language/draconic = list(LANGUAGE_ATOM))
+ spoken_languages = list(/datum/language/draconic = list(LANGUAGE_ATOM))
/datum/language_holder/monkey
understood_languages = list(/datum/language/common = list(LANGUAGE_ATOM),
@@ -323,6 +324,12 @@ Key procs
/datum/language/sylvan = list(LANGUAGE_ATOM))
spoken_languages = list(/datum/language/sylvan = list(LANGUAGE_ATOM))
+/datum/language_holder/ethereal
+ understood_languages = list(/datum/language/common = list(LANGUAGE_ATOM),
+ /datum/language/voltaic = list(LANGUAGE_ATOM))
+ spoken_languages = list(/datum/language/common = list(LANGUAGE_ATOM),
+ /datum/language/voltaic = list(LANGUAGE_ATOM))
+
/datum/language_holder/empty
understood_languages = list()
spoken_languages = list()
diff --git a/code/modules/language/language_menu.dm b/code/modules/language/language_menu.dm
index 0df7c01fca..bffd3d59af 100644
--- a/code/modules/language/language_menu.dm
+++ b/code/modules/language/language_menu.dm
@@ -8,10 +8,13 @@
language_holder = null
. = ..()
-/datum/language_menu/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.language_menu_state)
- ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+/datum/language_menu/ui_state(mob/user)
+ return GLOB.language_menu_state
+
+/datum/language_menu/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
- ui = new(user, src, ui_key, "LanguageMenu", "Language Menu", 700, 600, master_ui, state)
+ ui = new(user, src, "LanguageMenu")
ui.open()
/datum/language_menu/ui_data(mob/user)
diff --git a/code/modules/language/voltaic.dm b/code/modules/language/voltaic.dm
new file mode 100644
index 0000000000..ead7fe7c7f
--- /dev/null
+++ b/code/modules/language/voltaic.dm
@@ -0,0 +1,14 @@
+// One of these languages will actually work, I'm certain of it.
+/datum/language/voltaic
+ name = "Voltaic"
+ desc = "A sparky language made by manipulating electrical discharge."
+ key = "v"
+ space_chance = 20
+ syllables = list(
+ "bzzt", "skrrt", "zzp", "mmm", "hzz", "tk", "shz", "k", "z",
+ "bzt", "zzt", "skzt", "skzz", "hmmt", "zrrt", "hzzt", "hz",
+ "vzt", "zt", "vz", "zip", "tzp", "lzzt", "dzzt", "zdt", "kzt",
+ "zzzz", "mzz"
+ )
+ icon_state = "volt"
+ default_priority = 90
diff --git a/code/modules/library/lib_codex_gigas.dm b/code/modules/library/lib_codex_gigas.dm
index 146c4221f8..26fa5b6f3d 100644
--- a/code/modules/library/lib_codex_gigas.dm
+++ b/code/modules/library/lib_codex_gigas.dm
@@ -34,13 +34,13 @@
if(U.check_acedia())
to_chat(user, "None of this matters, why are you reading this? You put [title] down.")
return
- user.visible_message("[user] opens [title] and begins reading intently.")
+ user.visible_message("[user] opens [title] and begins reading intently.")
ask_name(user)
/obj/item/book/codex_gigas/proc/perform_research(mob/user, devilName)
if(!devilName)
- user.visible_message("[user] closes [title] without looking anything up.")
+ user.visible_message("[user] closes [title] without looking anything up.")
return
inUse = TRUE
var/speed = 300
@@ -50,7 +50,7 @@
if(U.job in list("Curator")) // the curator is both faster, and more accurate than normal crew members at research
speed = 100
correctness = 100
- correctness -= U.getOrganLoss(ORGAN_SLOT_BRAIN) *0.5 //Brain damage makes researching hard.
+ correctness -= U.getOrganLoss(ORGAN_SLOT_BRAIN) * 0.5 //Brain damage makes researching hard.
speed += U.getOrganLoss(ORGAN_SLOT_BRAIN) * 3
if(do_after(user, speed, 0, user))
var/usedName = devilName
@@ -95,11 +95,10 @@
currentSection = SUFFIX
return currentSection != oldSection
-/obj/item/book/codex_gigas/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \
- datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state)
- ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+/obj/item/book/codex_gigas/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
- ui = new(user, src, ui_key, "CodexGigas", name, 450, 450, master_ui, state)
+ ui = new(user, src, "CodexGigas", name)
ui.open()
/obj/item/book/codex_gigas/ui_data(mob/user)
diff --git a/code/modules/library/lib_items.dm b/code/modules/library/lib_items.dm
index 80ce2522ff..a4d88158e2 100644
--- a/code/modules/library/lib_items.dm
+++ b/code/modules/library/lib_items.dm
@@ -22,7 +22,7 @@
max_integrity = 200
armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 0)
var/state = 0
- var/list/allowed_books = list(/obj/item/book, /obj/item/spellbook, /obj/item/storage/book) //Things allowed in the bookcase
+ var/list/allowed_books = list(/obj/item/book, /obj/item/spellbook, /obj/item/storage/book, /obj/item/gun/magic/wand/book) //Things allowed in the bookcase
/obj/structure/bookcase/examine(mob/user)
. = ..()
@@ -192,7 +192,7 @@
desc = "Crack it open, inhale the musk of its pages, and learn something new."
throw_speed = 1
throw_range = 5
- w_class = WEIGHT_CLASS_NORMAL //upped to three because books are, y'know, pretty big. (and you could hide them inside eachother recursively forever)
+ w_class = WEIGHT_CLASS_NORMAL //upped to three because books are, y'know, pretty big. (and you could hide them inside eachother recursively forever)
attack_verb = list("bashed", "whacked", "educated")
resistance_flags = FLAMMABLE
var/dat //Actual page content
diff --git a/code/modules/library/lib_machines.dm b/code/modules/library/lib_machines.dm
index f777246453..3953f5e28c 100644
--- a/code/modules/library/lib_machines.dm
+++ b/code/modules/library/lib_machines.dm
@@ -66,7 +66,6 @@
dat += "\[Go Back\] "
var/datum/browser/popup = new(user, "publiclibrary", name, 600, 400)
popup.set_content(dat)
- popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state))
popup.open()
/obj/machinery/computer/libraryconsole/Topic(href, href_list)
@@ -314,7 +313,6 @@ GLOBAL_LIST(cachedbooks) // List of our cached book datums
var/datum/browser/popup = new(user, "library", name, 600, 400)
popup.set_content(dat)
- popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state))
popup.open()
/obj/machinery/computer/libraryconsole/bookmanagement/proc/findscanner(viewrange)
@@ -537,7 +535,6 @@ GLOBAL_LIST(cachedbooks) // List of our cached book datums
dat += " "
var/datum/browser/popup = new(user, "scanner", name, 600, 400)
popup.set_content(dat)
- popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state))
popup.open()
/obj/machinery/libraryscanner/Topic(href, href_list)
diff --git a/code/modules/library/soapstone.dm b/code/modules/library/soapstone.dm
index 50f984c44d..f17040a938 100644
--- a/code/modules/library/soapstone.dm
+++ b/code/modules/library/soapstone.dm
@@ -35,13 +35,13 @@
return
if(existing_message)
- user.visible_message("[user] starts erasing [existing_message].", "You start erasing [existing_message].", "You hear a chipping sound.")
- playsound(loc, 'sound/items/gavel.ogg', 50, 1, -1)
+ user.visible_message("[user] starts erasing [existing_message].", "You start erasing [existing_message].", "You hear a chipping sound.")
+ playsound(loc, 'sound/items/gavel.ogg', 50, TRUE, -1)
if(do_after(user, tool_speed, target = existing_message))
user.visible_message("[user] erases [existing_message].", "You erase [existing_message][existing_message.creator_key == user.ckey ? ", refunding a use" : ""].")
existing_message.persists = FALSE
qdel(existing_message)
- playsound(loc, 'sound/items/gavel.ogg', 50, 1, -1)
+ playsound(loc, 'sound/items/gavel.ogg', 50, TRUE, -1)
if(existing_message.creator_key == user.ckey)
refund_use()
return
@@ -54,12 +54,12 @@
if(!target.Adjacent(user) && locate(/obj/structure/chisel_message) in T)
to_chat(user, "Someone wrote here before you chose! Find another spot.")
return
- playsound(loc, 'sound/items/gavel.ogg', 50, 1, -1)
- user.visible_message("[user] starts engraving a message into [T]...", "You start engraving a message into [T]...", "You hear a chipping sound.")
+ playsound(loc, 'sound/items/gavel.ogg', 50, TRUE, -1)
+ user.visible_message("[user] starts engraving a message into [T]...", "You start engraving a message into [T]...", "You hear a chipping sound.")
if(can_use() && do_after(user, tool_speed, target = T) && can_use()) //This looks messy but it's actually really clever!
if(!locate(/obj/structure/chisel_message) in T)
- user.visible_message("[user] leaves a message for future spacemen!", "You engrave a message into [T]!", "You hear a chipping sound.")
- playsound(loc, 'sound/items/gavel.ogg', 50, 1, -1)
+ user.visible_message("[user] leaves a message for future spacemen!", "You engrave a message into [T]!", "You hear a chipping sound.")
+ playsound(loc, 'sound/items/gavel.ogg', 50, TRUE, -1)
var/obj/structure/chisel_message/M = new(T)
M.register(user, message)
remove_use()
@@ -112,12 +112,10 @@
desc = "A message from a past traveler."
icon = 'icons/obj/stationobjs.dmi'
icon_state = "soapstone_message"
- layer = HIGH_OBJ_LAYER
+ layer = LATTICE_LAYER
density = FALSE
anchored = TRUE
max_integrity = 30
- layer = LATTICE_LAYER
- light_power = 0.3
var/hidden_message
var/creator_key
@@ -206,10 +204,13 @@
/obj/structure/chisel_message/interact()
return
-/obj/structure/chisel_message/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.always_state)
- ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+/obj/structure/chisel_message/ui_state(mob/user)
+ return GLOB.always_state
+
+/obj/structure/chisel_message/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
- ui = new(user, src, ui_key, "EngravedMessage", name, 600, 300, master_ui, state)
+ ui = new(user, src, "EngravedMessage", name)
ui.open()
/obj/structure/chisel_message/ui_data(mob/user)
diff --git a/code/modules/mafia/_defines.dm b/code/modules/mafia/_defines.dm
new file mode 100644
index 0000000000..194851beed
--- /dev/null
+++ b/code/modules/mafia/_defines.dm
@@ -0,0 +1,65 @@
+///how many people can play mafia without issues (running out of spawns, procs not expecting more than this amount of people, etc)
+#define MAFIA_MAX_PLAYER_COUNT 12
+
+#define MAFIA_TEAM_TOWN "town"
+#define MAFIA_TEAM_MAFIA "mafia"
+#define MAFIA_TEAM_SOLO "solo"
+
+//types of town roles for random setup gen
+/// assistants it's just assistants filling up the rest of the roles
+#define TOWN_OVERFLOW "overflow"
+/// roles that learn info about others in the game (chaplain, detective, psych)
+#define TOWN_INVEST "invest"
+/// roles that keep other roles safe (doctor, and weirdly enough lawyer counts)
+#define TOWN_PROTECT "protect"
+/// roles that don't fit into anything else (hop)
+#define TOWN_MISC "misc"
+
+//other types (mafia team, neutrals)
+/// normal vote kill changelings
+#define MAFIA_REGULAR "regular"
+/// every other changeling role that has extra abilities
+#define MAFIA_SPECIAL "special"
+/// role that wins solo that nobody likes
+#define NEUTRAL_KILL "kill"
+/// role that upsets the game aka obsessed, usually worse for town than mafia but they can vote against mafia
+#define NEUTRAL_DISRUPT "disrupt"
+
+#define MAFIA_PHASE_SETUP 1
+#define MAFIA_PHASE_DAY 2
+#define MAFIA_PHASE_VOTING 3
+#define MAFIA_PHASE_JUDGEMENT 4
+#define MAFIA_PHASE_NIGHT 5
+#define MAFIA_PHASE_VICTORY_LAP 6
+
+#define MAFIA_ALIVE 1
+#define MAFIA_DEAD 2
+
+#define COMSIG_MAFIA_ON_KILL "mafia_onkill"
+#define MAFIA_PREVENT_KILL 1
+
+#define COMSIG_MAFIA_CAN_PERFORM_ACTION "mafia_can_perform_action"
+#define MAFIA_PREVENT_ACTION 1
+
+//in order of events + game end
+
+/// when the shutters fall, before the 45 second wait and night event resolution
+#define COMSIG_MAFIA_SUNDOWN "sundown"
+/// after the 45 second wait, for actions that must go first
+#define COMSIG_MAFIA_NIGHT_START "night_start"
+/// most night actions now resolve
+#define COMSIG_MAFIA_NIGHT_ACTION_PHASE "night_actions"
+/// now killing happens from the roles that do that. the reason this is post action phase is to ensure doctors can protect and lawyers can block
+#define COMSIG_MAFIA_NIGHT_KILL_PHASE "night_kill"
+/// now undoing states like protection, actions that must happen last, etc. right before shutters raise and the day begins
+#define COMSIG_MAFIA_NIGHT_END "night_end"
+
+/// signal sent to roles when the game is confirmed ending
+#define COMSIG_MAFIA_GAME_END "game_end"
+
+/// list of ghosts who want to play mafia, every time someone enters the list it checks to see if enough are in
+GLOBAL_LIST_EMPTY(mafia_signup)
+/// list of ghosts who want to play mafia that have since disconnected. They are kept in the lobby, but not counted for starting a game.
+GLOBAL_LIST_EMPTY(mafia_bad_signup)
+/// the current global mafia game running.
+GLOBAL_VAR(mafia_game)
diff --git a/code/modules/mafia/controller.dm b/code/modules/mafia/controller.dm
new file mode 100644
index 0000000000..cd8c382f30
--- /dev/null
+++ b/code/modules/mafia/controller.dm
@@ -0,0 +1,971 @@
+
+
+/**
+ * The mafia controller handles the mafia minigame in progress.
+ * It is first created when the first ghost signs up to play.
+ */
+/datum/mafia_controller
+ ///list of observers that should get game updates.
+ var/list/spectators = list()
+ ///all roles in the game, dead or alive. check their game status if you only want living or dead.
+ var/list/all_roles = list()
+ ///exists to speed up role retrieval, it's a dict. player_role_lookup[player ckey] will give you the role they play
+ var/list/player_role_lookup = list()
+ ///what part of the game you're playing in. day phases, night phases, judgement phases, etc.
+ var/phase = MAFIA_PHASE_SETUP
+ ///how long the game has gone on for, changes with every sunrise. day one, night one, day two, etc.
+ var/turn = 0
+ ///for debugging and testing a full game, or adminbuse. If this is not null, it will use this as a setup. clears when game is over
+ var/list/custom_setup = list()
+ ///first day has no voting, and thus is shorter
+ var/first_day_phase_period = 20 SECONDS
+ ///talk with others about the last night
+ var/day_phase_period = 1 MINUTES
+ ///vote someone to get put on trial
+ var/voting_phase_period = 30 SECONDS
+ ///defend yourself! don't get lynched! sometimes skipped if nobody votes.
+ var/judgement_phase_period = 30 SECONDS
+ ///guilty or innocent, we want a bit of time for players to process the outcome of the vote
+ var/judgement_lynch_period = 5 SECONDS
+ ///mafia talk at night and pick someone to kill, some town roles use their actions, etc etc.
+ var/night_phase_period = 45 SECONDS
+ ///like the lynch period, players need to see what the other players in the game's roles were
+ var/victory_lap_period = 20 SECONDS
+
+ ///template picked when the game starts. used for the name and desc reading
+ var/datum/map_template/mafia/current_map
+ ///map generation tool that deletes the current map after the game finishes
+ var/datum/mapGenerator/massdelete/map_deleter
+
+ ///Readable list of roles in current game, sent to the tgui panel for roles list > list("Psychologist x1", "Clown x2")
+ var/list/current_setup_text
+
+ ///starting outfit for all mafia players. it's just a grey jumpsuit.
+ var/player_outfit = /datum/outfit/mafia
+
+ ///spawn points for players, each one has a house
+ var/list/landmarks = list()
+ ///town center for when people get put on trial
+ var/town_center_landmark
+
+ ///group voting on one person, like putting people to trial or choosing who to kill as mafia
+ var/list/votes = list()
+ ///and these (judgement_innocent_votes, judgement_abstain_votes and judgement_guilty_votes) are the judgement phase votes, aka people sorting themselves into guilty and innocent, and "eh, i don't really care" lists. whichever has more inno or guilty wins!
+ var/list/judgement_abstain_votes = list()
+ var/list/judgement_innocent_votes = list()
+ var/list/judgement_guilty_votes = list()
+ ///current role on trial for the judgement phase, will die if guilty is greater than innocent
+ var/datum/mafia_role/on_trial
+
+ ///current timer for phase
+ var/next_phase_timer
+
+ ///used for debugging in testing (doesn't put people out of the game, some other shit i forgot, who knows just don't set this in live) honestly kinda deprecated
+ var/debug = FALSE
+
+ ///Max player count
+ var/max_player = MAFIA_MAX_PLAYER_COUNT
+ ///Required player count
+ var/required_player = 5
+ ///Prioritizes clients to have cool antag roles
+ var/low_pop_mode = FALSE
+
+/datum/mafia_controller/New()
+ . = ..()
+ GLOB.mafia_game = src
+ map_deleter = new
+
+/datum/mafia_controller/Destroy(force, ...)
+ . = ..()
+ GLOB.mafia_game = null
+ end_game()
+ qdel(map_deleter)
+
+/**
+ * Triggers at beginning of the game when there is a confirmed list of valid, ready players.
+ * Creates a 100% ready game that has NOT started (no players in bodies)
+ * Followed by start game
+ *
+ * Does the following:
+ * * Picks map, and loads it
+ * * Grabs landmarks if it is the first time it's loading
+ * * Sets up the role list
+ * * Puts players in each role randomly
+ * Arguments:
+ * * setup_list: list of all the datum setups (fancy list of roles) that would work for the game
+ * * ready_players: list of filtered, sane players (so not playing or disconnected) for the game to put into roles
+ */
+/datum/mafia_controller/proc/prepare_game(setup_list, ready_players)
+
+ var/list/possible_maps = subtypesof(/datum/map_template/mafia)
+ var/turf/spawn_area = get_turf(locate(/obj/effect/landmark/mafia_game_area) in GLOB.landmarks_list)
+
+ current_map = pick(possible_maps)
+ current_map = new current_map
+
+ if(!spawn_area)
+ CRASH("No spawn area detected for Mafia!")
+ var/list/bounds = current_map.load(spawn_area)
+ if(!bounds)
+ CRASH("Loading mafia map failed!")
+ map_deleter.defineRegion(spawn_area, locate(spawn_area.x + 23,spawn_area.y + 23,spawn_area.z), replace = TRUE) //so we're ready to mass delete when round ends
+
+ if(!landmarks.len)//we grab town center when we grab landmarks, if there is none (the first game signed up for let's grab them post load)
+ for(var/obj/effect/landmark/mafia/possible_spawn in GLOB.landmarks_list)
+ if(istype(possible_spawn, /obj/effect/landmark/mafia/town_center))
+ town_center_landmark = possible_spawn
+ else
+ landmarks += possible_spawn
+
+ current_setup_text = list()
+
+ var/list/boring_roles = list()
+ var/list/not_boring_roles = list()
+
+ for(var/rtype in setup_list)
+ for(var/i in 1 to setup_list[rtype])
+ var/datum/mafia_role/role = new rtype(src)
+ all_roles += role
+ if(role.role_type == TOWN_PROTECT || role.role_type == TOWN_INVEST || role.role_type == MAFIA_SPECIAL || role.role_type == MAFIA_REGULAR)
+ not_boring_roles += role
+ else
+ boring_roles += role
+ var/datum/mafia_role/rp = rtype
+ current_setup_text += "[initial(rp.name)] x[setup_list[rtype]]"
+
+ var/list/spawnpoints = landmarks.Copy()
+
+ if(length(ready_players) < 7 || low_pop_mode)
+ //do normal assign
+ for(var/datum/mafia_role/role in not_boring_roles)
+ role.assigned_landmark = pick_n_take(spawnpoints)
+ role.player_key = pick_n_take(ready_players)
+ //shame!
+ for(var/datum/mafia_role/role in boring_roles)
+ role.assigned_landmark = pick_n_take(spawnpoints)
+ role.player_key = pick_n_take(ready_players)
+
+ else //go run the normal one
+ for(var/datum/mafia_role/role in all_roles)
+ role.assigned_landmark = pick_n_take(spawnpoints)
+ if(!debug)
+ role.player_key = pick_n_take(ready_players)
+ else
+ role.player_key = pop(ready_players)
+
+/datum/mafia_controller/proc/send_message(msg,team)
+ for(var/datum/mafia_role/R in all_roles)
+ if(team && R.team != team)
+ continue
+ to_chat(R.body,msg)
+ var/team_suffix = team ? "([uppertext(team)] CHAT)" : ""
+ for(var/M in GLOB.dead_mob_list)
+ var/mob/spectator = M
+ if(spectator.ckey in spectators) //was in current game, or spectatin' (won't send to living)
+ var/link = FOLLOW_LINK(M, town_center_landmark)
+ to_chat(M, "[link] MAFIA: [msg] [team_suffix]")
+
+/**
+ * The game by this point is now all set up, and so we can put people in their bodies and start the first phase.
+ *
+ * Does the following:
+ * * Creates bodies for all of the roles with the first proc
+ * * Starts the first day manually (so no timer) with the second proc
+ */
+/datum/mafia_controller/proc/start_game()
+ create_bodies()
+ start_day()
+
+/**
+ * How every day starts.
+ *
+ * What players do in this phase:
+ * * If day one, just a small starting period to see who is in the game and check role, leading to the night phase.
+ * * Otherwise, it's a longer period used to discuss events that happened during the night, leading to the voting phase.
+ */
+/datum/mafia_controller/proc/start_day()
+ turn += 1
+ phase = MAFIA_PHASE_DAY
+ if(!check_victory())
+ if(turn == 1)
+ send_message("The selected map is [current_map.name]![current_map.description]")
+ send_message("Day [turn] started! There is no voting on the first day. Say hello to everybody!")
+ next_phase_timer = addtimer(CALLBACK(src,.proc/check_trial, FALSE),first_day_phase_period,TIMER_STOPPABLE) //no voting period = no votes = instant night
+ else
+ send_message("Day [turn] started! Voting will start in 1 minute.")
+ next_phase_timer = addtimer(CALLBACK(src,.proc/start_voting_phase),day_phase_period,TIMER_STOPPABLE)
+
+ SStgui.update_uis(src)
+
+/**
+ * Players have finished the discussion period, and now must put up someone to the chopping block.
+ *
+ * What players do in this phase:
+ * * Vote on which player to put up for lynching, leading to the judgement phase.
+ * * If no votes are case, the judgement phase is skipped, leading to the night phase.
+ */
+/datum/mafia_controller/proc/start_voting_phase()
+ phase = MAFIA_PHASE_VOTING
+ next_phase_timer = addtimer(CALLBACK(src, .proc/check_trial, TRUE),voting_phase_period,TIMER_STOPPABLE) //be verbose!
+ send_message("Voting started! Vote for who you want to see on trial today.")
+ SStgui.update_uis(src)
+
+/**
+ * Players have voted someone up, and now the person must defend themselves while the town votes innocent or guilty.
+ *
+ * What players do in this phase:
+ * * Vote innocent or guilty, if they are not on trial.
+ * * Defend themselves and wait for judgement, if they are.
+ * * Leads to the lynch phase.
+ * Arguments:
+ * * verbose: boolean, announces whether there were votes or not. after judgement it goes back here with no voting period to end the day.
+ */
+/datum/mafia_controller/proc/check_trial(verbose = TRUE)
+ var/datum/mafia_role/loser = get_vote_winner("Day")//, majority_of_town = TRUE)
+ // var/loser_votes = get_vote_count(loser,"Day")
+ if(loser)
+ // if(loser_votes > 12)
+ // loser.body.client?.give_award(/datum/award/achievement/mafia/universally_hated, loser.body)
+ send_message("[loser.body.real_name] wins the day vote, Listen to their defense and vote \"INNOCENT\" or \"GUILTY\"!")
+ //refresh the lists
+ judgement_abstain_votes = list()
+ judgement_innocent_votes = list()
+ judgement_guilty_votes = list()
+ for(var/i in all_roles)
+ var/datum/mafia_role/abstainee = i
+ if(abstainee.game_status == MAFIA_ALIVE && abstainee != loser)
+ judgement_abstain_votes += abstainee
+ on_trial = loser
+ on_trial.body.forceMove(get_turf(town_center_landmark))
+ phase = MAFIA_PHASE_JUDGEMENT
+ next_phase_timer = addtimer(CALLBACK(src, .proc/lynch),judgement_phase_period,TIMER_STOPPABLE)
+ reset_votes("Day")
+ else
+ if(verbose)
+ send_message("Not enough people have voted to put someone on trial, nobody will be lynched today.")
+ if(!check_victory())
+ lockdown()
+ SStgui.update_uis(src)
+
+/**
+ * Players have voted innocent or guilty on the person on trial, and that person is now killed or returned home.
+ *
+ * What players do in this phase:
+ * * r/watchpeopledie
+ * * If the accused is killed, their true role is revealed to the rest of the players.
+ */
+/datum/mafia_controller/proc/lynch()
+ for(var/i in judgement_innocent_votes)
+ var/datum/mafia_role/role = i
+ send_message("[role.body.real_name] voted innocent.")
+ for(var/ii in judgement_abstain_votes)
+ var/datum/mafia_role/role = ii
+ send_message("[role.body.real_name] abstained.")
+ for(var/iii in judgement_guilty_votes)
+ var/datum/mafia_role/role = iii
+ send_message("[role.body.real_name] voted guilty.")
+ if(judgement_guilty_votes.len > judgement_innocent_votes.len) //strictly need majority guilty to lynch
+ send_message("Guilty wins majority, [on_trial.body.real_name] has been lynched.")
+ on_trial.kill(src, lynch = TRUE)
+ addtimer(CALLBACK(src, .proc/send_home, on_trial),judgement_lynch_period)
+ else
+ send_message("Innocent wins majority, [on_trial.body.real_name] has been spared.")
+ on_trial.body.forceMove(get_turf(on_trial.assigned_landmark))
+ on_trial = null
+ //day votes are already cleared, so this will skip the trial and check victory/lockdown/whatever else
+ next_phase_timer = addtimer(CALLBACK(src, .proc/check_trial, FALSE),judgement_lynch_period,TIMER_STOPPABLE)// small pause to see the guy dead, no verbosity since we already did this
+
+/**
+ * Teenie helper proc to move players back to their home.
+ * Used in the above, but also used in the debug button "send all players home"
+ * Arguments:
+ * * role: mafia role that is getting sent back to the game.
+ */
+/datum/mafia_controller/proc/send_home(datum/mafia_role/role)
+ role.body.forceMove(get_turf(role.assigned_landmark))
+
+/**
+ * Checks to see if a faction (or solo antagonist) has won.
+ *
+ * Calculates in this order:
+ * * counts up town, mafia, and solo
+ * * solos can count as town members for the purposes of mafia winning
+ * * sends the amount of living people to the solo antagonists, and see if they won OR block the victory of the teams
+ * * checks if solos won from above, then if town, then if mafia
+ * * starts the end of the game if a faction won
+ * * returns TRUE if someone won the game, halting other procs from continuing in the case of a victory
+ */
+/datum/mafia_controller/proc/check_victory()
+ //needed for achievements
+ var/list/total_town = list()
+ var/list/total_mafia = list()
+
+ var/alive_town = 0
+ var/alive_mafia = 0
+ var/list/solos_to_ask = list() //need to ask after because first round is counting team sizes
+ var/list/total_victors = list() //if this list gets filled with anyone, they win. list because side antags can with with people
+ var/blocked_victory = FALSE //if a solo antagonist is stopping the town or mafia from finishing the game.
+
+ ///PHASE ONE: TALLY UP ALL NUMBERS OF PEOPLE STILL ALIVE
+
+ for(var/datum/mafia_role/R in all_roles)
+ switch(R.team)
+ if(MAFIA_TEAM_MAFIA)
+ total_mafia += R
+ if(R.game_status == MAFIA_ALIVE)
+ alive_mafia += R.vote_power
+ if(MAFIA_TEAM_TOWN)
+ total_town += R
+ if(R.game_status == MAFIA_ALIVE)
+ alive_town += R.vote_power
+ if(MAFIA_TEAM_SOLO)
+ if(R.game_status == MAFIA_ALIVE)
+ if(R.solo_counts_as_town)
+ alive_town += R.vote_power
+ solos_to_ask += R
+
+ ///PHASE TWO: SEND STATS TO SOLO ANTAGS, SEE IF THEY WON OR TEAMS CANNOT WIN
+
+ for(var/datum/mafia_role/solo in solos_to_ask)
+ if(solo.check_total_victory(alive_town, alive_mafia))
+ total_victors += solo
+ if(solo.block_team_victory(alive_town, alive_mafia))
+ blocked_victory = TRUE
+
+ //solo victories!
+ var/solo_end = FALSE
+ for(var/datum/mafia_role/winner in total_victors)
+ send_message("!! [uppertext(winner.name)] VICTORY !!")
+ // var/client/winner_client = GLOB.directory[winner.player_key]
+ // winner_client?.give_award(winner.winner_award, winner.body)
+ solo_end = TRUE
+ if(solo_end)
+ start_the_end()
+ return TRUE
+ if(blocked_victory)
+ return FALSE
+ if(alive_mafia == 0)
+ // for(var/datum/mafia_role/townie in total_town)
+ // var/client/townie_client = GLOB.directory[townie.player_key]
+ // townie_client?.give_award(townie.winner_award, townie.body)
+ start_the_end("!! TOWN VICTORY !!")
+ return TRUE
+ else if(alive_mafia >= alive_town) //guess could change if town nightkill is added
+ start_the_end("!! MAFIA VICTORY !!")
+ // for(var/datum/mafia_role/changeling in total_mafia)
+ // var/client/changeling_client = GLOB.directory[changeling.player_key]
+ // changeling_client?.give_award(changeling.winner_award, changeling.body)
+ return TRUE
+
+/**
+ * The end of the game is in two procs, because we want a bit of time for players to see eachothers roles.
+ * Because of how check_victory works, the game is halted in other places by this point.
+ *
+ * What players do in this phase:
+ * * See everyone's role postgame
+ * * See who won the game
+ * Arguments:
+ * * message: string, if non-null it sends it to all players. used to announce team victories while solos are handled in check victory
+ */
+/datum/mafia_controller/proc/start_the_end(message)
+ SEND_SIGNAL(src,COMSIG_MAFIA_GAME_END)
+ if(message)
+ send_message(message)
+ for(var/datum/mafia_role/R in all_roles)
+ R.reveal_role(src)
+ phase = MAFIA_PHASE_VICTORY_LAP
+ next_phase_timer = addtimer(CALLBACK(src,.proc/end_game),victory_lap_period,TIMER_STOPPABLE)
+
+/**
+ * Cleans up the game, resetting variables back to the beginning and removing the map with the generator.
+ */
+/datum/mafia_controller/proc/end_game()
+ map_deleter.generate() //remove the map, it will be loaded at the start of the next one
+ QDEL_LIST(all_roles)
+ current_setup_text = null
+ custom_setup = list()
+ turn = 0
+ votes = list()
+ //map gen does not deal with landmarks
+ QDEL_LIST(landmarks)
+ QDEL_NULL(town_center_landmark)
+ phase = MAFIA_PHASE_SETUP
+
+/**
+ * After the voting and judgement phases, the game goes to night shutting the windows and beginning night with a proc.
+ */
+/datum/mafia_controller/proc/lockdown()
+ toggle_night_curtains(close=TRUE)
+ start_night()
+
+/**
+ * Shuts poddoors attached to mafia.
+ * Arguments:
+ * * close: boolean, the state you want the curtains in.
+ */
+/datum/mafia_controller/proc/toggle_night_curtains(close)
+ for(var/obj/machinery/door/poddoor/D in GLOB.machines) //I really dislike pathing of these
+ if(D.id != "mafia") //so as to not trigger shutters on station, lol
+ continue
+ if(close)
+ INVOKE_ASYNC(D, /obj/machinery/door/poddoor.proc/close)
+ else
+ INVOKE_ASYNC(D, /obj/machinery/door/poddoor.proc/open)
+
+/**
+ * The actual start of night for players. Mostly info is given at the start of the night as the end of the night is when votes and actions are submitted and tried.
+ *
+ * What players do in this phase:
+ * * Mafia are told to begin voting on who to kill
+ * * Powers that are picked during the day announce themselves right now
+ */
+/datum/mafia_controller/proc/start_night()
+ phase = MAFIA_PHASE_NIGHT
+ send_message("Night [turn] started! Lockdown will end in 45 seconds.")
+ SEND_SIGNAL(src,COMSIG_MAFIA_SUNDOWN)
+ next_phase_timer = addtimer(CALLBACK(src, .proc/resolve_night),night_phase_period,TIMER_STOPPABLE)
+ SStgui.update_uis(src)
+
+/**
+ * The end of the night, and a series of signals for the order of events on a night.
+ *
+ * Order of events, and what they mean:
+ * * Start of resolve (NIGHT_START) is for activating night abilities that MUST go first
+ * * Action phase (NIGHT_ACTION_PHASE) is for non-lethal day abilities
+ * * Mafia then tallies votes and kills the highest voted person (note: one random voter visits that person for the purposes of roleblocking)
+ * * Killing phase (NIGHT_KILL_PHASE) is for lethal night abilities
+ * * End of resolve (NIGHT_END) is for cleaning up abilities that went off and i guess doing some that must go last
+ * * Finally opens the curtains and calls the start of day phase, completing the cycle until check victory returns TRUE
+ */
+/datum/mafia_controller/proc/resolve_night()
+ SEND_SIGNAL(src,COMSIG_MAFIA_NIGHT_START)
+ SEND_SIGNAL(src,COMSIG_MAFIA_NIGHT_ACTION_PHASE)
+ //resolve mafia kill, todo unsnowflake this
+ var/datum/mafia_role/R = get_vote_winner("Mafia")
+ if(R)
+ var/datum/mafia_role/killer = get_random_voter("Mafia")
+ if(SEND_SIGNAL(killer,COMSIG_MAFIA_CAN_PERFORM_ACTION,src,"mafia killing",R) & MAFIA_PREVENT_ACTION)
+ send_message("[killer.body.real_name] was unable to attack [R.body.real_name] tonight!",MAFIA_TEAM_MAFIA)
+ else
+ send_message("[killer.body.real_name] has attacked [R.body.real_name]!",MAFIA_TEAM_MAFIA)
+ R.kill(src)
+ reset_votes("Mafia")
+ SEND_SIGNAL(src,COMSIG_MAFIA_NIGHT_KILL_PHASE)
+ SEND_SIGNAL(src,COMSIG_MAFIA_NIGHT_END)
+ toggle_night_curtains(close=FALSE)
+ start_day()
+ SStgui.update_uis(src)
+
+/**
+ * Proc that goes off when players vote for something with their mafia panel.
+ *
+ * If teams, it hides the tally overlay and only sends the vote messages to the team that is voting
+ * Arguments:
+ * * voter: the mafia role that is trying to vote for...
+ * * target: the mafia role that is getting voted for
+ * * vote_type: type of vote submitted (is this the day vote? is this the mafia night vote?)
+ * * teams: see mafia team defines for what to put in, makes the messages only send to a specific team (so mafia night votes only sending messages to mafia at night)
+ */
+/datum/mafia_controller/proc/vote_for(datum/mafia_role/voter,datum/mafia_role/target,vote_type, teams)
+ if(!votes[vote_type])
+ votes[vote_type] = list()
+ var/old_vote = votes[vote_type][voter]
+ if(old_vote && old_vote == target)
+ votes[vote_type] -= voter
+ else
+ votes[vote_type][voter] = target
+ if(old_vote && old_vote == target)
+ send_message("[voter.body.real_name] retracts their vote for [target.body.real_name]!", team = teams)
+ else
+ send_message("[voter.body.real_name] voted for [target.body.real_name]!",team = teams)
+ if(!teams)
+ target.body.update_icon() //Update the vote display if it's a public vote
+ var/datum/mafia_role/old = old_vote
+ if(old)
+ old.body.update_icon()
+
+/**
+ * Clears out the votes of a certain type (day votes, mafia kill votes) while leaving others untouched
+ */
+/datum/mafia_controller/proc/reset_votes(vote_type)
+ var/list/bodies_to_update = list()
+ for(var/vote in votes[vote_type])
+ var/datum/mafia_role/R = votes[vote_type][vote]
+ bodies_to_update += R.body
+ votes[vote_type] = list()
+ for(var/mob/M in bodies_to_update)
+ M.update_icon()
+
+/**
+ * Returns how many people voted for the role, in whatever vote (day vote, night kill vote)
+ * Arguments:
+ * * role: the mafia role the proc tries to get the amount of votes for
+ * * vote_type: the vote type (getting how many day votes were for the role, or mafia night votes for the role)
+ */
+/datum/mafia_controller/proc/get_vote_count(role,vote_type)
+ . = 0
+ for(var/v in votes[vote_type])
+ var/datum/mafia_role/votee = v
+ if(votes[vote_type][votee] == role)
+ . += votee.vote_power
+
+/**
+ * Returns whichever role got the most votes, in whatever vote (day vote, night kill vote)
+ * returns null if no votes
+ * Arguments:
+ * * vote_type: the vote type (getting the role that got the most day votes, or the role that got the most mafia votes)
+ */
+/datum/mafia_controller/proc/get_vote_winner(vote_type)
+ var/list/tally = list()
+ for(var/votee in votes[vote_type])
+ if(!tally[votes[vote_type][votee]])
+ tally[votes[vote_type][votee]] = 1
+ else
+ tally[votes[vote_type][votee]] += 1
+ sortTim(tally,/proc/cmp_numeric_dsc,associative=TRUE)
+ return length(tally) ? tally[1] : null
+
+/**
+ * Returns a random person who voted for whatever vote (day vote, night kill vote)
+ * Arguments:
+ * * vote_type: vote type (getting a random day voter, or mafia night voter)
+ */
+/datum/mafia_controller/proc/get_random_voter(vote_type)
+ if(length(votes[vote_type]))
+ return pick(votes[vote_type])
+
+/**
+ * Adds mutable appearances to people who get publicly voted on (so not night votes) showing how many people are picking them
+ * Arguments:
+ * * source: the body of the role getting the overlays
+ * * overlay_list: signal var passing the overlay list of the mob
+ */
+/datum/mafia_controller/proc/display_votes(atom/source, list/overlay_list)
+ if(phase != MAFIA_PHASE_VOTING)
+ return
+ var/v = get_vote_count(player_role_lookup[source],"Day")
+ var/mutable_appearance/MA = mutable_appearance('icons/obj/mafia.dmi',"vote_[v > 12 ? "over_12" : v]")
+ overlay_list += MA
+
+/**
+ * Called when the game is setting up, AFTER map is loaded but BEFORE the phase timers start. Creates and places each role's body and gives the correct player key
+ *
+ * Notably:
+ * * Toggles godmode so the mafia players cannot kill themselves
+ * * Adds signals for voting overlays, see display_votes proc
+ * * gives mafia panel
+ * * sends the greeting text (goals, role name, etc)
+ */
+/datum/mafia_controller/proc/create_bodies()
+ for(var/datum/mafia_role/role in all_roles)
+ var/mob/living/carbon/human/H = new(get_turf(role.assigned_landmark))
+ H.equipOutfit(player_outfit)
+ H.status_flags |= GODMODE
+ RegisterSignal(H,COMSIG_ATOM_UPDATE_OVERLAYS,.proc/display_votes)
+ var/datum/action/innate/mafia_panel/mafia_panel = new(null,src)
+ mafia_panel.Grant(H)
+ var/client/player_client = GLOB.directory[role.player_key]
+ if(player_client)
+ player_client.prefs.copy_to(H)
+ if(H.dna.species.outfit_important_for_life) //plasmamen
+ H.set_species(/datum/species/human)
+ role.body = H
+ player_role_lookup[H] = role
+ H.key = role.player_key
+ role.greet()
+
+/datum/mafia_controller/ui_data(mob/user)
+ . = ..()
+ switch(phase)
+ if(MAFIA_PHASE_DAY,MAFIA_PHASE_VOTING,MAFIA_PHASE_JUDGEMENT)
+ .["phase"] = "Day [turn]"
+ if(MAFIA_PHASE_NIGHT)
+ .["phase"] = "Night [turn]"
+ else
+ .["phase"] = "No Game"
+ if(user.client?.holder)
+ .["admin_controls"] = TRUE //show admin buttons to start/setup/stop
+ if(phase == MAFIA_PHASE_JUDGEMENT)
+ .["judgement_phase"] = TRUE //show judgement section
+ else
+ .["judgement_phase"] = FALSE
+ var/datum/mafia_role/user_role = player_role_lookup[user]
+ if(user_role)
+ .["roleinfo"] = list("role" = user_role.name,"desc" = user_role.desc, "action_log" = user_role.role_notes, "hud_icon" = user_role.hud_icon, "revealed_icon" = user_role.revealed_icon)
+ var/actions = list()
+ for(var/action in user_role.actions)
+ if(user_role.validate_action_target(src,action,null))
+ actions += action
+ .["actions"] = actions
+ .["role_theme"] = user_role.special_theme
+ else
+ var/list/lobby_data = list()
+ for(var/key in GLOB.mafia_signup + GLOB.mafia_bad_signup)
+ var/list/lobby_member = list()
+ lobby_member["name"] = key
+ lobby_member["status"] = "Ready"
+ if(key in GLOB.mafia_bad_signup)
+ lobby_member["status"] = "Disconnected"
+ lobby_member["spectating"] = "Ghost"
+ if(key in spectators)
+ lobby_member["spectating"] = "Spectator"
+ lobby_data += list(lobby_member)
+ .["lobbydata"] = lobby_data
+ var/list/player_data = list()
+ for(var/datum/mafia_role/R in all_roles)
+ var/list/player_info = list()
+ var/list/actions = list()
+ if(user_role) //not observer
+ for(var/action in user_role.targeted_actions)
+ if(user_role.validate_action_target(src,action,R))
+ actions += action
+ //Awful snowflake, could use generalizing
+ if(phase == MAFIA_PHASE_VOTING)
+ player_info["votes"] = get_vote_count(R,"Day")
+ if(R.game_status == MAFIA_ALIVE && R != user_role)
+ actions += "Vote"
+ if(phase == MAFIA_PHASE_NIGHT && user_role.team == MAFIA_TEAM_MAFIA && R.game_status == MAFIA_ALIVE && R.team != MAFIA_TEAM_MAFIA)
+ actions += "Kill Vote"
+ player_info["name"] = R.body.real_name
+ player_info["ref"] = REF(R)
+ player_info["actions"] = actions
+ player_info["alive"] = R.game_status == MAFIA_ALIVE
+ player_data += list(player_info)
+ .["players"] = player_data
+ .["timeleft"] = next_phase_timer ? timeleft(next_phase_timer) : 0
+
+ //Not sure on this, should this info be visible
+ .["all_roles"] = current_setup_text
+
+/datum/mafia_controller/ui_assets(mob/user)
+ return list(
+ get_asset_datum(/datum/asset/spritesheet/mafia),
+ )
+
+/datum/mafia_controller/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
+ . = ..()
+ if(.)
+ return
+ var/datum/mafia_role/user_role = player_role_lookup[usr]
+ //Admin actions
+ if(usr.client?.holder)
+ switch(action)
+ if("new_game")
+ end_game()
+ basic_setup()
+ if("nuke")
+ end_game()
+ qdel(src)
+ if("next_phase")
+ var/datum/timedevent/timer = SStimer.timer_id_dict[next_phase_timer]
+ if(!timer.spent)
+ var/datum/callback/tc = timer.callBack
+ deltimer(next_phase_timer)
+ tc.InvokeAsync()
+ return TRUE
+ if("players_home")
+ var/list/failed = list()
+ for(var/datum/mafia_role/player in all_roles)
+ if(!player.body)
+ failed += player
+ continue
+ player.body.forceMove(get_turf(player.assigned_landmark))
+ if(failed.len)
+ to_chat(usr, "List of players who no longer had a body (if you see this, the game is runtiming anyway so just hit \"New Game\" to end it)")
+ for(var/i in failed)
+ var/datum/mafia_role/fail = i
+ to_chat(usr, fail.player_key)
+ if("debug_setup")
+ var/list/debug_setup = list()
+ var/list/rolelist_dict = list()
+ var/done = FALSE
+ for(var/p in typesof(/datum/mafia_role))
+ var/datum/mafia_role/path = p
+ rolelist_dict[initial(path.name) + " ([uppertext(initial(path.team))])"] = path
+ rolelist_dict = list("CANCEL", "FINISH") + rolelist_dict
+ while(!done)
+ to_chat(usr, "You have a total player count of [assoc_value_sum(debug_setup)] in this setup.")
+ var/chosen_role_name = input(usr,"Select a role!","Custom Setup Creation",rolelist_dict[1]) as null|anything in rolelist_dict
+ if(chosen_role_name == "CANCEL")
+ return
+ if(chosen_role_name == "FINISH")
+ break
+ var/found_path = rolelist_dict[chosen_role_name]
+ var/role_count = input(usr,"How many? Zero to cancel.","Custom Setup Creation",0) as null|num
+ if(role_count > 0)
+ debug_setup[found_path] = role_count
+ custom_setup = debug_setup
+ if("cancel_setup")
+ custom_setup = list()
+ switch(action) //both living and dead
+ if("mf_lookup")
+ var/role_lookup = params["atype"]
+ var/datum/mafia_role/helper
+ for(var/datum/mafia_role/role in all_roles)
+ if(role_lookup == role.name)
+ helper = role
+ break
+ helper.show_help(usr)
+ if(!user_role)//just the dead
+ var/client/C = ui.user.client
+ switch(action)
+ if("mf_signup")
+ if(!SSticker.HasRoundStarted())
+ to_chat(usr, "Wait for the round to start.")
+ return
+ if(GLOB.mafia_signup[C.ckey])
+ GLOB.mafia_signup -= C.ckey
+ to_chat(usr, "You unregister from Mafia.")
+ return
+ else
+ GLOB.mafia_signup[C.ckey] = C
+ to_chat(usr, "You sign up for Mafia.")
+ if(phase == MAFIA_PHASE_SETUP)
+ check_signups()
+ try_autostart()
+ if("mf_spectate")
+ if(C.ckey in spectators)
+ to_chat(usr, "You will no longer get messages from the game.")
+ spectators -= C.ckey
+ else
+ to_chat(usr, "You will now get messages from the game.")
+ spectators += C.ckey
+ if(user_role.game_status == MAFIA_DEAD)
+ return
+ //User actions (just living)
+ switch(action)
+ if("mf_action")
+ if(!user_role.actions.Find(params["atype"]))
+ return
+ user_role.handle_action(src,params["atype"],null)
+ return TRUE //vals for self-ui update
+ if("mf_targ_action")
+ var/datum/mafia_role/target = locate(params["target"]) in all_roles
+ if(!istype(target))
+ return
+ switch(params["atype"])
+ if("Vote")
+ if(phase != MAFIA_PHASE_VOTING)
+ return
+ vote_for(user_role,target,vote_type="Day")
+ if("Kill Vote")
+ if(phase != MAFIA_PHASE_NIGHT || user_role.team != MAFIA_TEAM_MAFIA)
+ return
+ vote_for(user_role,target,"Mafia", MAFIA_TEAM_MAFIA)
+ to_chat(user_role.body,"You will vote for [target.body.real_name] for tonights killing.")
+ else
+ if(!user_role.targeted_actions.Find(params["atype"]))
+ return
+ if(!user_role.validate_action_target(src,params["atype"],target))
+ return
+ user_role.handle_action(src,params["atype"],target)
+ return TRUE
+ if(user_role != on_trial)
+ switch(action)
+ if("vote_abstain")
+ if(phase != MAFIA_PHASE_JUDGEMENT || (user_role in judgement_abstain_votes))
+ return
+ to_chat(user_role.body,"You have decided to abstain.")
+ judgement_innocent_votes -= user_role
+ judgement_guilty_votes -= user_role
+ judgement_abstain_votes += user_role
+ if("vote_innocent")
+ if(phase != MAFIA_PHASE_JUDGEMENT || (user_role in judgement_innocent_votes))
+ return
+ to_chat(user_role.body,"Your vote on [on_trial.body.real_name] submitted as INNOCENT!")
+ judgement_abstain_votes -= user_role//no fakers, and...
+ judgement_guilty_votes -= user_role//no radical centrism
+ judgement_innocent_votes += user_role
+ if("vote_guilty")
+ if(phase != MAFIA_PHASE_JUDGEMENT || (user_role in judgement_guilty_votes))
+ return
+ to_chat(user_role.body,"Your vote on [on_trial.body.real_name] submitted as GUILTY!")
+ judgement_abstain_votes -= user_role//no fakers, and...
+ judgement_innocent_votes -= user_role//no radical centrism
+ judgement_guilty_votes += user_role
+
+/datum/mafia_controller/ui_state(mob/user)
+ return GLOB.always_state
+
+/datum/mafia_controller/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, null)
+ if(!ui)
+ ui = new(user, src, "MafiaPanel")
+ ui.set_autoupdate(FALSE)
+ ui.open()
+
+/proc/assoc_value_sum(list/L)
+ . = 0
+ for(var/key in L)
+ . += L[key]
+
+/**
+ * Returns a semirandom setup, with...
+ * Town, Two invest roles, one protect role, sometimes a misc role, and the rest assistants for town.
+ * Mafia, 2 normal mafia and one special.
+ * Neutral, two disruption roles, sometimes one is a killing.
+ *
+ * See _defines.dm in the mafia folder for a rundown on what these groups of roles include.
+ */
+/datum/mafia_controller/proc/generate_random_setup()
+ var/invests_left = 2
+ var/protects_left = 1
+ var/miscs_left = prob(35)
+ var/mafiareg_left = 2
+ var/mafiaspe_left = 1
+ var/killing_role = prob(50)
+ var/disruptors = killing_role ? 1 : 2 //still required to calculate overflow
+ var/overflow_left = max_player - (invests_left + protects_left + miscs_left + mafiareg_left + mafiaspe_left + killing_role + disruptors)
+
+ var/list/random_setup = list()
+ for(var/i in 1 to max_player) //should match the number of roles to add
+ if(overflow_left)
+ add_setup_role(random_setup, TOWN_OVERFLOW)
+ overflow_left--
+ else if(invests_left)
+ add_setup_role(random_setup, TOWN_INVEST)
+ invests_left--
+ else if(protects_left)
+ add_setup_role(random_setup, TOWN_PROTECT)
+ protects_left--
+ else if(miscs_left)
+ add_setup_role(random_setup, TOWN_MISC)
+ miscs_left--
+ else if(mafiareg_left)
+ add_setup_role(random_setup, MAFIA_REGULAR)
+ mafiareg_left--
+ else if(mafiaspe_left)
+ add_setup_role(random_setup, MAFIA_SPECIAL)
+ mafiaspe_left--
+ else if(killing_role)
+ add_setup_role(random_setup, NEUTRAL_KILL)
+ killing_role--
+ else
+ add_setup_role(random_setup, NEUTRAL_DISRUPT)
+ return random_setup
+
+/**
+ * Helper proc that adds a random role of a type to a setup. if it doesn't exist in the setup, it adds the path to the list and otherwise bumps the path in the list up one
+ */
+/datum/mafia_controller/proc/add_setup_role(setup_list, wanted_role_type)
+ var/list/role_type_paths = list()
+ for(var/path in typesof(/datum/mafia_role))
+ var/datum/mafia_role/instance = path
+ if(initial(instance.role_type) == wanted_role_type)
+ role_type_paths += instance
+
+ var/mafia_path = pick(role_type_paths)
+ var/datum/mafia_role/mafia_path_type = mafia_path
+ var/found_role
+ for(var/searched_path in setup_list)
+ var/datum/mafia_role/searched_path_type = searched_path
+ if(initial(mafia_path_type.name) == initial(searched_path_type.name))
+ found_role = searched_path
+ break
+ if(found_role)
+ setup_list[found_role] += 1
+ return
+ setup_list[mafia_path] = 1
+
+/**
+ * Called when enough players have signed up to fill a setup. DOESN'T NECESSARILY MEAN THE GAME WILL START.
+ *
+ * Checks for a custom setup, if so gets the required players from that and if not it sets the player requirement to required_player(max_player) and generates one IF basic setup starts a game.
+ * Checks if everyone signed up is an observer, and is still connected. If people aren't, they're removed from the list.
+ * If there aren't enough players post sanity, it aborts. otherwise, it selects enough people for the game and starts preparing the game for real.
+ */
+/datum/mafia_controller/proc/basic_setup()
+ var/req_players
+ var/list/setup = custom_setup
+ if(!setup.len)
+ req_players = required_player //max_player
+ else
+ req_players = assoc_value_sum(setup)
+
+ //final list for all the players who will be in this game
+ var/list/filtered_keys = list()
+ //cuts invalid players from signups (disconnected/not a ghost)
+ var/list/possible_keys = list()
+ for(var/key in GLOB.mafia_signup)
+ if(GLOB.directory[key])
+ var/client/C = GLOB.directory[key]
+ if(isobserver(C.mob))
+ possible_keys += key
+ continue
+ GLOB.mafia_signup -= key //not valid to play when we checked so remove them from signups
+
+ //if there were not enough players, don't start. we already trimmed the list to now hold only valid signups
+ if(length(possible_keys) < req_players)
+ return
+ else //hacky implementation of max players
+ req_players = clamp(length(possible_keys), 1, max_player)
+
+ //if there were too many players, still start but only make filtered keys as big as it needs to be (cut excess)
+ //also removes people who do get into final player list from the signup so they have to sign up again when game ends
+ for(var/i in 1 to req_players)
+ var/chosen_key = pick_n_take(possible_keys)
+ filtered_keys += chosen_key
+ GLOB.mafia_signup -= chosen_key
+ //small message about not getting into this game for clarity on why they didn't get in
+ for(var/unpicked in possible_keys)
+ var/client/unpicked_client = GLOB.directory[unpicked]
+ to_chat(unpicked_client, "Sorry, the starting mafia game has too many players and you were not picked.")
+ to_chat(unpicked_client, "You're still signed up, getting messages from the current round, and have another chance to join when the one starting now finishes.")
+
+ if(!setup.len) //don't actually have one yet, so generate a max player random setup. it's good to do this here instead of above so it doesn't generate one every time a game could possibly start.
+ setup = generate_random_setup()
+ prepare_game(setup,filtered_keys)
+ start_game()
+
+/**
+ * Called when someone signs up, and sees if there are enough people in the signup list to begin.
+ *
+ * Only checks if everyone is actually valid to start (still connected and an observer) if there are enough players (basic_setup)
+ */
+/datum/mafia_controller/proc/try_autostart()
+ if(phase != MAFIA_PHASE_SETUP) // || !(GLOB.ghost_role_flags & GHOSTROLE_MINIGAME))
+ return
+ if(GLOB.mafia_signup.len >= max_player || GLOB.mafia_signup.len >= required_player|| custom_setup.len)//enough people to try and make something (or debug mode)
+ basic_setup()
+
+/**
+ * Filters inactive player into a different list until they reconnect, and removes players who are no longer ghosts.
+ *
+ * If a disconnected player gets a non-ghost mob and reconnects, they will be first put back into mafia_signup then filtered by that.
+ */
+/datum/mafia_controller/proc/check_signups()
+ for(var/bad_key in GLOB.mafia_bad_signup)
+ if(GLOB.directory[bad_key])//they have reconnected if we can search their key and get a client
+ GLOB.mafia_bad_signup -= bad_key
+ GLOB.mafia_signup += bad_key
+ for(var/key in GLOB.mafia_signup)
+ var/client/C = GLOB.directory[key]
+ if(!C)//vice versa but in a variable we use later
+ GLOB.mafia_signup -= key
+ GLOB.mafia_bad_signup += key
+ if(!isobserver(C.mob))
+ //they are back to playing the game, remove them from the signups
+ GLOB.mafia_signup -= key
+
+/datum/action/innate/mafia_panel
+ name = "Mafia Panel"
+ desc = "Use this to play."
+ icon_icon = 'icons/obj/mafia.dmi'
+ button_icon_state = "board"
+ var/datum/mafia_controller/parent
+
+/datum/action/innate/mafia_panel/New(Target,mf)
+ . = ..()
+ parent = mf
+
+/datum/action/innate/mafia_panel/Activate()
+ parent.ui_interact(owner)
+
+/**
+ * Creates the global datum for playing mafia games, destroys the last if that's required and returns the new.
+ */
+/proc/create_mafia_game()
+ if(GLOB.mafia_game)
+ QDEL_NULL(GLOB.mafia_game)
+ var/datum/mafia_controller/MF = new()
+ return MF
diff --git a/code/modules/mafia/map_pieces.dm b/code/modules/mafia/map_pieces.dm
new file mode 100644
index 0000000000..3339c596b4
--- /dev/null
+++ b/code/modules/mafia/map_pieces.dm
@@ -0,0 +1,79 @@
+/obj/effect/landmark/mafia_game_area //locations where mafia will be loaded by the datum
+ name = "Mafia Area Spawn"
+ var/game_id = "mafia"
+
+/obj/effect/landmark/mafia
+ name = "Mafia Player Spawn"
+ var/game_id = "mafia"
+
+/obj/effect/landmark/mafia/town_center
+ name = "Mafia Town Center"
+
+//for ghosts/admins
+/obj/mafia_game_board
+ name = "Mafia Game Board"
+ icon = 'icons/obj/mafia.dmi'
+ icon_state = "board"
+ anchored = TRUE
+ var/game_id = "mafia"
+ var/datum/mafia_controller/MF
+
+/obj/mafia_game_board/attack_ghost(mob/user)
+ . = ..()
+ if(!MF)
+ MF = GLOB.mafia_game
+ if(!MF)
+ MF = create_mafia_game()
+ MF.ui_interact(user)
+
+/area/mafia
+ name = "Mafia Minigame"
+ icon_state = "mafia"
+ dynamic_lighting = DYNAMIC_LIGHTING_DISABLED
+ requires_power = FALSE
+ has_gravity = STANDARD_GRAVITY
+ flags_1 = NONE
+ // block_suicide = TRUE
+
+/datum/map_template/mafia
+ var/description = ""
+
+/datum/map_template/mafia/summerball
+ name = "Summerball 2020"
+ description = "The original, the OG. The 2020 Summer ball was where mafia came from, with this map."
+ mappath = "_maps/map_files/Mafia/mafia_ball.dmm"
+
+/datum/map_template/mafia/syndicate
+ name = "Syndicate Megastation"
+ description = "Yes, it's a very confusing day at the Megastation. Will the syndicate conflict resolution operatives succeed?"
+ mappath = "_maps/map_files/Mafia/mafia_syndie.dmm"
+
+/datum/map_template/mafia/lavaland
+ name = "Lavaland Excursion"
+ description = "The station has no idea what's going down on lavaland right now, we got changelings... traitors, and worst of all... lawyers roleblocking you every night."
+ mappath = "_maps/map_files/Mafia/mafia_lavaland.dmm"
+
+/datum/map_template/mafia/ufo
+ name = "Alien Mothership"
+ description = "The haunted ghost UFO tour has gone south and now it's up to our fine townies and scare seekers to kill the actual real alien changelings..."
+ mappath = "_maps/map_files/Mafia/mafia_ayylmao.dmm"
+
+/datum/map_template/mafia/spider_clan
+ name = "Spider Clan Kidnapping"
+ description = "New and improved spider clan kidnappings are a lot less boring and have a lot more lynching. Damn westaboos!"
+ mappath = "_maps/map_files/Mafia/mafia_spiderclan.dmm"
+
+/datum/map_template/mafia/snowy
+ name = "Snowdin"
+ description = "Based off of the icey moon map of the same name, the guy who reworked it pretty much did it for nothing since away missions are disabled but at least he'll get this...?"
+ mappath = "_maps/map_files/Mafia/mafia_snow.dmm"
+
+/datum/map_template/mafia/gothic
+ name = "Vampire's Castle"
+ description = "Vampires and changelings clash to find out who's the superior bloodsucking monster in this creepy castle map."
+ mappath = "_maps/map_files/Mafia/mafia_gothic.dmm"
+
+/datum/map_template/mafia/reebe
+ name = "Reebe"
+ description = "Trouble in Reebe station! Copypaste guranteed by ClockCo™"
+ mappath = "_maps/map_files/Mafia/mafia_reebe.dmm"
diff --git a/code/modules/mafia/outfits.dm b/code/modules/mafia/outfits.dm
new file mode 100644
index 0000000000..bbc72bd120
--- /dev/null
+++ b/code/modules/mafia/outfits.dm
@@ -0,0 +1,108 @@
+
+//what people wear unrevealed
+
+/datum/outfit/mafia
+ name = "Mafia Game Outfit"
+ uniform = /obj/item/clothing/under/color/grey
+ shoes = /obj/item/clothing/shoes/sneakers/black
+
+//town
+
+/datum/outfit/mafia/assistant
+ name = "Mafia Assistant"
+
+ uniform = /obj/item/clothing/under/color/rainbow
+
+/datum/outfit/mafia/detective
+ name = "Mafia Detective"
+
+ uniform = /obj/item/clothing/under/rank/security/detective
+ // neck = /obj/item/clothing/neck/tie/detective
+ shoes = /obj/item/clothing/shoes/sneakers/brown
+ suit = /obj/item/clothing/suit/det_suit
+ gloves = /obj/item/clothing/gloves/color/black
+ head = /obj/item/clothing/head/fedora/det_hat
+ mask = /obj/item/clothing/mask/cigarette
+
+/datum/outfit/mafia/psychologist
+ name = "Mafia Psychologist"
+
+ uniform = /obj/item/clothing/under/suit/black
+ shoes = /obj/item/clothing/shoes/laceup
+
+/datum/outfit/mafia/md
+ name = "Mafia Medical Doctor"
+
+ uniform = /obj/item/clothing/under/rank/medical/doctor
+ shoes = /obj/item/clothing/shoes/sneakers/white
+ suit = /obj/item/clothing/suit/toggle/labcoat
+
+/datum/outfit/mafia/chaplain
+ name = "Mafia Chaplain"
+
+ uniform = /obj/item/clothing/under/rank/civilian/chaplain
+
+/datum/outfit/mafia/lawyer
+ name = "Mafia Lawyer"
+
+ uniform = /obj/item/clothing/under/rank/civilian/lawyer/bluesuit
+ suit = /obj/item/clothing/suit/toggle/lawyer
+ shoes = /obj/item/clothing/shoes/laceup
+
+/datum/outfit/mafia/hop
+ name = "Mafia Head of Personnel"
+
+ uniform = /obj/item/clothing/under/rank/civilian/head_of_personnel
+ suit = /obj/item/clothing/suit/armor/vest/alt
+ shoes = /obj/item/clothing/shoes/sneakers/brown
+ head = /obj/item/clothing/head/hopcap
+ glasses = /obj/item/clothing/glasses/sunglasses
+
+//mafia
+
+/datum/outfit/mafia/changeling
+ name = "Mafia Changeling"
+
+ head = /obj/item/clothing/head/helmet/changeling
+ suit = /obj/item/clothing/suit/armor/changeling
+
+//solo
+
+/datum/outfit/mafia/fugitive
+ name = "Mafia Fugitive"
+
+ uniform = /obj/item/clothing/under/rank/prisoner
+ shoes = /obj/item/clothing/shoes/sneakers/orange
+
+/datum/outfit/mafia/obsessed
+ name = "Mafia Obsessed"
+ uniform = /obj/item/clothing/under/misc/overalls
+ shoes = /obj/item/clothing/shoes/sneakers/white
+ gloves = /obj/item/clothing/gloves/color/latex
+ mask = /obj/item/clothing/mask/surgical
+ suit = /obj/item/clothing/suit/apron
+
+/datum/outfit/mafia/obsessed/post_equip(mob/living/carbon/human/H)
+ for(var/obj/item/carried_item in H.get_equipped_items(TRUE))
+ carried_item.add_mob_blood(H)//Oh yes, there will be blood...
+ H.regenerate_icons()
+
+/datum/outfit/mafia/clown
+ name = "Mafia Clown"
+
+ uniform = /obj/item/clothing/under/rank/civilian/clown
+ shoes = /obj/item/clothing/shoes/clown_shoes
+ mask = /obj/item/clothing/mask/gas/clown_hat
+
+/datum/outfit/mafia/traitor
+ name = "Mafia Traitor"
+
+ mask = /obj/item/clothing/mask/gas/syndicate
+ uniform = /obj/item/clothing/under/syndicate/tacticool
+ shoes = /obj/item/clothing/shoes/jackboots
+
+/datum/outfit/mafia/nightmare
+ name = "Mafia Nightmare"
+
+ uniform = null
+ shoes = null
diff --git a/code/modules/mafia/roles.dm b/code/modules/mafia/roles.dm
new file mode 100644
index 0000000000..2461a93976
--- /dev/null
+++ b/code/modules/mafia/roles.dm
@@ -0,0 +1,705 @@
+/datum/mafia_role
+ var/name = "Assistant"
+ var/desc = "You are a crewmember without any special abilities."
+ var/win_condition = "kill all mafia and solo killing roles."
+ var/team = MAFIA_TEAM_TOWN
+ ///how the random setup chooses which roles get put in
+ var/role_type = TOWN_OVERFLOW
+
+ var/player_key
+ var/mob/living/carbon/human/body
+ var/obj/effect/landmark/mafia/assigned_landmark
+
+ ///how many votes submitted when you vote.
+ var/vote_power = 1
+ var/detect_immune = FALSE
+ var/revealed = FALSE
+ var/datum/outfit/revealed_outfit = /datum/outfit/mafia/assistant //the assistants need a special path to call out they were in fact assistant, everything else can just use job equipment
+ //action = uses
+ var/list/actions = list()
+ var/list/targeted_actions = list()
+ //what the role gets when it wins a game
+ // var/winner_award = /datum/award/achievement/mafia/assistant
+
+ //so mafia have to also kill them to have a majority
+ var/solo_counts_as_town = FALSE //(don't set this for town)
+ var/game_status = MAFIA_ALIVE
+
+ ///icon state in the mafia dmi of the hud of the role, used in the mafia ui
+ var/hud_icon = "hudassistant"
+ ///icon state in the mafia dmi of the hud of the role, used in the mafia ui
+ var/revealed_icon = "assistant"
+ ///set this to something cool for antagonists and their window will look different
+ var/special_theme
+
+ var/list/role_notes = list()
+
+
+/datum/mafia_role/New(datum/mafia_controller/game)
+ . = ..()
+
+/datum/mafia_role/proc/kill(datum/mafia_controller/game,lynch=FALSE)
+ if(SEND_SIGNAL(src,COMSIG_MAFIA_ON_KILL,game,lynch) & MAFIA_PREVENT_KILL)
+ return FALSE
+ game_status = MAFIA_DEAD
+ body.death()
+ if(lynch)
+ reveal_role(game, verbose = TRUE)
+ if(!(player_key in game.spectators)) //people who played will want to see the end of the game more often than not
+ game.spectators += player_key
+ return TRUE
+
+/datum/mafia_role/Destroy(force, ...)
+ QDEL_NULL(body)
+ . = ..()
+
+/datum/mafia_role/proc/greet()
+ SEND_SOUND(body, 'sound/ambience/ambifailure.ogg')
+ to_chat(body,"You are the [name].")
+ to_chat(body,"[desc]")
+ switch(team)
+ if(MAFIA_TEAM_MAFIA)
+ to_chat(body,"You and your co-conspirators win if you outnumber crewmembers.")
+ if(MAFIA_TEAM_TOWN)
+ to_chat(body,"You are a crewmember. Find out and lynch the changelings!")
+ if(MAFIA_TEAM_SOLO)
+ to_chat(body,"You are not aligned to town or mafia. Accomplish your own objectives!")
+ to_chat(body, "Be sure to read the wiki page to learn more, if you have no idea what's going on.")
+
+/datum/mafia_role/proc/reveal_role(datum/mafia_controller/game, verbose = FALSE)
+ if(revealed)
+ return
+ if(verbose)
+ game.send_message("It is revealed that the true role of [body] [game_status == MAFIA_ALIVE ? "is" : "was"] [name]!")
+ var/list/oldoutfit = body.get_equipped_items()
+ for(var/thing in oldoutfit)
+ qdel(thing)
+ special_reveal_equip(game)
+ body.equipOutfit(revealed_outfit)
+ revealed = TRUE
+
+/datum/mafia_role/proc/special_reveal_equip(datum/mafia_controller/game)
+ return
+
+/datum/mafia_role/proc/handle_action(datum/mafia_controller/game,action,datum/mafia_role/target)
+ return
+
+/datum/mafia_role/proc/validate_action_target(datum/mafia_controller/game,action,datum/mafia_role/target)
+ if(SEND_SIGNAL(src,COMSIG_MAFIA_CAN_PERFORM_ACTION,game,action,target) & MAFIA_PREVENT_ACTION)
+ return FALSE
+ return TRUE
+
+/datum/mafia_role/proc/add_note(note)
+ role_notes += note
+
+/datum/mafia_role/proc/check_total_victory(alive_town, alive_mafia) //solo antags can win... solo.
+ return FALSE
+
+/datum/mafia_role/proc/block_team_victory(alive_town, alive_mafia) //solo antags can also block team wins.
+ return FALSE
+
+/datum/mafia_role/proc/show_help(clueless)
+ var/list/result = list()
+ var/team_desc = ""
+ var/team_span = ""
+ var/the = TRUE
+ switch(team)
+ if(MAFIA_TEAM_TOWN)
+ team_desc = "Town"
+ team_span = "nicegreen"
+ if(MAFIA_TEAM_MAFIA)
+ team_desc = "Mafia"
+ team_span = "red"
+ if(MAFIA_TEAM_SOLO)
+ team_desc = "Nobody"
+ team_span = "comradio"
+ the = FALSE
+ result += "The [name] is aligned with [the ? "the " : ""][team_desc]"
+ result += "\"[desc]\""
+ result += "[name] wins when they [win_condition]"
+ to_chat(clueless, result.Join(""))
+
+/datum/mafia_role/detective
+ name = "Detective"
+ desc = "You can investigate a single person each night to learn their team."
+ revealed_outfit = /datum/outfit/mafia/detective
+ role_type = TOWN_INVEST
+ // winner_award = /datum/award/achievement/mafia/detective
+
+ hud_icon = "huddetective"
+ revealed_icon = "detective"
+
+ targeted_actions = list("Investigate")
+
+ var/datum/mafia_role/current_investigation
+
+/datum/mafia_role/detective/New(datum/mafia_controller/game)
+ . = ..()
+ RegisterSignal(game,COMSIG_MAFIA_NIGHT_ACTION_PHASE,.proc/investigate)
+
+/datum/mafia_role/detective/validate_action_target(datum/mafia_controller/game,action,datum/mafia_role/target)
+ . = ..()
+ if(!.)
+ return
+ return game.phase == MAFIA_PHASE_NIGHT && target.game_status == MAFIA_ALIVE && target != src
+
+/datum/mafia_role/detective/handle_action(datum/mafia_controller/game,action,datum/mafia_role/target)
+ if(!target || target.game_status != MAFIA_ALIVE)
+ to_chat(body,"You can only investigate alive people.")
+ return
+ to_chat(body,"You will investigate [target.body.real_name] tonight.")
+ current_investigation = target
+
+/datum/mafia_role/detective/proc/investigate(datum/mafia_controller/game)
+ var/datum/mafia_role/target = current_investigation
+ if(target)
+ if(target.detect_immune)
+ to_chat(body,"Your investigations reveal that [target.body.real_name] is a true member of the station.")
+ add_note("N[game.turn] - [target.body.real_name] - Town")
+ else
+ var/team_text
+ var/fluff
+ switch(target.team)
+ if(MAFIA_TEAM_TOWN)
+ team_text = "Town"
+ fluff = "a true member of the station."
+ if(MAFIA_TEAM_MAFIA)
+ team_text = "Mafia"
+ fluff = "an unfeeling, hideous changeling!"
+ if(MAFIA_TEAM_SOLO)
+ team_text = "Solo"
+ fluff = "a rogue, with their own objectives..."
+ to_chat(body,"Your investigations reveal that [target.body.real_name] is [fluff]")
+ add_note("N[game.turn] - [target.body.real_name] - [team_text]")
+ current_investigation = null
+
+/datum/mafia_role/psychologist
+ name = "Psychologist"
+ desc = "You can visit someone ONCE PER GAME to reveal their true role in the morning!"
+ revealed_outfit = /datum/outfit/mafia/psychologist
+ role_type = TOWN_INVEST
+ // winner_award = /datum/award/achievement/mafia/psychologist
+
+ hud_icon = "hudpsychologist"
+ revealed_icon = "psychologist"
+
+ targeted_actions = list("Reveal")
+ var/datum/mafia_role/current_target
+ var/can_use = TRUE
+
+/datum/mafia_role/psychologist/New(datum/mafia_controller/game)
+ . = ..()
+ RegisterSignal(game,COMSIG_MAFIA_NIGHT_END,.proc/therapy_reveal)
+
+/datum/mafia_role/psychologist/validate_action_target(datum/mafia_controller/game, action, datum/mafia_role/target)
+ . = ..()
+ if(!. || !can_use || game.phase == MAFIA_PHASE_NIGHT || target.game_status != MAFIA_ALIVE || target.revealed || target == src)
+ return FALSE
+
+/datum/mafia_role/psychologist/handle_action(datum/mafia_controller/game, action, datum/mafia_role/target)
+ . = ..()
+ to_chat(body,"You will reveal [target.body.real_name] tonight.")
+ current_target = target
+
+/datum/mafia_role/psychologist/proc/therapy_reveal(datum/mafia_controller/game)
+ if(SEND_SIGNAL(src,COMSIG_MAFIA_CAN_PERFORM_ACTION,game,"reveal",current_target) & MAFIA_PREVENT_ACTION || game_status != MAFIA_ALIVE) //Got lynched or roleblocked by a lawyer.
+ current_target = null
+ if(current_target)
+ add_note("N[game.turn] - [current_target.body.real_name] - Revealed true identity")
+ to_chat(body,"You have revealed the true nature of the [current_target]!")
+ current_target.reveal_role(game, verbose = TRUE)
+ current_target = null
+ can_use = FALSE
+
+/datum/mafia_role/chaplain
+ name = "Chaplain"
+ desc = "You can communicate with spirits of the dead each night to discover dead crewmember roles."
+ revealed_outfit = /datum/outfit/mafia/chaplain
+ role_type = TOWN_INVEST
+ hud_icon = "hudchaplain"
+ revealed_icon = "chaplain"
+ // winner_award = /datum/award/achievement/mafia/chaplain
+
+ targeted_actions = list("Pray")
+ var/current_target
+
+/datum/mafia_role/chaplain/New(datum/mafia_controller/game)
+ . = ..()
+ RegisterSignal(game,COMSIG_MAFIA_NIGHT_ACTION_PHASE,.proc/commune)
+
+/datum/mafia_role/chaplain/validate_action_target(datum/mafia_controller/game, action, datum/mafia_role/target)
+ . = ..()
+ if(!.)
+ return
+ return game.phase == MAFIA_PHASE_NIGHT && target.game_status == MAFIA_DEAD && target != src && !target.revealed
+
+/datum/mafia_role/chaplain/handle_action(datum/mafia_controller/game, action, datum/mafia_role/target)
+ to_chat(body,"You will commune with the spirit of [target.body.real_name] tonight.")
+ current_target = target
+
+/datum/mafia_role/chaplain/proc/commune(datum/mafia_controller/game)
+ var/datum/mafia_role/target = current_target
+ if(target)
+ to_chat(body,"You invoke spirit of [target.body.real_name] and learn their role was [target.name].")
+ add_note("N[game.turn] - [target.body.real_name] - [target.name]")
+ current_target = null
+
+/datum/mafia_role/md
+ name = "Medical Doctor"
+ desc = "You can protect a single person each night from killing."
+ revealed_outfit = /datum/outfit/mafia/md // /mafia <- outfit must be readded (just make a new mafia outfits file for all of these)
+ role_type = TOWN_PROTECT
+ hud_icon = "hudmedicaldoctor"
+ revealed_icon = "medicaldoctor"
+ // winner_award = /datum/award/achievement/mafia/md
+
+ targeted_actions = list("Protect")
+ var/datum/mafia_role/current_protected
+
+/datum/mafia_role/md/New(datum/mafia_controller/game)
+ . = ..()
+ RegisterSignal(game,COMSIG_MAFIA_NIGHT_ACTION_PHASE,.proc/protect)
+ RegisterSignal(game,COMSIG_MAFIA_NIGHT_END,.proc/end_protection)
+
+/datum/mafia_role/md/validate_action_target(datum/mafia_controller/game,action,datum/mafia_role/target)
+ . = ..()
+ if(!.)
+ return
+ if(target.name == "Head of Personnel" && target.revealed)
+ return FALSE
+ return game.phase == MAFIA_PHASE_NIGHT && target.game_status == MAFIA_ALIVE && target != src
+
+/datum/mafia_role/md/handle_action(datum/mafia_controller/game,action,datum/mafia_role/target)
+ if(!target || target.game_status != MAFIA_ALIVE)
+ to_chat(body,"You can only protect alive people.")
+ return
+ to_chat(body,"You will protect [target.body.real_name] tonight.")
+ current_protected = target
+
+/datum/mafia_role/md/proc/protect(datum/mafia_controller/game)
+ if(current_protected)
+ RegisterSignal(current_protected,COMSIG_MAFIA_ON_KILL,.proc/prevent_kill)
+ add_note("N[game.turn] - Protected [current_protected.body.real_name]")
+
+/datum/mafia_role/md/proc/prevent_kill(datum/source)
+ to_chat(body,"The person you protected tonight was attacked!")
+ to_chat(current_protected.body,"You were attacked last night, but someone nursed you back to life!")
+ return MAFIA_PREVENT_KILL
+
+/datum/mafia_role/md/proc/end_protection(datum/mafia_controller/game)
+ if(current_protected)
+ UnregisterSignal(current_protected,COMSIG_MAFIA_ON_KILL)
+ current_protected = null
+
+/datum/mafia_role/lawyer
+ name = "Lawyer"
+ desc = "You can choose a person during the day to provide extensive legal advice to during the night, preventing night actions."
+ revealed_outfit = /datum/outfit/mafia/lawyer
+ role_type = TOWN_PROTECT
+ hud_icon = "hudlawyer"
+ revealed_icon = "lawyer"
+ // winner_award = /datum/award/achievement/mafia/lawyer
+
+ targeted_actions = list("Advise")
+ var/datum/mafia_role/current_target
+
+/datum/mafia_role/lawyer/New(datum/mafia_controller/game)
+ . = ..()
+ RegisterSignal(game,COMSIG_MAFIA_SUNDOWN,.proc/roleblock_text)
+ RegisterSignal(game,COMSIG_MAFIA_NIGHT_START,.proc/try_to_roleblock)
+ RegisterSignal(game,COMSIG_MAFIA_NIGHT_END,.proc/release)
+
+/datum/mafia_role/lawyer/proc/roleblock_text(datum/mafia_controller/game)
+ if(SEND_SIGNAL(src,COMSIG_MAFIA_CAN_PERFORM_ACTION,game,"roleblock",current_target) & MAFIA_PREVENT_ACTION || game_status != MAFIA_ALIVE) //Got lynched or roleblocked by another lawyer.
+ current_target = null
+ if(current_target)
+ to_chat(current_target.body,"YOU HAVE BEEN BLOCKED! YOU CANNOT PERFORM ANY ACTIONS TONIGHT.")
+ add_note("N[game.turn] - [current_target.body.real_name] - Blocked")
+
+/datum/mafia_role/lawyer/validate_action_target(datum/mafia_controller/game, action, datum/mafia_role/target)
+ . = ..()
+ if(!.)
+ return FALSE
+ if(game.phase == MAFIA_PHASE_NIGHT)
+ return FALSE
+ if(target.game_status != MAFIA_ALIVE)
+ return FALSE
+
+/datum/mafia_role/lawyer/handle_action(datum/mafia_controller/game, action, datum/mafia_role/target)
+ . = ..()
+ if(target == current_target)
+ current_target = null
+ to_chat(body,"You have decided against blocking anyone tonight.")
+ else
+ current_target = target
+ to_chat(body,"You will block [target.body.real_name] tonight.")
+
+/datum/mafia_role/lawyer/proc/try_to_roleblock(datum/mafia_controller/game)
+ if(current_target)
+ RegisterSignal(current_target,COMSIG_MAFIA_CAN_PERFORM_ACTION, .proc/prevent_action)
+
+/datum/mafia_role/lawyer/proc/release(datum/mafia_controller/game)
+ . = ..()
+ if(current_target)
+ UnregisterSignal(current_target, COMSIG_MAFIA_CAN_PERFORM_ACTION)
+ current_target = null
+
+/datum/mafia_role/lawyer/proc/prevent_action(datum/source)
+ if(game_status == MAFIA_ALIVE) //in case we got killed while imprisoning sk - bad luck edge
+ return MAFIA_PREVENT_ACTION
+
+/datum/mafia_role/hop
+ name = "Head of Personnel"
+ desc = "You can reveal yourself once per game, tripling your vote power but becoming unable to be protected!"
+ revealed_outfit = /datum/outfit/mafia/hop
+ role_type = TOWN_MISC
+ hud_icon = "hudheadofpersonnel"
+ revealed_icon = "headofpersonnel"
+ // winner_award = /datum/award/achievement/mafia/hop
+
+ targeted_actions = list("Reveal")
+
+/datum/mafia_role/hop/validate_action_target(datum/mafia_controller/game, action, datum/mafia_role/target)
+ . = ..()
+ if(!. || game.phase == MAFIA_PHASE_NIGHT || game.turn == 1 || target.game_status != MAFIA_ALIVE || target != src || revealed)
+ return FALSE
+
+/datum/mafia_role/hop/handle_action(datum/mafia_controller/game, action, datum/mafia_role/target)
+ . = ..()
+ reveal_role(game, TRUE)
+ vote_power = 2
+
+///MAFIA ROLES/// only one until i rework this to allow more, they're the "anti-town" working to kill off townies to win
+
+/datum/mafia_role/mafia
+ name = "Changeling"
+ desc = "You're a member of the changeling hive. Use ':j' talk prefix to talk to your fellow lings."
+ team = MAFIA_TEAM_MAFIA
+ role_type = MAFIA_REGULAR
+ hud_icon = "hudchangeling"
+ revealed_icon = "changeling"
+ // winner_award = /datum/award/achievement/mafia/changeling
+
+ revealed_outfit = /datum/outfit/mafia/changeling
+ special_theme = "syndicate"
+ win_condition = "become majority over the town and no solo killing role can stop them."
+
+/datum/mafia_role/mafia/New(datum/mafia_controller/game)
+ . = ..()
+ RegisterSignal(game,COMSIG_MAFIA_SUNDOWN,.proc/mafia_text)
+
+/datum/mafia_role/mafia/proc/mafia_text(datum/mafia_controller/source)
+ to_chat(body,"Vote for who to kill tonight. The killer will be chosen randomly from voters.")
+
+//better detective for mafia
+/datum/mafia_role/mafia/thoughtfeeder
+ name = "Thoughtfeeder"
+ desc = "You're a changeling variant that feeds on the memories of others. Use ':j' talk prefix to talk to your fellow lings, and visit people at night to learn their role."
+ role_type = MAFIA_SPECIAL
+ hud_icon = "hudthoughtfeeder"
+ revealed_icon = "thoughtfeeder"
+ // winner_award = /datum/award/achievement/mafia/thoughtfeeder
+
+ targeted_actions = list("Learn Role")
+ var/datum/mafia_role/current_investigation
+
+/datum/mafia_role/mafia/thoughtfeeder/New(datum/mafia_controller/game)
+ . = ..()
+ RegisterSignal(game,COMSIG_MAFIA_NIGHT_ACTION_PHASE,.proc/investigate)
+
+/datum/mafia_role/mafia/thoughtfeeder/validate_action_target(datum/mafia_controller/game,action,datum/mafia_role/target)
+ . = ..()
+ if(!.)
+ return
+ return game.phase == MAFIA_PHASE_NIGHT && target.game_status == MAFIA_ALIVE && target != src
+
+/datum/mafia_role/mafia/thoughtfeeder/handle_action(datum/mafia_controller/game,action,datum/mafia_role/target)
+ to_chat(body,"You will feast on the memories of [target.body.real_name] tonight.")
+ current_investigation = target
+
+/datum/mafia_role/mafia/thoughtfeeder/proc/investigate(datum/mafia_controller/game)
+ var/datum/mafia_role/target = current_investigation
+ current_investigation = null
+ if(SEND_SIGNAL(src,COMSIG_MAFIA_CAN_PERFORM_ACTION,game,"thoughtfeed",target) & MAFIA_PREVENT_ACTION)
+ to_chat(body,"You were unable to investigate [target.body.real_name].")
+ add_note("N[game.turn] - [target.body.real_name] - Unable to investigate")
+ return
+ if(target)
+ if(target.detect_immune)
+ to_chat(body,"[target.body.real_name]'s memories reveal that they are the Assistant.")
+ add_note("N[game.turn] - [target.body.real_name] - Assistant")
+ else
+ to_chat(body,"[target.body.real_name]'s memories reveal that they are the [target.name].")
+ add_note("N[game.turn] - [target.body.real_name] - [target.name]")
+
+
+///SOLO ROLES/// they range from anomalous factors to deranged killers that try to win alone.
+
+/datum/mafia_role/traitor
+ name = "Traitor"
+ desc = "You're a solo traitor. You are immune to night kills, can kill every night and you win by outnumbering everyone else."
+ win_condition = "kill everyone."
+ team = MAFIA_TEAM_SOLO
+ role_type = NEUTRAL_KILL
+ // winner_award = /datum/award/achievement/mafia/traitor
+
+ targeted_actions = list("Night Kill")
+ revealed_outfit = /datum/outfit/mafia/traitor
+
+ hud_icon = "hudtraitor"
+ revealed_icon = "traitor"
+ special_theme = "neutral"
+
+ var/datum/mafia_role/current_victim
+
+/datum/mafia_role/traitor/New(datum/mafia_controller/game)
+ . = ..()
+ RegisterSignal(src,COMSIG_MAFIA_ON_KILL,.proc/nightkill_immunity)
+ RegisterSignal(game,COMSIG_MAFIA_NIGHT_KILL_PHASE,.proc/try_to_kill)
+
+/datum/mafia_role/traitor/check_total_victory(alive_town, alive_mafia) //serial killers just want teams dead
+ return alive_town + alive_mafia <= 1
+
+/datum/mafia_role/traitor/block_team_victory(alive_town, alive_mafia) //no team can win until they're dead
+ return TRUE //while alive, town AND mafia cannot win (though since mafia know who is who it's pretty easy to win from that point)
+
+/datum/mafia_role/traitor/proc/nightkill_immunity(datum/source,datum/mafia_controller/game,lynch)
+ if(game.phase == MAFIA_PHASE_NIGHT && !lynch)
+ to_chat(body,"You were attacked, but they'll have to try harder than that to put you down.")
+ return MAFIA_PREVENT_KILL
+
+/datum/mafia_role/traitor/validate_action_target(datum/mafia_controller/game, action, datum/mafia_role/target)
+ . = ..()
+ if(!.)
+ return FALSE
+ if(game.phase != MAFIA_PHASE_NIGHT || target.game_status != MAFIA_ALIVE || target == src)
+ return FALSE
+
+/datum/mafia_role/traitor/handle_action(datum/mafia_controller/game, action, datum/mafia_role/target)
+ . = ..()
+ current_victim = target
+ to_chat(body,"You will attempt to kill [target.body.real_name] tonight.")
+
+/datum/mafia_role/traitor/proc/try_to_kill(datum/mafia_controller/source)
+ var/datum/mafia_role/target = current_victim
+ current_victim = null
+ if(SEND_SIGNAL(src,COMSIG_MAFIA_CAN_PERFORM_ACTION,source,"traitor kill",target) & MAFIA_PREVENT_ACTION)
+ return
+ if(game_status == MAFIA_ALIVE && target && target.game_status == MAFIA_ALIVE)
+ if(!target.kill(source))
+ to_chat(body,"Your attempt at killing [target.body] was prevented!")
+
+/datum/mafia_role/nightmare
+ name = "Nightmare"
+ desc = "You're a solo monster that cannot be detected by detective roles. You can flicker lights of another room each night. You can instead decide to hunt, killing everyone in a flickering room. Kill everyone to win."
+ win_condition = "kill everyone."
+ revealed_outfit = /datum/outfit/mafia/nightmare
+ detect_immune = TRUE
+ team = MAFIA_TEAM_SOLO
+ role_type = NEUTRAL_KILL
+ special_theme = "neutral"
+ hud_icon = "hudnightmare"
+ revealed_icon = "nightmare"
+ // winner_award = /datum/award/achievement/mafia/nightmare
+
+ targeted_actions = list("Flicker", "Hunt")
+ var/list/flickering = list()
+ var/datum/mafia_role/flicker_target
+
+/datum/mafia_role/nightmare/New(datum/mafia_controller/game)
+ . = ..()
+ RegisterSignal(game,COMSIG_MAFIA_NIGHT_KILL_PHASE,.proc/flicker_or_hunt)
+
+/datum/mafia_role/nightmare/check_total_victory(alive_town, alive_mafia) //nightmares just want teams dead
+ return alive_town + alive_mafia <= 1
+
+/datum/mafia_role/nightmare/block_team_victory(alive_town, alive_mafia) //no team can win until they're dead
+ return TRUE //while alive, town AND mafia cannot win (though since mafia know who is who it's pretty easy to win from that point)
+
+/datum/mafia_role/nightmare/special_reveal_equip()
+ body.underwear = "Nude"
+ body.undershirt = "Nude"
+ body.socks = "Nude"
+ body.set_species(/datum/species/shadow)
+ body.update_body()
+
+/datum/mafia_role/nightmare/validate_action_target(datum/mafia_controller/game, action, datum/mafia_role/target)
+ . = ..()
+ if(!. || game.phase != MAFIA_PHASE_NIGHT || target.game_status != MAFIA_ALIVE)
+ return FALSE
+ if(action == "Flicker")
+ return target != src && !(target in flickering)
+ return target == src
+
+/datum/mafia_role/nightmare/handle_action(datum/mafia_controller/game, action, datum/mafia_role/target)
+ . = ..()
+ if(target == flicker_target)
+ to_chat(body,"You will do nothing tonight.")
+ flicker_target = null
+ flicker_target = target
+ if(action == "Flicker")
+ to_chat(body,"You will attempt to flicker [target.body.real_name]'s room tonight.")
+ else
+ to_chat(body,"You will hunt everyone in a flickering room down tonight.")
+
+/datum/mafia_role/nightmare/proc/flicker_or_hunt(datum/mafia_controller/source)
+ if(game_status != MAFIA_ALIVE || !flicker_target)
+ return
+ if(SEND_SIGNAL(src,COMSIG_MAFIA_CAN_PERFORM_ACTION,source,"nightmare actions",flicker_target) & MAFIA_PREVENT_ACTION)
+ to_chat(flicker_target.body, "Your actions were prevented!")
+ return
+ var/datum/mafia_role/target = flicker_target
+ flicker_target = null
+ if(target != src) //flicker instead of hunt
+ to_chat(target.body, "The lights begin to flicker and dim. You're in danger.")
+ flickering += target
+ return
+ for(var/r in flickering)
+ var/datum/mafia_role/role = r
+ if(role && role.game_status == MAFIA_ALIVE)
+ to_chat(role.body, "A shadowy monster appears out of the darkness!")
+ role.kill(source)
+ flickering -= role
+
+//just helps read better
+#define FUGITIVE_NOT_PRESERVING 0//will not become night immune tonight
+#define FUGITIVE_WILL_PRESERVE 1 //will become night immune tonight
+
+/datum/mafia_role/fugitive
+ name = "Fugitive"
+ desc = "You're on the run. You can become immune to night kills exactly twice, and you win by surviving to the end of the game with anyone."
+ win_condition = "survive to the end of the game, with anyone"
+ solo_counts_as_town = TRUE //should not count towards mafia victory, they should have the option to work with town
+ revealed_outfit = /datum/outfit/mafia/fugitive
+ team = MAFIA_TEAM_SOLO
+ role_type = NEUTRAL_DISRUPT
+ special_theme = "neutral"
+ hud_icon = "hudfugitive"
+ revealed_icon = "fugitive"
+ // winner_award = /datum/award/achievement/mafia/fugitive
+
+ actions = list("Self Preservation")
+ var/charges = 2
+ var/protection_status = FUGITIVE_NOT_PRESERVING
+
+
+/datum/mafia_role/fugitive/New(datum/mafia_controller/game)
+ . = ..()
+ RegisterSignal(game,COMSIG_MAFIA_SUNDOWN,.proc/night_start)
+ RegisterSignal(game,COMSIG_MAFIA_NIGHT_END,.proc/night_end)
+ RegisterSignal(game,COMSIG_MAFIA_GAME_END,.proc/survived)
+
+/datum/mafia_role/fugitive/handle_action(datum/mafia_controller/game, action, datum/mafia_role/target)
+ . = ..()
+ if(!charges)
+ to_chat(body,"You're out of supplies and cannot protect yourself anymore.")
+ return
+ if(game.phase == MAFIA_PHASE_NIGHT)
+ to_chat(body,"You don't have time to prepare, night has already arrived.")
+ return
+ if(protection_status == FUGITIVE_WILL_PRESERVE)
+ to_chat(body,"You decide to not prepare tonight.")
+ else
+ to_chat(body,"You decide to prepare for a horrible night.")
+ protection_status = !protection_status
+
+/datum/mafia_role/fugitive/proc/night_start(datum/mafia_controller/game)
+ if(protection_status == FUGITIVE_WILL_PRESERVE)
+ to_chat(body,"Your preparations are complete. Nothing could kill you tonight!")
+ RegisterSignal(src,COMSIG_MAFIA_ON_KILL,.proc/prevent_death)
+
+/datum/mafia_role/fugitive/proc/night_end(datum/mafia_controller/game)
+ if(protection_status == FUGITIVE_WILL_PRESERVE)
+ charges--
+ UnregisterSignal(src,COMSIG_MAFIA_ON_KILL)
+ to_chat(body,"You are no longer protected. You have [charges] use[charges == 1 ? "" : "s"] left of your power.")
+ protection_status = FUGITIVE_NOT_PRESERVING
+
+/datum/mafia_role/fugitive/proc/prevent_death(datum/mafia_controller/game)
+ to_chat(body,"You were attacked! Luckily, you were ready for this!")
+ return MAFIA_PREVENT_KILL
+
+/datum/mafia_role/fugitive/proc/survived(datum/mafia_controller/game)
+ if(game_status == MAFIA_ALIVE)
+ // var/client/winner_client = GLOB.directory[player_key]
+ // winner_client?.give_award(winner_award, body)
+ game.send_message("!! FUGITIVE VICTORY !!")
+
+#undef FUGITIVE_NOT_PRESERVING
+#undef FUGITIVE_WILL_PRESERVE
+
+/datum/mafia_role/obsessed
+ name = "Obsessed"
+ desc = "You're completely lost in your own mind. You win by lynching your obsession before you get killed in this mess. Obsession assigned on the first night!"
+ win_condition = "lynch their obsession."
+ revealed_outfit = /datum/outfit/mafia/obsessed // /mafia <- outfit must be readded (just make a new mafia outfits file for all of these)
+ solo_counts_as_town = TRUE //after winning or whatever, can side with whoever. they've already done their objective!
+ team = MAFIA_TEAM_SOLO
+ role_type = NEUTRAL_DISRUPT
+ special_theme = "neutral"
+ hud_icon = "hudobsessed"
+ revealed_icon = "obsessed"
+
+ // winner_award = /datum/award/achievement/mafia/obsessed
+
+ revealed_outfit = /datum/outfit/mafia/obsessed // /mafia <- outfit must be readded (just make a new mafia outfits file for all of these)
+ solo_counts_as_town = TRUE //after winning or whatever, can side with whoever. they've already done their objective!
+ var/datum/mafia_role/obsession
+ var/lynched_target = FALSE
+
+/datum/mafia_role/obsessed/New(datum/mafia_controller/game) //note: obsession is always a townie
+ . = ..()
+ RegisterSignal(game,COMSIG_MAFIA_SUNDOWN,.proc/find_obsession)
+
+/datum/mafia_role/obsessed/proc/find_obsession(datum/mafia_controller/game)
+ var/list/all_roles_shuffle = shuffle(game.all_roles)
+ for(var/role in all_roles_shuffle)
+ var/datum/mafia_role/possible = role
+ if(possible.team == MAFIA_TEAM_TOWN && possible.game_status != MAFIA_DEAD)
+ obsession = possible
+ break
+ if(!obsession)
+ obsession = pick(all_roles_shuffle) //okay no town just pick anyone here
+ //if you still don't have an obsession you're playing a single player game like i can't help your dumb ass
+ to_chat(body, "Your obsession is [obsession.body.real_name]! Get them lynched to win!")
+ add_note("N[game.turn] - I vowed to watch my obsession, [obsession.body.real_name], hang!") //it'll always be N1 but whatever
+ RegisterSignal(obsession,COMSIG_MAFIA_ON_KILL,.proc/check_victory)
+ UnregisterSignal(game,COMSIG_MAFIA_SUNDOWN)
+
+/datum/mafia_role/obsessed/proc/check_victory(datum/source,datum/mafia_controller/game,lynch)
+ UnregisterSignal(source,COMSIG_MAFIA_ON_KILL)
+ if(game_status == MAFIA_DEAD)
+ return
+ if(lynch)
+ game.send_message("!! OBSESSED VICTORY !!")
+ // var/client/winner_client = GLOB.directory[player_key]
+ // winner_client?.give_award(winner_award, body)
+ reveal_role(game, FALSE)
+ else
+ to_chat(body, "You have failed your objective to lynch [obsession.body]!")
+
+/datum/mafia_role/clown
+ name = "Clown"
+ desc = "If you are lynched you take down one of your voters (guilty or abstain) with you and win. HONK!"
+ win_condition = "get themselves lynched!"
+ revealed_outfit = /datum/outfit/mafia/clown
+ solo_counts_as_town = TRUE
+ team = MAFIA_TEAM_SOLO
+ role_type = NEUTRAL_DISRUPT
+ special_theme = "neutral"
+ hud_icon = "hudclown"
+ revealed_icon = "clown"
+ // winner_award = /datum/award/achievement/mafia/clown
+
+/datum/mafia_role/clown/New(datum/mafia_controller/game)
+ . = ..()
+ RegisterSignal(src,COMSIG_MAFIA_ON_KILL,.proc/prank)
+
+/datum/mafia_role/clown/proc/prank(datum/source,datum/mafia_controller/game,lynch)
+ if(lynch)
+ var/datum/mafia_role/victim = pick(game.judgement_guilty_votes + game.judgement_abstain_votes)
+ game.send_message("[body.real_name] WAS A CLOWN! HONK! They take down [victim.body.real_name] with their last prank.")
+ game.send_message("!! CLOWN VICTORY !!")
+ // var/client/winner_client = GLOB.directory[player_key]
+ // winner_client?.give_award(winner_award, body)
+ victim.kill(game,FALSE)
diff --git a/code/modules/mapping/map_config.dm b/code/modules/mapping/map_config.dm
index efa2655325..c03ef65f43 100644
--- a/code/modules/mapping/map_config.dm
+++ b/code/modules/mapping/map_config.dm
@@ -20,7 +20,7 @@
var/map_file = "BoxStation.dmm"
var/traits = null
- var/space_ruin_levels = 2
+ var/space_ruin_levels = 4
var/space_empty_levels = 1
var/station_ruin_budget = -1 // can be set to manually override the station ruins budget on maps that don't support station ruins, stopping the error from being unable to place the ruins.
diff --git a/code/modules/mapping/minimaps.dm b/code/modules/mapping/minimaps.dm
index 7a62e9ab44..e0eb174cb3 100644
--- a/code/modules/mapping/minimaps.dm
+++ b/code/modules/mapping/minimaps.dm
@@ -4,7 +4,7 @@
// The map icons
var/icon/map_icon
var/icon/meta_icon
-
+
var/list/color_area_names = list()
var/minx
@@ -33,7 +33,7 @@
meta_icon = new('html/blank.png')
map_icon.Scale(x2 - x1 + 1, y2 - y1 + 1) // arrays start at 1
meta_icon.Scale(x2 - x1 + 1, y2 - y1 + 1)
-
+
var/list/area_to_color = list()
for(var/turf/T in block(locate(x1, y1, z_level), locate(x2, y2, z_level)))
var/area/A = T.loc
@@ -44,7 +44,7 @@
crop_x2 = max(crop_x2, T.x)
crop_y1 = min(crop_y1, T.y)
crop_y2 = max(crop_y2, T.y)
-
+
var/meta_color = area_to_color[A]
if(!meta_color)
meta_color = rgb(rand(0, 255), rand(0, 255), rand(0, 255)) // technically conflicts could happen but it's like very unlikely and it's not that big of a deal if one happens
@@ -73,13 +73,15 @@
overlay_icon = new(map_icon)
overlay_icon.Scale(16, 16)
//we're done baking, now we ship it.
- register_asset("minimap-[id].png", map_icon)
- register_asset("minimap-[id]-meta.png", meta_icon)
+ if (!SSassets.cache["minimap-[id].png"])
+ SSassets.transport.register_asset("minimap-[id].png", map_icon)
+ if (!SSassets.cache["minimap-[id]-meta.png"])
+ SSassets.transport.register_asset("minimap-[id]-meta.png", meta_icon)
/datum/minimap/proc/send(mob/user)
if(!id)
CRASH("ERROR: send called, but the minimap id is null/missing. ID: [id]")
- send_asset_list(user, list("minimap-[id].png" = map_icon, "minimap-[id]-meta.png" = meta_icon))
+ SSassets.transport.send_assets(user, list("minimap-[id].png" = map_icon, "minimap-[id]-meta.png" = meta_icon))
/datum/minimap_group
var/list/minimaps = list()
@@ -100,15 +102,17 @@
var/list/datas = list()
var/list/info = list()
-
+
for(var/i in 1 to length(minimaps))// OLD: for(var/i in 1 to length(minimaps))
var/datum/minimap/M = minimaps[i]
+ var/map_name = "minimap-[M.id].png"
+ var/meta_name = "minimap-[M.id]-meta.png"
M.send(user)
info += {"
-
-
+
+
@@ -183,6 +187,4 @@
var/datum/browser/popup = new(user, "minimap_[id]", name, 500, 700)
popup.add_head_content(headerJS) //set the head
popup.set_content(info)
- var/datum/minimap/MICO = minimaps[1]
- popup.set_title_image(MICO.overlay_icon)
popup.open(FALSE)
diff --git a/code/modules/mining/aux_base.dm b/code/modules/mining/aux_base.dm
index 006065d048..b10177ebaf 100644
--- a/code/modules/mining/aux_base.dm
+++ b/code/modules/mining/aux_base.dm
@@ -70,7 +70,6 @@ interface with the mining shuttle at the landing site if a mobile beacon is also
var/datum/browser/popup = new(user, "computer", "base management", 550, 300) //width, height
popup.set_content("
[dat]
")
- popup.set_title_image(usr.browse_rsc_icon(src.icon, src.icon_state))
popup.open()
diff --git a/code/modules/mining/equipment/kinetic_crusher.dm b/code/modules/mining/equipment/kinetic_crusher.dm
index 745c46b131..a6f456ce6f 100644
--- a/code/modules/mining/equipment/kinetic_crusher.dm
+++ b/code/modules/mining/equipment/kinetic_crusher.dm
@@ -17,7 +17,7 @@
custom_materials = list(/datum/material/iron=1150, /datum/material/glass=2075)
hitsound = 'sound/weapons/bladeslice.ogg'
attack_verb = list("smashed", "crushed", "cleaved", "chopped", "pulped")
- sharpness = IS_SHARP
+ sharpness = SHARP_EDGED
actions_types = list(/datum/action/item_action/toggle_light)
var/list/trophies = list()
var/charged = TRUE
@@ -175,6 +175,60 @@
if(light_on)
. += "[icon_state]_lit"
+/obj/item/kinetic_crusher/glaive
+ name = "proto-kinetic glaive"
+ desc = "A modified design of a proto-kinetic crusher, it is still little more of a combination of various mining tools cobbled together \
+ and kit-bashed into a high-tech cleaver on a stick - with a handguard and a goliath hide grip. While it is still of little use to any \
+ but the most skilled and/or suicidal miners against local fauna, it's an elegant weapon for a more civilized hunter."
+ attack_verb = list("stabbed", "diced", "sliced", "cleaved", "chopped", "lacerated", "cut", "jabbed", "punctured")
+ icon_state = "crusher-glaive"
+ item_state = "crusher0-glaive"
+ block_parry_data = /datum/block_parry_data/crusherglaive
+ //ideas: altclick that lets you pummel people with the handguard/handle?
+ //parrying functionality?
+
+/datum/block_parry_data/crusherglaive // small perfect window, active for a fair while, time it right or use the Forbidden Technique
+ parry_time_windup = 0
+ parry_time_active = 8
+ parry_time_spindown = 0
+ parry_time_perfect = 1
+ parry_time_perfect_leeway = 2
+ parry_imperfect_falloff_percent = 20
+ parry_efficiency_to_counterattack = 100 // perfect parry or you're cringe
+ parry_failed_stagger_duration = 1.5 SECONDS // a good time to reconsider your actions...
+ parry_failed_clickcd_duration = 1.5 SECONDS // or your failures
+
+/obj/item/kinetic_crusher/glaive/on_active_parry(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, list/block_return, parry_efficiency, parry_time) // if you're dumb enough to go for a parry...
+ var/turf/proj_turf = owner.loc // destabilizer bolt, ignoring cooldown
+ if(!isturf(proj_turf))
+ return
+ var/obj/item/projectile/destabilizer/D = new /obj/item/projectile/destabilizer(proj_turf)
+ for(var/t in trophies)
+ var/obj/item/crusher_trophy/T = t
+ T.on_projectile_fire(D, owner)
+ D.preparePixelProjectile(attacker, owner)
+ D.firer = owner
+ D.hammer_synced = src
+ playsound(owner, 'sound/weapons/plasma_cutter.ogg', 100, 1)
+ D.fire()
+
+/obj/item/kinetic_crusher/glaive/active_parry_reflex_counter(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, list/return_list, parry_efficiency, list/effect_text)
+ if(owner.Adjacent(attacker) && (!attacker.anchored || ismegafauna(attacker))) // free backstab, if you perfect parry
+ attacker.dir = get_dir(owner,attacker)
+
+/// triggered on wield of two handed item
+/obj/item/kinetic_crusher/glaive/on_wield(obj/item/source, mob/user)
+ wielded = TRUE
+ item_flags |= (ITEM_CAN_PARRY)
+
+/// triggered on unwield of two handed item
+/obj/item/kinetic_crusher/glaive/on_unwield(obj/item/source, mob/user)
+ wielded = FALSE
+ item_flags &= ~(ITEM_CAN_PARRY)
+
+/obj/item/kinetic_crusher/glaive/update_icon_state()
+ item_state = "crusher[wielded]-glaive" // this is not icon_state and not supported by 2hcomponent
+
//destablizing force
/obj/item/projectile/destabilizer
name = "destabilizing force"
diff --git a/code/modules/mining/equipment/mineral_scanner.dm b/code/modules/mining/equipment/mineral_scanner.dm
index 9ccb7c0efc..b2cd840468 100644
--- a/code/modules/mining/equipment/mineral_scanner.dm
+++ b/code/modules/mining/equipment/mineral_scanner.dm
@@ -12,13 +12,14 @@
slot_flags = ITEM_SLOT_BELT
var/cooldown = 35
var/current_cooldown = 0
+ var/range = 7
/obj/item/mining_scanner/attack_self(mob/user)
if(!user.client)
return
if(current_cooldown <= world.time)
current_cooldown = world.time + cooldown
- mineral_scan_pulse(get_turf(user))
+ mineral_scan_pulse(get_turf(user), range)
//Debug item to identify all ore spread quickly
/obj/item/mining_scanner/admin
diff --git a/code/modules/mining/equipment/mining_tools.dm b/code/modules/mining/equipment/mining_tools.dm
index 50a3dec9dd..31ee37c3b0 100644
--- a/code/modules/mining/equipment/mining_tools.dm
+++ b/code/modules/mining/equipment/mining_tools.dm
@@ -66,6 +66,16 @@
force = 19
custom_materials = list(/datum/material/diamond=4000)
+/obj/item/pickaxe/rosegold
+ name = "rose gold pickaxe"
+ icon_state = "rgpickaxe"
+ item_state = "rgpickaxe"
+ toolspeed = 0.1
+ desc = "A pickaxe with a light rose gold head and some red glowing runes. Extremely robust at cracking rock walls and digging up dirt."
+ force = 19
+ custom_materials = list(/datum/material/gold=4000)
+ digrange = 3
+
/obj/item/pickaxe/plasteel
name = "plasteel-tipped pickaxe"
icon_state = "titaxe"
@@ -143,7 +153,7 @@
w_class = WEIGHT_CLASS_NORMAL
custom_materials = list(/datum/material/iron=350)
attack_verb = list("bashed", "bludgeoned", "thrashed", "whacked")
- sharpness = IS_SHARP
+ sharpness = SHARP_EDGED
/obj/item/shovel/Initialize()
. = ..()
@@ -181,4 +191,4 @@
w_class = WEIGHT_CLASS_NORMAL
toolspeed = 0.7
attack_verb = list("slashed", "impaled", "stabbed", "sliced")
- sharpness = IS_SHARP
+ sharpness = SHARP_EDGED
diff --git a/code/modules/mining/equipment/wormhole_jaunter.dm b/code/modules/mining/equipment/wormhole_jaunter.dm
index c31008fa62..e9f8079400 100644
--- a/code/modules/mining/equipment/wormhole_jaunter.dm
+++ b/code/modules/mining/equipment/wormhole_jaunter.dm
@@ -18,6 +18,15 @@
SSblackbox.record_feedback("tally", "jaunter", 1, "User") // user activated
activate(user, TRUE)
+/obj/item/wormhole_jaunter/equipped(mob/user, slot)
+ . = ..()
+ if(slot == SLOT_BELT)
+ RegisterSignal(user, COMSIG_MOVABLE_CHASM_DROP, .proc/chasm_react)
+
+/obj/item/wormhole_jaunter/dropped(mob/user)
+ . = ..()
+ UnregisterSignal(user, COMSIG_MOVABLE_CHASM_DROP)
+
/obj/item/wormhole_jaunter/proc/turf_check(mob/user)
var/turf/device_turf = get_turf(user)
if(!device_turf || is_centcom_level(device_turf.z) || is_reserved_level(device_turf.z))
@@ -35,18 +44,20 @@
return destinations
-/obj/item/wormhole_jaunter/proc/activate(mob/user, adjacent)
+/obj/item/wormhole_jaunter/proc/activate(mob/user, adjacent, force_entry = FALSE)
if(!turf_check(user))
return
var/list/L = get_destinations(user)
if(!L.len)
- to_chat(user, "The [src.name] found no beacons in the world to anchor a wormhole to.")
+ to_chat(user, "The [name] found no beacons in the world to anchor a wormhole to.")
return
var/chosen_beacon = pick(L)
- var/obj/effect/portal/jaunt_tunnel/J = new (get_turf(src), src, 100, null, FALSE, get_turf(chosen_beacon))
+ var/obj/effect/portal/jaunt_tunnel/J = new (get_turf(src), 100, null, FALSE, get_turf(chosen_beacon))
if(adjacent)
try_move_adjacent(J)
+ if(force_entry)
+ J.teleport(user, force = TRUE)
playsound(src,'sound/effects/sparks4.ogg',50,1)
qdel(src)
@@ -69,13 +80,14 @@
SSblackbox.record_feedback("tally", "jaunter", 1, "EMP") // EMP accidental activation
activate(M)
-/obj/item/wormhole_jaunter/proc/chasm_react(mob/user)
- if(user.get_item_by_slot(SLOT_BELT) == src)
- to_chat(user, "Your [name] activates, saving you from the chasm!")
- SSblackbox.record_feedback("tally", "jaunter", 1, "Chasm") // chasm automatic activation
- activate(user, FALSE)
- else
- to_chat(user, "[src] is not attached to your belt, preventing it from saving you from the chasm. RIP.")
+/obj/item/wormhole_jaunter/proc/chasm_react(mob/source, datum/component/chasm/C)
+ to_chat(source, "Your [name] activates, saving you from the chasm!")
+ SSblackbox.record_feedback("tally", "jaunter", 1, "Chasm") // chasm automatic activation
+ activate(source, FALSE, TRUE)
+ if(C)
+ var/atom/A = C.parent
+ A.visible_message("[source] falls into [A]!")
+ return TRUE
//jaunter tunnel
/obj/effect/portal/jaunt_tunnel
@@ -84,9 +96,10 @@
icon_state = "bhole3"
desc = "A stable hole in the universe made by a wormhole jaunter. Turbulent doesn't even begin to describe how rough passage through one of these is, but at least it will always get you somewhere near a beacon."
mech_sized = TRUE //save your ripley
+ teleport_channel = TELEPORT_CHANNEL_WORMHOLE
innate_accuracy_penalty = 6
-/obj/effect/portal/jaunt_tunnel/teleport(atom/movable/M)
+/obj/effect/portal/jaunt_tunnel/teleport(atom/movable/M, force = FALSE)
. = ..()
if(.)
// KERPLUNK
diff --git a/code/modules/mining/laborcamp/laborstacker.dm b/code/modules/mining/laborcamp/laborstacker.dm
index 4d0b7dff21..429dc98e8a 100644
--- a/code/modules/mining/laborcamp/laborstacker.dm
+++ b/code/modules/mining/laborcamp/laborstacker.dm
@@ -8,8 +8,6 @@ GLOBAL_LIST(labor_sheet_values)
icon = 'icons/obj/machines/mining_machines.dmi'
icon_state = "console"
density = FALSE
- ui_x = 315
- ui_y = 430
var/obj/machinery/mineral/stacking_machine/laborstacker/stacking_machine = null
var/machinedir = SOUTH
@@ -35,11 +33,10 @@ GLOBAL_LIST(labor_sheet_values)
/proc/cmp_sheet_list(list/a, list/b)
return a["value"] - b["value"]
-/obj/machinery/mineral/labor_claim_console/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \
- datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state)
- ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+/obj/machinery/mineral/labor_claim_console/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
- ui = new(user, src, ui_key, "LaborClaimConsole", name, ui_x, ui_y, master_ui, state)
+ ui = new(user, src, "LaborClaimConsole", name)
ui.open()
/obj/machinery/mineral/labor_claim_console/ui_data(mob/user)
@@ -121,7 +118,7 @@ GLOBAL_LIST(labor_sheet_values)
/obj/machinery/mineral/stacking_machine/laborstacker
force_connect = TRUE
var/points = 0 //The unclaimed value of ore stacked.
-
+ //damage_deflection = 21
/obj/machinery/mineral/stacking_machine/laborstacker/process_sheet(obj/item/stack/sheet/inp)
points += inp.point_value * inp.amount
..()
diff --git a/code/modules/mining/lavaland/necropolis_chests.dm b/code/modules/mining/lavaland/necropolis_chests.dm
index 270d3601fd..82492052a9 100644
--- a/code/modules/mining/lavaland/necropolis_chests.dm
+++ b/code/modules/mining/lavaland/necropolis_chests.dm
@@ -7,81 +7,199 @@
icon_state = "necrocrate"
resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF
+/obj/structure/closet/crate/necropolis/tendril/random
+ name = "necropolis crate"
+ desc = "A chest for a chest, a head for a head."
+
+/obj/structure/closet/crate/necropolis/tendril/random/PopulateContents()
+ var/loot = rand(1,3)
+ switch(loot)
+ if(1)
+ new /obj/structure/closet/crate/necropolis/tendril/magic(src)
+ if(2)
+ new /obj/structure/closet/crate/necropolis/tendril/weapon_armor(src)
+ if(3)
+ new /obj/structure/closet/crate/necropolis/tendril/misc(src)
+
/obj/structure/closet/crate/necropolis/tendril
desc = "It's watching you suspiciously."
-/obj/structure/closet/crate/necropolis/tendril/PopulateContents()
+/obj/structure/closet/crate/necropolis/tendril/magic
+ name = "relic necropolis chest"
+
+/obj/structure/closet/crate/necropolis/tendril/weapon_armor
+ name = "armament necropolis chest"
+
+/obj/structure/closet/crate/necropolis/tendril/misc
+
+/obj/structure/closet/crate/necropolis/tendril/all
+ desc = "It's watching you suspiciously."
+
+/obj/structure/closet/crate/necropolis/tendril/magic/PopulateContents()
+ var/loot = rand(1,10)
+ switch(loot)
+ if(1)
+ new /obj/item/soulstone/anybody(src)
+ if(2)
+ new /obj/item/rod_of_asclepius(src)
+ if(3)
+ new /obj/item/organ/heart/cursed/wizard(src)
+ if(4)
+ new /obj/item/book/granter/spell/summonitem(src)
+ if(5)
+ new /obj/item/borg/upgrade/modkit/lifesteal(src)
+ new /obj/item/bedsheet/cult(src)
+ if(6)
+ new /obj/item/clothing/neck/necklace/memento_mori(src)
+ if(7)
+ new /obj/item/warp_cube/red(src)
+ if(8)
+ new /obj/item/immortality_talisman(src)
+ if(9)
+ new /obj/item/gun/magic/wand/book/healing(src)
+ if(10)
+ new /obj/item/reagent_containers/glass/bottle/ichor/red(src)
+ new /obj/item/reagent_containers/glass/bottle/ichor/blue(src)
+ new /obj/item/reagent_containers/glass/bottle/ichor/green(src)
+
+/obj/structure/closet/crate/necropolis/tendril/weapon_armor/PopulateContents()
+ var/loot = rand(1,11)
+ switch(loot)
+ if(1)
+ new /obj/item/clothing/suit/space/hardsuit/cult(src)
+ if(2)
+ new /obj/item/katana/lavaland(src)
+ if(3)
+ if(prob(50))
+ new /obj/item/disk/design_disk/modkit_disc/resonator_blast(src)
+ else
+ new /obj/item/disk/design_disk/modkit_disc/rapid_repeater(src)
+ if(4)
+ new /obj/item/clothing/suit/space/hardsuit/ert/paranormal/beserker/old(src)
+ if(5)
+ new /obj/item/nullrod/scythe/talking(src)
+ if(6)
+ new /obj/item/nullrod/armblade(src)
+ if(7)
+ new /obj/item/reagent_containers/food/drinks/bottle/holywater/hell(src)
+ new /obj/item/clothing/suit/space/hardsuit/ert/paranormal/inquisitor/old(src)
+ if(8)
+ new /obj/item/grenade/clusterbuster/inferno(src)
+ if(9)
+ new /obj/item/gun/magic/wand/book/shock(src)
+ if(10)
+ new /obj/item/gun/magic/wand/book/page(src)
+ if(11)
+ new /obj/item/gun/magic/wand/book/spark(src)
+
+/obj/structure/closet/crate/necropolis/tendril/misc/PopulateContents()
+ var/loot = rand(1,14)
+ switch(loot)
+ if(1)
+ new /obj/item/shared_storage/red(src)
+ if(2)
+ new /obj/item/reagent_containers/glass/bottle/potion/flight(src)
+ if(3)
+ new /obj/item/ship_in_a_bottle(src)
+ if(4)
+ new /obj/item/voodoo(src)
+ if(5)
+ new /obj/item/book_of_babel(src)
+ if(6)
+ new /obj/item/jacobs_ladder(src)
+ if(7)
+ if(prob(50))
+ new /obj/item/disk/design_disk/modkit_disc/mob_and_turf_aoe(src)
+ else
+ new /obj/item/disk/design_disk/modkit_disc/bounty(src)
+ if(8)
+ new /obj/item/wisp_lantern(src)
+ if(9)
+ new /obj/item/pickaxe/rosegold(src)
+ if(10)
+ new /obj/item/bedsheet/cosmos(src)
+ new /obj/item/melee/skateboard/hoverboard(src)
+ if(11)
+ new /obj/item/disk/tech_disk/illegal(src)
+ if(12)
+ new /obj/item/clothing/suit/space/hardsuit/cult(src)
+ if(13)
+ new /obj/item/katana/lavaland(src)
+ if(14)
+ if(prob(50))
+ new /obj/item/disk/design_disk/modkit_disc/resonator_blast(src)
+ else
+ new /obj/item/disk/design_disk/modkit_disc/rapid_repeater(src)
+
+/obj/structure/closet/crate/necropolis/tendril/all/PopulateContents()
var/loot = rand(1,29)
switch(loot)
if(1)
new /obj/item/shared_storage/red(src)
if(2)
- new /obj/item/clothing/suit/space/hardsuit/cult(src)
- if(3)
- new /obj/item/soulstone/anybody(src)
- if(4)
- new /obj/item/katana/cursed(src)
- if(5)
- new /obj/item/clothing/glasses/godeye(src)
- if(6)
new /obj/item/reagent_containers/glass/bottle/potion/flight(src)
- if(7)
- new /obj/item/pickaxe/diamond(src)
- if(8)
- if(prob(50))
- new /obj/item/disk/design_disk/modkit_disc/resonator_blast(src)
- else
- new /obj/item/disk/design_disk/modkit_disc/rapid_repeater(src)
- if(9)
- new /obj/item/rod_of_asclepius(src)
- if(10)
- new /obj/item/organ/heart/cursed/wizard(src)
- if(11)
+ if(3)
new /obj/item/ship_in_a_bottle(src)
- if(12)
- new /obj/item/clothing/suit/space/hardsuit/ert/paranormal/beserker/old(src)
- if(13)
+ if(4)
+ new /obj/item/voodoo(src)
+ if(5)
+ new /obj/item/book_of_babel(src)
+ if(6)
new /obj/item/jacobs_ladder(src)
- if(14)
- new /obj/item/nullrod/scythe/talking(src)
- if(15)
- new /obj/item/nullrod/armblade(src)
- if(16)
- new /obj/item/guardiancreator(src)
- if(17)
+ if(7)
if(prob(50))
new /obj/item/disk/design_disk/modkit_disc/mob_and_turf_aoe(src)
else
new /obj/item/disk/design_disk/modkit_disc/bounty(src)
- if(18)
- new /obj/item/warp_cube/red(src)
- if(19)
+ if(8)
new /obj/item/wisp_lantern(src)
- if(20)
- new /obj/item/immortality_talisman(src)
- if(21)
- new /obj/item/gun/magic/hook(src)
- if(22)
- new /obj/item/voodoo(src)
- if(23)
- new /obj/item/grenade/clusterbuster/inferno(src)
- if(24)
+ if(9)
+ new /obj/item/pickaxe/rosegold(src)
+ if(10)
+ new /obj/item/bedsheet/cosmos(src)
+ new /obj/item/melee/skateboard/hoverboard(src)
+ if(11)
+ new /obj/item/disk/tech_disk/illegal(src)
+ if(12)
+ new /obj/item/clothing/suit/space/hardsuit/ert/paranormal/beserker/old(src)
+ if(13)
+ new /obj/item/nullrod/scythe/talking(src)
+ if(14)
+ new /obj/item/nullrod/armblade(src)
+ if(15)
new /obj/item/reagent_containers/food/drinks/bottle/holywater/hell(src)
new /obj/item/clothing/suit/space/hardsuit/ert/paranormal/inquisitor/old(src)
- if(25)
+ if(16)
+ new /obj/item/grenade/clusterbuster/inferno(src)
+ if(17)
+ new /obj/item/gun/magic/wand/book/shock(src)
+ if(18)
+ new /obj/item/gun/magic/wand/book/page(src)
+ if(19)
+ new /obj/item/gun/magic/wand/book/spark(src)
+ if(20)
+ new /obj/item/soulstone/anybody(src)
+ if(21)
+ new /obj/item/rod_of_asclepius(src)
+ if(22)
+ new /obj/item/organ/heart/cursed/wizard(src)
+ if(23)
new /obj/item/book/granter/spell/summonitem(src)
- if(26)
- new /obj/item/book_of_babel(src)
- if(27)
+ if(24)
new /obj/item/borg/upgrade/modkit/lifesteal(src)
new /obj/item/bedsheet/cult(src)
- if(28)
+ if(25)
new /obj/item/clothing/neck/necklace/memento_mori(src)
+ if(26)
+ new /obj/item/warp_cube/red(src)
+ if(27)
+ new /obj/item/immortality_talisman(src)
+ if(28)
+ new /obj/item/gun/magic/wand/book/healing(src)
if(29)
- if(prob(50))
- new /obj/item/malf_upgrade(src)
- else
- new /obj/item/disk/tech_disk/illegal(src)
+ new /obj/item/reagent_containers/glass/bottle/ichor/red(src)
+ new /obj/item/reagent_containers/glass/bottle/ichor/blue(src)
+ new /obj/item/reagent_containers/glass/bottle/ichor/green(src)
//KA modkit design discs
/obj/item/disk/design_disk/modkit_disc
@@ -659,7 +777,7 @@
hitsound = 'sound/weapons/bladeslice.ogg'
hitsound_on = 'sound/weapons/bladeslice.ogg'
w_class = WEIGHT_CLASS_BULKY
- sharpness = IS_SHARP
+ sharpness = SHARP_EDGED
faction_bonus_force = 30
nemesis_factions = list("mining", "boss")
var/transform_cooldown
@@ -747,7 +865,7 @@
new /obj/item/lava_staff(src)
if(3)
new /obj/item/book/granter/spell/sacredflame(src)
- new /obj/item/gun/magic/wand/fireball(src)
+ new /obj/item/gun/magic/hook(src)
if(4)
new /obj/item/dragons_blood(src)
@@ -766,7 +884,7 @@
lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi'
righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi'
flags_1 = CONDUCT_1
- sharpness = IS_SHARP
+ sharpness = SHARP_EDGED
w_class = WEIGHT_CLASS_BULKY
force = 1
throwforce = 1
@@ -930,6 +1048,9 @@
/obj/item/lava_staff/afterattack(atom/target, mob/user, proximity_flag, click_parameters)
. = ..()
+ INVOKE_ASYNC(src, .proc/attempt_lava, target, user, proximity_flag, click_parameters)
+
+/obj/item/lava_staff/proc/attempt_lava(atom/target, mob/user, proximity_flag, click_parameters)
if(timer > world.time)
return
@@ -983,7 +1104,7 @@
if(2)
new /obj/item/gun/ballistic/revolver/doublebarrel/super(src)
if(3)
- new /obj/item/gun/magic/staff/spellblade(src)
+ new /obj/item/guardiancreator(src)
/obj/structure/closet/crate/necropolis/bubblegum/crusher
name = "bloody bubblegum chest"
@@ -1072,6 +1193,7 @@
var/random_crystal = pick(choices)
new random_crystal(src)
new /obj/item/organ/vocal_cords/colossus(src)
+ new /obj/item/clothing/glasses/godeye(src)
/obj/structure/closet/crate/necropolis/colossus/crusher
name = "angelic colossus chest"
diff --git a/code/modules/mining/machine_redemption.dm b/code/modules/mining/machine_redemption.dm
index bae0e94032..0268d32a6b 100644
--- a/code/modules/mining/machine_redemption.dm
+++ b/code/modules/mining/machine_redemption.dm
@@ -195,10 +195,10 @@
to_chat(user, "You change [src]'s I/O settings, setting the input to [dir2text(input_dir)] and the output to [dir2text(output_dir)].")
return TRUE
-/obj/machinery/mineral/ore_redemption/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state)
- ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+/obj/machinery/mineral/ore_redemption/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
- ui = new(user, src, ui_key, "OreRedemptionMachine", "Ore Redemption Machine", 440, 550, master_ui, state)
+ ui = new(user, src, "OreRedemptionMachine")
ui.open()
/obj/machinery/mineral/ore_redemption/ui_data(mob/user)
diff --git a/code/modules/mining/machine_vending.dm b/code/modules/mining/machine_vending.dm
index b00e291685..2cbb965ef5 100644
--- a/code/modules/mining/machine_vending.dm
+++ b/code/modules/mining/machine_vending.dm
@@ -7,8 +7,6 @@
icon_state = "mining"
density = TRUE
circuit = /obj/item/circuitboard/machine/mining_equipment_vendor
- ui_x = 425
- ui_y = 600
var/icon_deny = "mining-deny"
var/obj/item/card/id/inserted_id
var/list/prize_list = list( //if you add something to this, please, for the love of god, sort it by price/type. use tabs and not spaces.
@@ -32,7 +30,7 @@
new /datum/data/mining_equipment("500 Point Transfer Card", /obj/item/card/mining_point_card/mp500, 500),
new /datum/data/mining_equipment("Tracking Implant Kit", /obj/item/storage/box/minertracker, 600),
new /datum/data/mining_equipment("Jaunter", /obj/item/wormhole_jaunter, 750),
- new /datum/data/mining_equipment("Kinetic Crusher", /obj/item/kinetic_crusher, 750),
+ new /datum/data/mining_equipment("Kinetic Crusher", /obj/item/kinetic_crusher, 750),
new /datum/data/mining_equipment("Kinetic Accelerator", /obj/item/gun/energy/kinetic_accelerator, 750),
new /datum/data/mining_equipment("Survival Medipen", /obj/item/reagent_containers/hypospray/medipen/survival, 750),
new /datum/data/mining_equipment("Brute First-Aid Kit", /obj/item/storage/firstaid/brute, 800),
@@ -73,7 +71,8 @@
new /datum/data/mining_equipment("KA Cooldown Decrease", /obj/item/borg/upgrade/modkit/cooldown, 1000),
new /datum/data/mining_equipment("KA AoE Damage", /obj/item/borg/upgrade/modkit/aoe/mobs, 2000),
new /datum/data/mining_equipment("Miner Full Replacement", /obj/item/storage/backpack/duffelbag/mining_cloned, 3000),
- new /datum/data/mining_equipment("Premium Accelerator", /obj/item/gun/energy/kinetic_accelerator/premiumka, 8000)
+ new /datum/data/mining_equipment("Premium Accelerator", /obj/item/gun/energy/kinetic_accelerator/premiumka, 8000),
+ new /datum/data/mining_equipment("Kinetic Glaive", /obj/item/kinetic_crusher/glaive, 2250),
)
/datum/data/mining_equipment
@@ -101,17 +100,15 @@
else
icon_state = "[initial(icon_state)]-off"
-/obj/machinery/mineral/equipment_vendor/ui_base_html(html)
- var/datum/asset/spritesheet/assets = get_asset_datum(/datum/asset/spritesheet/vending)
- . = replacetext(html, "", assets.css_tag())
+/obj/machinery/mineral/equipment_vendor/ui_assets(mob/user)
+ return list(
+ get_asset_datum(/datum/asset/spritesheet/vending),
+ )
-/obj/machinery/mineral/equipment_vendor/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \
- datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state)
- ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+/obj/machinery/mineral/equipment_vendor/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
- var/datum/asset/assets = get_asset_datum(/datum/asset/spritesheet/vending)
- assets.send(user)
- ui = new(user, src, ui_key, "MiningVendor", name, ui_x, ui_y, master_ui, state)
+ ui = new(user, src, "MiningVendor", name)
ui.open()
/obj/machinery/mineral/equipment_vendor/ui_static_data(mob/user)
@@ -136,10 +133,12 @@
if(C)
.["user"] = list()
.["user"]["points"] = C.mining_points
- if(C.assignment)
- .["user"]["job"] = C.assignment
- else
- .["user"]["job"] = "No Job"
+ if(C.registered_account)
+ .["user"]["name"] = C.registered_account.account_holder
+ if(C.registered_account.account_job)
+ .["user"]["job"] = C.registered_account.account_job.title
+ else
+ .["user"]["job"] = "No Job"
/obj/machinery/mineral/equipment_vendor/ui_act(action, params)
if(..())
diff --git a/code/modules/mining/satchel_ore_boxdm.dm b/code/modules/mining/satchel_ore_boxdm.dm
index ae42ca4745..36da9d5db9 100644
--- a/code/modules/mining/satchel_ore_boxdm.dm
+++ b/code/modules/mining/satchel_ore_boxdm.dm
@@ -9,9 +9,6 @@
density = TRUE
pressure_resistance = 5*ONE_ATMOSPHERE
- var/ui_x = 335
- var/ui_y = 415
-
/obj/structure/ore_box/attackby(obj/item/W, mob/user, params)
if (istype(W, /obj/item/stack/ore))
user.transferItemToLoc(W, src)
@@ -58,11 +55,10 @@
stoplag()
drop = drop_location()
-/obj/structure/ore_box/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \
- datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state)
- ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+/obj/structure/ore_box/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
- ui = new(user, src, ui_key, "OreBox", name, ui_x, ui_y, master_ui, state)
+ ui = new(user, src, "OreBox", name)
ui.open()
/obj/structure/ore_box/ui_data()
diff --git a/code/modules/mob/dead/new_player/new_player.dm b/code/modules/mob/dead/new_player/new_player.dm
index 49a3bffd42..312449064f 100644
--- a/code/modules/mob/dead/new_player/new_player.dm
+++ b/code/modules/mob/dead/new_player/new_player.dm
@@ -414,6 +414,8 @@
give_magic(humanc)
if(GLOB.curse_of_madness_triggered)
give_madness(humanc, GLOB.curse_of_madness_triggered)
+ if(humanc.client)
+ humanc.client.prefs.post_copy_to(humanc)
GLOB.joined_player_list += character.ckey
GLOB.latejoiners += character
@@ -557,7 +559,6 @@
if(frn)
client.prefs.random_character()
client.prefs.real_name = client.prefs.pref_species.random_name(gender,1)
- client.prefs.copy_to(H)
var/cur_scar_index = client.prefs.scars_index
if(client.prefs.persistent_scars && client.prefs.scars_list["[cur_scar_index]"])
var/scar_string = client.prefs.scars_list["[cur_scar_index]"]
@@ -568,8 +569,7 @@
client.prefs.scars_list["[cur_scar_index]"] = valid_scars
client.prefs.save_character()
-
- client.prefs.copy_to(H)
+ client.prefs.copy_to(H, initial_spawn = TRUE)
H.dna.update_dna_identity()
if(mind)
if(transfer_after)
diff --git a/code/modules/mob/dead/new_player/preferences_setup.dm b/code/modules/mob/dead/new_player/preferences_setup.dm
index 9a0d9f9242..b77fae68c8 100644
--- a/code/modules/mob/dead/new_player/preferences_setup.dm
+++ b/code/modules/mob/dead/new_player/preferences_setup.dm
@@ -41,7 +41,7 @@
var/mob/living/carbon/human/dummy/mannequin = generate_or_wait_for_human_dummy(DUMMY_HUMAN_SLOT_PREFERENCES)
// Apply the Dummy's preview background first so we properly layer everything else on top of it.
mannequin.add_overlay(mutable_appearance('modular_citadel/icons/ui/backgrounds.dmi', bgstate, layer = SPACE_LAYER))
- copy_to(mannequin)
+ copy_to(mannequin, initial_spawn = TRUE)
if(previewJob && equip_job)
mannequin.job = previewJob.title
diff --git a/code/modules/mob/dead/new_player/sprite_accessories/Citadel_Snowflake.dm b/code/modules/mob/dead/new_player/sprite_accessories/Citadel_Snowflake.dm
index 020776a75f..3c2c850e3c 100644
--- a/code/modules/mob/dead/new_player/sprite_accessories/Citadel_Snowflake.dm
+++ b/code/modules/mob/dead/new_player/sprite_accessories/Citadel_Snowflake.dm
@@ -1,9 +1,9 @@
-/datum/sprite_accessory/mam_tails/shark/datashark
+/datum/sprite_accessory/tails/mam_tails/shark/datashark
name = "DataShark"
icon_state = "datashark"
ckeys_allowed = list("rubyflamewing")
-/datum/sprite_accessory/mam_tails_animated/shark/datashark
+/datum/sprite_accessory/tails_animated/mam_tails_animated/shark/datashark
name = "DataShark"
icon_state = "datashark"
ckeys_allowed = list("rubyflamewing")
@@ -14,19 +14,19 @@
ckeys_allowed = list("rubyflamewing")
//Sabresune
-/datum/sprite_accessory/mam_ears/sabresune
+/datum/sprite_accessory/ears/mam_ears/sabresune
name = "Sabresune"
icon_state = "sabresune"
ckeys_allowed = list("poojawa")
extra = TRUE
extra_color_src = MUTCOLORS3
-/datum/sprite_accessory/mam_tails/sabresune
+/datum/sprite_accessory/tails/mam_tails/sabresune
name = "Sabresune"
icon_state = "sabresune"
ckeys_allowed = list("poojawa")
-/datum/sprite_accessory/mam_tails_animated/sabresune
+/datum/sprite_accessory/tails_animated/mam_tails_animated/sabresune
name = "Sabresune"
icon_state = "sabresune"
ckeys_allowed = list("poojawa")
@@ -37,17 +37,17 @@
ckeys_allowed = list("poojawa")
//Lunasune
-/datum/sprite_accessory/mam_ears/lunasune
+/datum/sprite_accessory/ears/mam_ears/lunasune
name = "lunasune"
icon_state = "lunasune"
ckeys_allowed = list("invader4352")
-/datum/sprite_accessory/mam_tails/lunasune
+/datum/sprite_accessory/tails/mam_tails/lunasune
name = "lunasune"
icon_state = "lunasune"
ckeys_allowed = list("invader4352")
-/datum/sprite_accessory/mam_tails_animated/lunasune
+/datum/sprite_accessory/tails_animated/mam_tails_animated/lunasune
name = "lunasune"
icon_state = "lunasune"
ckeys_allowed = list("invader4352")
diff --git a/code/modules/mob/dead/new_player/sprite_accessories/_sprite_accessories.dm b/code/modules/mob/dead/new_player/sprite_accessories/_sprite_accessories.dm
index 6514cb4f80..4cb8d080ff 100644
--- a/code/modules/mob/dead/new_player/sprite_accessories/_sprite_accessories.dm
+++ b/code/modules/mob/dead/new_player/sprite_accessories/_sprite_accessories.dm
@@ -76,6 +76,9 @@
//For soft-restricting markings to species IDs
var/list/recommended_species
+/datum/sprite_accessory/proc/is_not_visible(var/mob/living/carbon/human/H, var/tauric) //return if the accessory shouldn't be shown
+ return FALSE
+
/datum/sprite_accessory/underwear
icon = 'icons/mob/clothing/underwear.dmi'
var/has_color = FALSE
diff --git a/code/modules/mob/dead/new_player/sprite_accessories/alienpeople.dm b/code/modules/mob/dead/new_player/sprite_accessories/alienpeople.dm
index c8e7aca26d..d1f7f15ac9 100644
--- a/code/modules/mob/dead/new_player/sprite_accessories/alienpeople.dm
+++ b/code/modules/mob/dead/new_player/sprite_accessories/alienpeople.dm
@@ -7,18 +7,21 @@
mutant_part_string = "xenodorsal"
relevant_layers = list(BODY_BEHIND_LAYER, BODY_FRONT_LAYER)
+/datum/sprite_accessory/xeno_dorsal/is_not_visible(var/mob/living/carbon/human/H, var/tauric)
+ return (!H.dna.features["xenodorsal"] || H.dna.features["xenodorsal"] == "None" || (H.wear_suit && (H.wear_suit.flags_inv & HIDEJUMPSUIT)))
+
/datum/sprite_accessory/xeno_dorsal/standard
name = "Standard"
icon_state = "standard"
-/datum/sprite_accessory/xeno_dorsal/royal
- name = "Royal"
- icon_state = "royal"
-
/datum/sprite_accessory/xeno_dorsal/down
name = "Dorsal Down"
icon_state = "down"
+/datum/sprite_accessory/xeno_dorsal/royal
+ name = "Royal"
+ icon_state = "royal"
+
/******************************************
************* Xeno Tails ******************
*******************************************/
@@ -27,6 +30,9 @@
mutant_part_string = "tail"
relevant_layers = list(BODY_BEHIND_LAYER, BODY_FRONT_LAYER)
+/datum/sprite_accessory/xeno_tail/is_not_visible(var/mob/living/carbon/human/H, var/tauric)
+ return (!H.dna.features["xenotail"] || H.dna.features["xenotail"] == "None" || H.wear_suit && (H.wear_suit.flags_inv & HIDEJUMPSUIT))
+
/datum/sprite_accessory/xeno_tail/none
name = "None"
relevant_layers = null
@@ -43,18 +49,22 @@
mutant_part_string = "xhead"
relevant_layers = list(BODY_ADJ_LAYER)
+/datum/sprite_accessory/xeno_head/is_not_visible(var/mob/living/carbon/human/H, var/tauric)
+ var/obj/item/bodypart/head/HD = H.get_bodypart(BODY_ZONE_HEAD)
+ return (!H.dna.features["xenohead"] || H.dna.features["xenohead"] == "None" || H.head && (H.head.flags_inv & HIDEHAIR) || (H.wear_mask && (H.wear_mask.flags_inv & HIDEHAIR)) || !HD || HD.status == BODYPART_ROBOTIC)
+
/datum/sprite_accessory/xeno_head/standard
name = "Standard"
icon_state = "standard"
-/datum/sprite_accessory/xeno_head/royal
- name = "royal"
- icon_state = "royal"
-
/datum/sprite_accessory/xeno_head/hollywood
name = "hollywood"
icon_state = "hollywood"
+/datum/sprite_accessory/xeno_head/royal
+ name = "royal"
+ icon_state = "royal"
+
/datum/sprite_accessory/xeno_head/warrior
name = "warrior"
icon_state = "warrior"
diff --git a/code/modules/mob/dead/new_player/sprite_accessories/body_markings.dm b/code/modules/mob/dead/new_player/sprite_accessories/body_markings.dm
index 9950f0d76a..5286acb33a 100644
--- a/code/modules/mob/dead/new_player/sprite_accessories/body_markings.dm
+++ b/code/modules/mob/dead/new_player/sprite_accessories/body_markings.dm
@@ -16,6 +16,12 @@
icon_state = "dtiger"
gender_specific = 1
+/datum/sprite_accessory/body_markings/guilmon
+ name = "Guilmon"
+ icon_state = "guilmon"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/markings_notmammals.dmi'
+
/datum/sprite_accessory/body_markings/ltiger
name = "Light Tiger Body"
icon_state = "ltiger"
@@ -49,11 +55,6 @@
icon = 'modular_citadel/icons/mob/markings_notmammals.dmi'
relevant_layers = null
-/datum/sprite_accessory/mam_body_markings/plain
- name = "Plain"
- icon_state = "plain"
- icon = 'modular_citadel/icons/mob/markings_notmammals.dmi'
-
/datum/sprite_accessory/mam_body_markings/redpanda
name = "Redpanda"
icon_state = "redpanda"
@@ -77,14 +78,14 @@
icon_state = "bellyslim"
icon = 'modular_citadel/icons/mob/markings_notmammals.dmi'
-/datum/sprite_accessory/mam_body_markings/corgi
- name = "Corgi"
- icon_state = "corgi"
-
/datum/sprite_accessory/mam_body_markings/cow
name = "Bovine"
icon_state = "bovine"
+/datum/sprite_accessory/mam_body_markings/corgi
+ name = "Corgi"
+ icon_state = "corgi"
+
/datum/sprite_accessory/mam_body_markings/corvid
name = "Corvid"
icon_state = "corvid"
@@ -139,15 +140,19 @@
name = "Hyena"
icon_state = "hyena"
-/datum/sprite_accessory/mam_body_markings/lab
- name = "Lab"
- icon_state = "lab"
-
/datum/sprite_accessory/mam_body_markings/insect
name = "Insect"
icon_state = "insect"
icon = 'modular_citadel/icons/mob/markings_notmammals.dmi'
+/datum/sprite_accessory/mam_body_markings/lab
+ name = "Lab"
+ icon_state = "lab"
+
+/datum/sprite_accessory/mam_body_markings/orca
+ name = "Orca"
+ icon_state = "orca"
+
/datum/sprite_accessory/mam_body_markings/otie
name = "Otie"
icon_state = "otie"
@@ -156,14 +161,15 @@
name = "Otter"
icon_state = "otter"
-/datum/sprite_accessory/mam_body_markings/orca
- name = "Orca"
- icon_state = "orca"
-
/datum/sprite_accessory/mam_body_markings/panther
name = "Panther"
icon_state = "panther"
+/datum/sprite_accessory/mam_body_markings/plain
+ name = "Plain"
+ icon_state = "plain"
+ icon = 'modular_citadel/icons/mob/markings_notmammals.dmi'
+
/datum/sprite_accessory/mam_body_markings/possum
name = "Possum"
icon_state = "possum"
@@ -172,6 +178,10 @@
name = "Raccoon"
icon_state = "raccoon"
+/datum/sprite_accessory/mam_body_markings/sergal
+ name = "Sergal"
+ icon_state = "sergal"
+
/datum/sprite_accessory/mam_body_markings/pede
name = "Scolipede"
icon_state = "scolipede"
@@ -181,18 +191,14 @@
name = "Shark"
icon_state = "shark"
-/datum/sprite_accessory/mam_body_markings/skunk
- name = "Skunk"
- icon_state = "skunk"
-
-/datum/sprite_accessory/mam_body_markings/sergal
- name = "Sergal"
- icon_state = "sergal"
-
/datum/sprite_accessory/mam_body_markings/shepherd
name = "Shepherd"
icon_state = "shepherd"
+/datum/sprite_accessory/mam_body_markings/skunk
+ name = "Skunk"
+ icon_state = "skunk"
+
/datum/sprite_accessory/mam_body_markings/tajaran
name = "Tajaran"
icon_state = "tajaran"
@@ -224,80 +230,18 @@
color_src = 0
relevant_layers = list(BODY_FRONT_LAYER)
+/datum/sprite_accessory/insect_fluff/is_not_visible(var/mob/living/carbon/human/H, var/tauric)
+ return (!H.dna.features["insect_fluff"] || H.dna.features["insect_fluff"] == "None" || H.wear_suit && (H.wear_suit.flags_inv & HIDEJUMPSUIT))
+
/datum/sprite_accessory/insect_fluff/none
name = "None"
icon_state = "none"
relevant_layers = null
-/datum/sprite_accessory/insect_fluff/plain
- name = "Plain"
- icon_state = "plain"
-
-/datum/sprite_accessory/insect_fluff/reddish
- name = "Reddish"
- icon_state = "redish"
-
-/datum/sprite_accessory/insect_fluff/royal
- name = "Royal"
- icon_state = "royal"
-
-/datum/sprite_accessory/insect_fluff/gothic
- name = "Gothic"
- icon_state = "gothic"
-
-/datum/sprite_accessory/insect_fluff/lovers
- name = "Lovers"
- icon_state = "lovers"
-
-/datum/sprite_accessory/insect_fluff/whitefly
- name = "White Fly"
- icon_state = "whitefly"
-
/datum/sprite_accessory/insect_fluff/punished
name = "Burnt Off"
icon_state = "punished"
-/datum/sprite_accessory/insect_fluff/firewatch
- name = "Firewatch"
- icon_state = "firewatch"
-
-/datum/sprite_accessory/insect_fluff/deathhead
- name = "Deathshead"
- icon_state = "deathhead"
-
-/datum/sprite_accessory/insect_fluff/poison
- name = "Poison"
- icon_state = "poison"
-
-/datum/sprite_accessory/insect_fluff/ragged
- name = "Ragged"
- icon_state = "ragged"
-
-/datum/sprite_accessory/insect_fluff/moonfly
- name = "Moon Fly"
- icon_state = "moonfly"
-
-/datum/sprite_accessory/insect_fluff/snow
- name = "Snow"
- icon_state = "snow"
-
-/datum/sprite_accessory/insect_fluff/oakworm
- name = "Oak Worm"
- icon_state = "oakworm"
-
-/datum/sprite_accessory/insect_fluff/jungle
- name = "Jungle"
- icon_state = "jungle"
-
-/datum/sprite_accessory/insect_fluff/witchwing
- name = "Witch Wing"
- icon_state = "witchwing"
-
-/datum/sprite_accessory/insect_fluff/colored
- name = "Colored (Hair)"
- icon_state = "snow"
- color_src = HAIR
-
/datum/sprite_accessory/insect_fluff/colored1
name = "Colored (Primary)"
icon_state = "snow"
@@ -311,4 +255,69 @@
/datum/sprite_accessory/insect_fluff/colored3
name = "Colored (Tertiary)"
icon_state = "snow"
- color_src = MUTCOLORS3
\ No newline at end of file
+ color_src = MUTCOLORS3
+
+/datum/sprite_accessory/insect_fluff/colored
+ name = "Colored (Hair)"
+ icon_state = "snow"
+ color_src = HAIR
+
+/datum/sprite_accessory/insect_fluff/deathhead
+ name = "Deathshead"
+ icon_state = "deathhead"
+
+/datum/sprite_accessory/insect_fluff/firewatch
+ name = "Firewatch"
+ icon_state = "firewatch"
+
+/datum/sprite_accessory/insect_fluff/gothic
+ name = "Gothic"
+ icon_state = "gothic"
+
+/datum/sprite_accessory/insect_fluff/jungle
+ name = "Jungle"
+ icon_state = "jungle"
+
+/datum/sprite_accessory/insect_fluff/lovers
+ name = "Lovers"
+ icon_state = "lovers"
+
+/datum/sprite_accessory/insect_fluff/moonfly
+ name = "Moon Fly"
+ icon_state = "moonfly"
+
+/datum/sprite_accessory/insect_fluff/oakworm
+ name = "Oak Worm"
+ icon_state = "oakworm"
+
+/datum/sprite_accessory/insect_fluff/plain
+ name = "Plain"
+ icon_state = "plain"
+
+/datum/sprite_accessory/insect_fluff/poison
+ name = "Poison"
+ icon_state = "poison"
+
+/datum/sprite_accessory/insect_fluff/ragged
+ name = "Ragged"
+ icon_state = "ragged"
+
+/datum/sprite_accessory/insect_fluff/reddish
+ name = "Reddish"
+ icon_state = "redish"
+
+/datum/sprite_accessory/insect_fluff/royal
+ name = "Royal"
+ icon_state = "royal"
+
+/datum/sprite_accessory/insect_fluff/snow
+ name = "Snow"
+ icon_state = "snow"
+
+/datum/sprite_accessory/insect_fluff/whitefly
+ name = "White Fly"
+ icon_state = "whitefly"
+
+/datum/sprite_accessory/insect_fluff/witchwing
+ name = "Witch Wing"
+ icon_state = "witchwing"
diff --git a/code/modules/mob/dead/new_player/sprite_accessories/ears.dm b/code/modules/mob/dead/new_player/sprite_accessories/ears.dm
index bc269ccf62..7515ce560a 100644
--- a/code/modules/mob/dead/new_player/sprite_accessories/ears.dm
+++ b/code/modules/mob/dead/new_player/sprite_accessories/ears.dm
@@ -3,6 +3,10 @@
mutant_part_string = "ears"
relevant_layers = list(BODY_BEHIND_LAYER, BODY_ADJ_LAYER, BODY_FRONT_LAYER)
+/datum/sprite_accessory/ears/is_not_visible(var/mob/living/carbon/human/H, var/tauric)
+ var/obj/item/bodypart/head/HD = H.get_bodypart(BODY_ZONE_HEAD)
+ return (!H.dna.features["ears"] || H.dna.features["ears"] == "None" || H.head && (H.head.flags_inv & HIDEEARS) || (H.wear_mask && (H.wear_mask.flags_inv & HIDEEARS)) || !HD || HD.status == BODYPART_ROBOTIC)
+
/datum/sprite_accessory/ears/none
name = "None"
icon_state = "none"
@@ -37,7 +41,7 @@
extra = TRUE
extra_color_src = NONE
-/datum/sprite_accessory/ears/human/bigwolfdark
+/datum/sprite_accessory/ears/human/bigwolfdark //ignore alphabetical sort here for ease-of-use
name = "Dark Big Wolf"
icon_state = "bigwolfdark"
icon = 'modular_citadel/icons/mob/mam_ears.dmi'
@@ -51,6 +55,12 @@
extra = TRUE
extra_color_src = NONE
+/datum/sprite_accessory/ears/bunny
+ name = "Bunny"
+ icon_state = "bunny"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_ears.dmi'
+
/datum/sprite_accessory/ears/cat
name = "Cat"
icon_state = "cat"
@@ -70,6 +80,12 @@
icon = 'modular_citadel/icons/mob/mam_ears.dmi'
color_src = MUTCOLORS3
+/datum/sprite_accessory/ears/lab
+ name = "Dog, Floppy"
+ icon_state = "lab"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_ears.dmi'
+
/datum/sprite_accessory/ears/human/eevee
name = "Eevee"
icon_state = "eevee"
@@ -111,12 +127,6 @@
icon_state = "jellyfish"
color_src = HAIR
-/datum/sprite_accessory/ears/lab
- name = "Dog, Floppy"
- icon_state = "lab"
- color_src = MATRIXED
- icon = 'modular_citadel/icons/mob/mam_ears.dmi'
-
/datum/sprite_accessory/ears/murid
name = "Murid"
icon_state = "murid"
@@ -129,18 +139,18 @@
color_src = MATRIXED
icon = 'modular_citadel/icons/mob/mam_ears.dmi'
-/datum/sprite_accessory/ears/human/pede
- name = "Scolipede"
- icon_state = "pede"
- icon = 'modular_citadel/icons/mob/mam_ears.dmi'
- color_src = MATRIXED
-
/datum/sprite_accessory/ears/human/rabbit
name = "Rabbit"
icon_state = "rabbit"
color_src = MATRIXED
icon = 'modular_citadel/icons/mob/mam_ears.dmi'
+/datum/sprite_accessory/ears/human/pede
+ name = "Scolipede"
+ icon_state = "pede"
+ icon = 'modular_citadel/icons/mob/mam_ears.dmi'
+ color_src = MATRIXED
+
/datum/sprite_accessory/ears/human/sergal
name = "Sergal"
icon_state = "sergal"
@@ -165,60 +175,62 @@
color_src = MATRIXED
icon = 'modular_citadel/icons/mob/mam_ears.dmi'
-/datum/sprite_accessory/ears/bunny
- name = "Bunny"
- icon_state = "bunny"
- color_src = MATRIXED
- icon = 'modular_citadel/icons/mob/mam_ears.dmi'
-
/******************************************
*************** Furry Ears ****************
*******************************************/
-/datum/sprite_accessory/mam_ears
+/datum/sprite_accessory/ears/mam_ears
icon = 'modular_citadel/icons/mob/mam_ears.dmi'
color_src = MATRIXED
mutant_part_string = "ears"
relevant_layers = list(BODY_BEHIND_LAYER, BODY_ADJ_LAYER, BODY_FRONT_LAYER)
-/datum/sprite_accessory/mam_ears/none
+/datum/sprite_accessory/ears/mam_ears/is_not_visible(var/mob/living/carbon/human/H, var/tauric)
+ var/obj/item/bodypart/head/HD = H.get_bodypart(BODY_ZONE_HEAD)
+ return (!H.dna.features["mam_ears"] || H.dna.features["mam_ears"] == "None" || H.head && (H.head.flags_inv & HIDEEARS) || (H.wear_mask && (H.wear_mask.flags_inv & HIDEEARS)) || !HD || HD.status == BODYPART_ROBOTIC)
+
+/datum/sprite_accessory/ears/mam_ears/none
name = "None"
icon_state = "none"
relevant_layers = null
-/datum/sprite_accessory/mam_ears/axolotl
+/datum/sprite_accessory/ears/mam_ears/axolotl
name = "Axolotl"
icon_state = "axolotl"
-/datum/sprite_accessory/mam_ears/bat
+/datum/sprite_accessory/ears/mam_ears/bat
name = "Bat"
icon_state = "bat"
-/datum/sprite_accessory/mam_ears/bear
+/datum/sprite_accessory/ears/mam_ears/bear
name = "Bear"
icon_state = "bear"
-/datum/sprite_accessory/mam_ears/bigwolf
+/datum/sprite_accessory/ears/mam_ears/bigwolf
name = "Big Wolf"
icon_state = "bigwolf"
-/datum/sprite_accessory/mam_ears/bigwolfinner
+/datum/sprite_accessory/ears/mam_ears/bigwolfinner
name = "Big Wolf (ALT)"
icon_state = "bigwolfinner"
extra = TRUE
extra_color_src = NONE
-/datum/sprite_accessory/mam_ears/bigwolfdark
+/datum/sprite_accessory/ears/mam_ears/bigwolfdark //alphabetical sort ignored here for ease-of-use
name = "Dark Big Wolf"
icon_state = "bigwolfdark"
-/datum/sprite_accessory/mam_ears/bigwolfinnerdark
+/datum/sprite_accessory/ears/mam_ears/bigwolfinnerdark
name = "Dark Big Wolf (ALT)"
icon_state = "bigwolfinnerdark"
extra = TRUE
extra_color_src = NONE
-/datum/sprite_accessory/mam_ears/cat
+/datum/sprite_accessory/ears/mam_ears/bunny
+ name = "Bunny"
+ icon_state = "bunny"
+
+/datum/sprite_accessory/ears/mam_ears/cat
name = "Cat"
icon_state = "cat"
icon = 'icons/mob/mutant_bodyparts.dmi'
@@ -226,100 +238,94 @@
extra = TRUE
extra_color_src = NONE
-/datum/sprite_accessory/mam_ears/catbig
+/datum/sprite_accessory/ears/mam_ears/catbig
name = "Cat, Big"
icon_state = "catbig"
-/datum/sprite_accessory/mam_ears/cow
+/datum/sprite_accessory/ears/mam_ears/cow
name = "Cow"
icon_state = "cow"
-/datum/sprite_accessory/mam_ears/curled
+/datum/sprite_accessory/ears/mam_ears/curled
name = "Curled Horn"
icon_state = "horn1"
color_src = MUTCOLORS3
-/datum/sprite_accessory/mam_ears/deer
+/datum/sprite_accessory/ears/mam_ears/deer
name = "Deer"
icon_state = "deer"
color_src = MUTCOLORS3
-/datum/sprite_accessory/mam_ears/eevee
+/datum/sprite_accessory/ears/mam_ears/eevee
name = "Eevee"
icon_state = "eevee"
-
-/datum/sprite_accessory/mam_ears/elf
+/datum/sprite_accessory/ears/mam_ears/elf
name = "Elf"
icon_state = "elf"
color_src = MUTCOLORS3
-
-/datum/sprite_accessory/mam_ears/elephant
+/datum/sprite_accessory/ears/mam_ears/elephant
name = "Elephant"
icon_state = "elephant"
-/datum/sprite_accessory/mam_ears/fennec
+/datum/sprite_accessory/ears/mam_ears/fennec
name = "Fennec"
icon_state = "fennec"
-/datum/sprite_accessory/mam_ears/fish
+/datum/sprite_accessory/ears/mam_ears/fish
name = "Fish"
icon_state = "fish"
-/datum/sprite_accessory/mam_ears/fox
+/datum/sprite_accessory/ears/mam_ears/fox
name = "Fox"
icon_state = "fox"
-/datum/sprite_accessory/mam_ears/husky
+/datum/sprite_accessory/ears/mam_ears/husky
name = "Husky"
icon_state = "wolf"
-/datum/sprite_accessory/mam_ears/kangaroo
- name = "kangaroo"
- icon_state = "kangaroo"
-
-/datum/sprite_accessory/mam_ears/jellyfish
+/datum/sprite_accessory/ears/mam_ears/jellyfish
name = "Jellyfish"
icon_state = "jellyfish"
color_src = HAIR
-/datum/sprite_accessory/mam_ears/lab
+/datum/sprite_accessory/ears/mam_ears/kangaroo
+ name = "kangaroo"
+ icon_state = "kangaroo"
+
+/datum/sprite_accessory/ears/mam_ears/lab
name = "Dog, Long"
icon_state = "lab"
-/datum/sprite_accessory/mam_ears/murid
+/datum/sprite_accessory/ears/mam_ears/murid
name = "Murid"
icon_state = "murid"
-/datum/sprite_accessory/mam_ears/otie
+/datum/sprite_accessory/ears/mam_ears/otie
name = "Otusian"
icon_state = "otie"
-/datum/sprite_accessory/mam_ears/squirrel
- name = "Squirrel"
- icon_state = "squirrel"
-
-/datum/sprite_accessory/mam_ears/pede
- name = "Scolipede"
- icon_state = "pede"
-
-/datum/sprite_accessory/mam_ears/rabbit
+/datum/sprite_accessory/ears/mam_ears/rabbit
name = "Rabbit"
icon_state = "rabbit"
-/datum/sprite_accessory/mam_ears/sergal
+/datum/sprite_accessory/ears/mam_ears/pede
+ name = "Scolipede"
+ icon_state = "pede"
+
+/datum/sprite_accessory/ears/mam_ears/sergal
name = "Sergal"
icon_state = "sergal"
-/datum/sprite_accessory/mam_ears/skunk
+/datum/sprite_accessory/ears/mam_ears/skunk
name = "skunk"
icon_state = "skunk"
-/datum/sprite_accessory/mam_ears/wolf
+/datum/sprite_accessory/ears/mam_ears/squirrel
+ name = "Squirrel"
+ icon_state = "squirrel"
+
+/datum/sprite_accessory/ears/mam_ears/wolf
name = "Wolf"
icon_state = "wolf"
-
-/datum/sprite_accessory/mam_ears/bunny
- name = "Bunny"
- icon_state = "bunny"
diff --git a/code/modules/mob/dead/new_player/sprite_accessories/frills.dm b/code/modules/mob/dead/new_player/sprite_accessories/frills.dm
index 0aaec309a4..49013161a9 100644
--- a/code/modules/mob/dead/new_player/sprite_accessories/frills.dm
+++ b/code/modules/mob/dead/new_player/sprite_accessories/frills.dm
@@ -2,11 +2,19 @@
icon = 'icons/mob/mutant_bodyparts.dmi'
relevant_layers = list(BODY_ADJ_LAYER)
+/datum/sprite_accessory/frills/is_not_visible(var/mob/living/carbon/human/H, var/tauric)
+ var/obj/item/bodypart/head/HD = H.get_bodypart(BODY_ZONE_HEAD)
+ return (!H.dna.features["frills"] || H.dna.features["frills"] == "None" || H.head && (H.head.flags_inv & HIDEEARS) || !HD || HD.status == BODYPART_ROBOTIC)
+
/datum/sprite_accessory/frills/none
name = "None"
icon_state = "none"
relevant_layers = null
+/datum/sprite_accessory/frills/aquatic
+ name = "Aquatic"
+ icon_state = "aqua"
+
/datum/sprite_accessory/frills/simple
name = "Simple"
icon_state = "simple"
@@ -14,7 +22,3 @@
/datum/sprite_accessory/frills/short
name = "Short"
icon_state = "short"
-
-/datum/sprite_accessory/frills/aquatic
- name = "Aquatic"
- icon_state = "aqua"
diff --git a/code/modules/mob/dead/new_player/sprite_accessories/hair_face.dm b/code/modules/mob/dead/new_player/sprite_accessories/hair_face.dm
index 34988f5656..a07fdaa5d3 100644
--- a/code/modules/mob/dead/new_player/sprite_accessories/hair_face.dm
+++ b/code/modules/mob/dead/new_player/sprite_accessories/hair_face.dm
@@ -6,6 +6,10 @@
gender = MALE // barf (unless you're a dorf, dorfs dig chix w/ beards :P)
// please make sure they're sorted alphabetically and categorized
+/datum/sprite_accessory/facial_hair/shaved //this is exempt from the alphabetical sort
+ name = "Shaved"
+ icon_state = null
+ gender = NEUTER
/datum/sprite_accessory/facial_hair/threeoclock
name = "Beard (3 o\'Clock)"
@@ -135,11 +139,6 @@
name = "Mutton Chops with Moustache"
icon_state = "facial_muttonmus"
-/datum/sprite_accessory/facial_hair/shaved
- name = "Shaved"
- icon_state = null
- gender = NEUTER
-
/datum/sprite_accessory/facial_hair/sideburn
name = "Sideburns"
icon_state = "facial_sideburns"
diff --git a/code/modules/mob/dead/new_player/sprite_accessories/hair_head.dm b/code/modules/mob/dead/new_player/sprite_accessories/hair_head.dm
index 89f2dd5370..8e4e6ad617 100644
--- a/code/modules/mob/dead/new_player/sprite_accessories/hair_head.dm
+++ b/code/modules/mob/dead/new_player/sprite_accessories/hair_head.dm
@@ -9,6 +9,10 @@
// try to spell
// you do not need to define _s or _l sub-states, game automatically does this for you
+/datum/sprite_accessory/hair/bald //this is exempt from the alphabetical sort
+ name = "Bald"
+ icon_state = "bald"
+
/datum/sprite_accessory/hair/afro
name = "Afro"
icon_state = "hair_afro"
@@ -25,10 +29,6 @@
name = "Ahoge"
icon_state = "hair_antenna"
-/datum/sprite_accessory/hair/bald
- name = "Bald"
- icon_state = "bald"
-
/datum/sprite_accessory/hair/balding
name = "Balding Hair"
icon_state = "hair_e"
@@ -791,6 +791,10 @@
name = "Volaju"
icon_state = "hair_volaju"
+/datum/sprite_accessory/hair/volajupompless
+ name = "Volaju (Alt)"
+ icon_state = "hair_volajupompless"
+
/datum/sprite_accessory/hair/wisp
name = "Wisp"
icon_state = "hair_wisp"
diff --git a/code/modules/mob/dead/new_player/sprite_accessories/horns.dm b/code/modules/mob/dead/new_player/sprite_accessories/horns.dm
index b39f48f858..aff342c7a6 100644
--- a/code/modules/mob/dead/new_player/sprite_accessories/horns.dm
+++ b/code/modules/mob/dead/new_player/sprite_accessories/horns.dm
@@ -3,27 +3,19 @@
color_src = HORNCOLOR
relevant_layers = list(HORNS_LAYER)
+/datum/sprite_accessory/horns/is_not_visible(var/mob/living/carbon/human/H, var/tauric)
+ var/obj/item/bodypart/head/HD = H.get_bodypart(BODY_ZONE_HEAD)
+ return (!H.dna.features["horns"] || H.dna.features["horns"] == "None" || H.head && (H.head.flags_inv & HIDEHAIR) || (H.wear_mask && (H.wear_mask.flags_inv & HIDEHAIR)) || !HD || HD.status == BODYPART_ROBOTIC)
+
/datum/sprite_accessory/horns/none
name = "None"
icon_state = "none"
relevant_layers = null
-/datum/sprite_accessory/horns/simple
- name = "Simple"
- icon_state = "simple"
-
-/datum/sprite_accessory/horns/short
- name = "Short"
- icon_state = "short"
-
/datum/sprite_accessory/horns/curled
name = "Curled"
icon_state = "curled"
-/datum/sprite_accessory/horns/ram
- name = "Ram"
- icon_state = "ram"
-
/datum/sprite_accessory/horns/angler
name = "Angeler"
icon_state = "angler"
@@ -36,3 +28,15 @@
/datum/sprite_accessory/horns/guilmon
name = "Guilmon"
icon_state = "guilmon"
+
+/datum/sprite_accessory/horns/ram
+ name = "Ram"
+ icon_state = "ram"
+
+/datum/sprite_accessory/horns/simple
+ name = "Simple"
+ icon_state = "simple"
+
+/datum/sprite_accessory/horns/short
+ name = "Short"
+ icon_state = "short"
diff --git a/code/modules/mob/dead/new_player/sprite_accessories/ipc_synths.dm b/code/modules/mob/dead/new_player/sprite_accessories/ipc_synths.dm
index 110ac69201..6019245b80 100644
--- a/code/modules/mob/dead/new_player/sprite_accessories/ipc_synths.dm
+++ b/code/modules/mob/dead/new_player/sprite_accessories/ipc_synths.dm
@@ -11,96 +11,90 @@
name = "Blank"
icon_state = "blank"
-/datum/sprite_accessory/screen/pink
- name = "Pink"
- icon_state = "pink"
-
-/datum/sprite_accessory/screen/green
- name = "Green"
- icon_state = "green"
-
-/datum/sprite_accessory/screen/red
- name = "Red"
- icon_state = "red"
-
/datum/sprite_accessory/screen/blue
name = "Blue"
icon_state = "blue"
-/datum/sprite_accessory/screen/yellow
- name = "Yellow"
- icon_state = "yellow"
-
-/datum/sprite_accessory/screen/shower
- name = "Shower"
- icon_state = "shower"
-
-/datum/sprite_accessory/screen/nature
- name = "Nature"
- icon_state = "nature"
-
-/datum/sprite_accessory/screen/eight
- name = "Eight"
- icon_state = "eight"
-
-/datum/sprite_accessory/screen/goggles
- name = "Goggles"
- icon_state = "goggles"
-
-/datum/sprite_accessory/screen/heart
- name = "Heart"
- icon_state = "heart"
-
-/datum/sprite_accessory/screen/monoeye
- name = "Mono eye"
- icon_state = "monoeye"
-
/datum/sprite_accessory/screen/breakout
name = "Breakout"
icon_state = "breakout"
-/datum/sprite_accessory/screen/purple
- name = "Purple"
- icon_state = "purple"
-
-/datum/sprite_accessory/screen/scroll
- name = "Scroll"
- icon_state = "scroll"
+/datum/sprite_accessory/screen/bsod
+ name = "BSOD"
+ icon_state = "bsod"
/datum/sprite_accessory/screen/console
name = "Console"
icon_state = "console"
-/datum/sprite_accessory/screen/rgb
- name = "RGB"
- icon_state = "rgb"
+/datum/sprite_accessory/screen/eight
+ name = "Eight"
+ icon_state = "eight"
+
+/datum/sprite_accessory/screen/eyes
+ name = "Eyes"
+ icon_state = "eyes"
+
+/datum/sprite_accessory/screen/ecgwave
+ name = "ECG wave"
+ icon_state = "ecgwave"
+
+/datum/sprite_accessory/screen/green
+ name = "Green"
+ icon_state = "green"
+
+/datum/sprite_accessory/screen/goggles
+ name = "Goggles"
+ icon_state = "goggles"
/datum/sprite_accessory/screen/golglider
name = "Gol Glider"
icon_state = "golglider"
+/datum/sprite_accessory/screen/heart
+ name = "Heart"
+ icon_state = "heart"
+
+/datum/sprite_accessory/screen/pink
+ name = "Pink"
+ icon_state = "pink"
+
+/datum/sprite_accessory/screen/red
+ name = "Red"
+ icon_state = "red"
+
+/datum/sprite_accessory/screen/monoeye
+ name = "Mono eye"
+ icon_state = "monoeye"
+
+/datum/sprite_accessory/screen/nature
+ name = "Nature"
+ icon_state = "nature"
+
+/datum/sprite_accessory/screen/purple
+ name = "Purple"
+ icon_state = "purple"
+
/datum/sprite_accessory/screen/rainbow
name = "Rainbow"
icon_state = "rainbow"
-/datum/sprite_accessory/screen/sunburst
- name = "Sunburst"
- icon_state = "sunburst"
-
-/datum/sprite_accessory/screen/static
- name = "Static"
- icon_state = "static"
-
-//Oracle Station sprites
-
-/datum/sprite_accessory/screen/bsod
- name = "BSOD"
- icon_state = "bsod"
-
/datum/sprite_accessory/screen/redtext
name = "Red Text"
icon_state = "retext"
+/datum/sprite_accessory/screen/rgb
+ name = "RGB"
+ icon_state = "rgb"
+
+/datum/sprite_accessory/screen/scroll
+ name = "Scroll"
+ icon_state = "scroll"
+
+/datum/sprite_accessory/screen/shower
+ name = "Shower"
+ icon_state = "shower"
+
/datum/sprite_accessory/screen/sinewave
name = "Sine wave"
icon_state = "sinewave"
@@ -109,22 +103,25 @@
name = "Square wave"
icon_state = "squarwave"
-/datum/sprite_accessory/screen/ecgwave
- name = "ECG wave"
- icon_state = "ecgwave"
+/datum/sprite_accessory/screen/stars
+ name = "Stars"
+ icon_state = "stars"
-/datum/sprite_accessory/screen/eyes
- name = "Eyes"
- icon_state = "eyes"
+/datum/sprite_accessory/screen/static
+ name = "Static"
+ icon_state = "static"
+
+/datum/sprite_accessory/screen/sunburst
+ name = "Sunburst"
+ icon_state = "sunburst"
/datum/sprite_accessory/screen/textdrop
name = "Text drop"
icon_state = "textdrop"
-/datum/sprite_accessory/screen/stars
- name = "Stars"
- icon_state = "stars"
-
+/datum/sprite_accessory/screen/yellow
+ name = "Yellow"
+ icon_state = "yellow"
/******************************************
************** IPC Antennas ***************
@@ -145,14 +142,6 @@
name = "Angled Antennae"
icon_state = "antennae"
-/datum/sprite_accessory/antenna/tvantennae
- name = "TV Antennae"
- icon_state = "tvantennae"
-
-/datum/sprite_accessory/antenna/cyberhead
- name = "Cyberhead"
- icon_state = "cyberhead"
-
/datum/sprite_accessory/antenna/antlers
name = "Antlers"
icon_state = "antlers"
@@ -160,3 +149,11 @@
/datum/sprite_accessory/antenna/crowned
name = "Crowned"
icon_state = "crowned"
+
+/datum/sprite_accessory/antenna/cyberhead
+ name = "Cyberhead"
+ icon_state = "cyberhead"
+
+/datum/sprite_accessory/antenna/tvantennae
+ name = "TV Antennae"
+ icon_state = "tvantennae"
diff --git a/code/modules/mob/dead/new_player/sprite_accessories/legs_and_taurs.dm b/code/modules/mob/dead/new_player/sprite_accessories/legs_and_taurs.dm
index 2ec6da2da8..6f7b955d8f 100644
--- a/code/modules/mob/dead/new_player/sprite_accessories/legs_and_taurs.dm
+++ b/code/modules/mob/dead/new_player/sprite_accessories/legs_and_taurs.dm
@@ -30,6 +30,9 @@
var/alt_taur_mode = NONE //Same as above.
var/hide_legs = USE_QUADRUPED_CLIP_MASK
+/datum/sprite_accessory/taur/is_not_visible(var/mob/living/carbon/human/H, var/tauric)
+ return (!tauric || (H.wear_suit && (H.wear_suit.flags_inv & HIDETAUR)))
+
/datum/sprite_accessory/taur/New()
switch(hide_legs)
if(USE_QUADRUPED_CLIP_MASK)
@@ -46,6 +49,13 @@
relevant_layers = null
hide_legs = FALSE
+/datum/sprite_accessory/taur/canine
+ name = "Canine"
+ icon_state = "canine"
+ taur_mode = STYLE_PAW_TAURIC
+ color_src = MUTCOLORS
+ extra = TRUE
+
/datum/sprite_accessory/taur/cow
name = "Cow"
icon_state = "cow"
@@ -92,6 +102,13 @@
color_src = MUTCOLORS
extra = TRUE
+/datum/sprite_accessory/taur/feline
+ name = "Feline"
+ icon_state = "feline"
+ taur_mode = STYLE_PAW_TAURIC
+ color_src = MUTCOLORS
+ extra = TRUE
+
/datum/sprite_accessory/taur/horse
name = "Horse"
icon_state = "horse"
@@ -123,17 +140,3 @@
taur_mode = STYLE_SNEK_TAURIC
color_src = MUTCOLORS
hide_legs = USE_SNEK_CLIP_MASK
-
-/datum/sprite_accessory/taur/canine
- name = "Canine"
- icon_state = "canine"
- taur_mode = STYLE_PAW_TAURIC
- color_src = MUTCOLORS
- extra = TRUE
-
-/datum/sprite_accessory/taur/feline
- name = "Feline"
- icon_state = "feline"
- taur_mode = STYLE_PAW_TAURIC
- color_src = MUTCOLORS
- extra = TRUE
diff --git a/code/modules/mob/dead/new_player/sprite_accessories/snouts.dm b/code/modules/mob/dead/new_player/sprite_accessories/snouts.dm
index 60e8ed1007..99d2c67cc9 100644
--- a/code/modules/mob/dead/new_player/sprite_accessories/snouts.dm
+++ b/code/modules/mob/dead/new_player/sprite_accessories/snouts.dm
@@ -3,14 +3,23 @@
mutant_part_string = "snout"
relevant_layers = list(BODY_ADJ_LAYER, BODY_FRONT_LAYER)
-/datum/sprite_accessory/snouts/sharp
- name = "Sharp"
- icon_state = "sharp"
+/datum/sprite_accessory/snouts/is_not_visible(var/mob/living/carbon/human/H, var/tauric)
+ var/obj/item/bodypart/head/HD = H.get_bodypart(BODY_ZONE_HEAD)
+ return ((H.wear_mask && (H.wear_mask.flags_inv & HIDESNOUT)) || (H.head && (H.head.flags_inv & HIDESNOUT)) || !HD || HD.status == BODYPART_ROBOTIC)
+
+/datum/sprite_accessory/snout/guilmon
+ name = "Guilmon"
+ icon_state = "guilmon"
+ color_src = MATRIXED
/datum/sprite_accessory/snouts/round
name = "Round"
icon_state = "round"
+/datum/sprite_accessory/snouts/sharp
+ name = "Sharp"
+ icon_state = "sharp"
+
/datum/sprite_accessory/snouts/sharplight
name = "Sharp + Light"
icon_state = "sharplight"
@@ -19,11 +28,6 @@
name = "Round + Light"
icon_state = "roundlight"
-/datum/sprite_accessory/snout/guilmon
- name = "Guilmon"
- icon_state = "guilmon"
- color_src = MATRIXED
-
//christ this was a mistake, but it's here just in case someone wants to selectively fix -- Pooj
/************* Lizard compatable snoots ***********
/datum/sprite_accessory/snouts/bird
@@ -150,242 +154,251 @@
************** Mammal Snouts **************
*******************************************/
-/datum/sprite_accessory/mam_snouts
+/datum/sprite_accessory/snouts/mam_snouts
color_src = MATRIXED
icon = 'modular_citadel/icons/mob/mam_snouts.dmi'
- recommended_species = list("mammal", "slimeperson", "insect", "podweak")
+ recommended_species = list("mammal", "slimeperson", "insect", "podweak", "lizard")
mutant_part_string = "snout"
relevant_layers = list(BODY_ADJ_LAYER, BODY_FRONT_LAYER)
-/datum/sprite_accessory/mam_snouts/none
+/datum/sprite_accessory/snouts/mam_snouts/is_not_visible(var/mob/living/carbon/human/H, var/tauric)
+ var/obj/item/bodypart/head/HD = H.get_bodypart(BODY_ZONE_HEAD)
+ return ((H.wear_mask && (H.wear_mask.flags_inv & HIDESNOUT)) || (H.head && (H.head.flags_inv & HIDESNOUT)) || !HD || HD.status == BODYPART_ROBOTIC)
+
+/datum/sprite_accessory/snouts/mam_snouts/none
name = "None"
icon_state = "none"
recommended_species = null
relevant_layers = null
-/datum/sprite_accessory/mam_snouts/bird
+/datum/sprite_accessory/snouts/mam_snouts/bird
name = "Beak"
icon_state = "bird"
-/datum/sprite_accessory/mam_snouts/bigbeak
+/datum/sprite_accessory/snouts/mam_snouts/bigbeak
name = "Big Beak"
icon_state = "bigbeak"
-/datum/sprite_accessory/mam_snouts/bug
+/datum/sprite_accessory/snouts/mam_snouts/bug
name = "Bug"
icon_state = "bug"
color_src = MUTCOLORS
extra2 = TRUE
extra2_color_src = MUTCOLORS3
-/datum/sprite_accessory/mam_snouts/elephant
+/datum/sprite_accessory/snouts/mam_snouts/elephant
name = "Elephant"
icon_state = "elephant"
extra = TRUE
extra_color_src = MUTCOLORS3
-/datum/sprite_accessory/mam_snouts/lcanid
- name = "Mammal, Long"
- icon_state = "lcanid"
+/datum/sprite_accessory/snouts/mam_snouts/husky
+ name = "Husky"
+ icon_state = "husky"
-/datum/sprite_accessory/mam_snouts/lcanidalt
- name = "Mammal, Long ALT"
- icon_state = "lcanidalt"
-
-/datum/sprite_accessory/mam_snouts/scanid
- name = "Mammal, Short"
- icon_state = "scanid"
-
-/datum/sprite_accessory/mam_snouts/scanidalt
- name = "Mammal, Short ALT"
- icon_state = "scanidalt"
-
-/datum/sprite_accessory/mam_snouts/scanidalt2
- name = "Mammal, Short ALT 2"
- icon_state = "scanidalt2"
-
-/datum/sprite_accessory/mam_snouts/wolf
- name = "Mammal, Thick"
- icon_state = "wolf"
-
-/datum/sprite_accessory/mam_snouts/wolfalt
- name = "Mammal, Thick ALT"
- icon_state = "wolfalt"
-
-/datum/sprite_accessory/mam_snouts/redpanda
- name = "WahCoon"
- icon_state = "wah"
-
-/datum/sprite_accessory/mam_snouts/redpandaalt
- name = "WahCoon ALT"
- icon_state = "wahalt"
-
-/datum/sprite_accessory/mam_snouts/rhino
+/datum/sprite_accessory/snouts/mam_snouts/rhino
name = "Horn"
icon_state = "rhino"
extra = TRUE
extra = MUTCOLORS3
-/datum/sprite_accessory/mam_snouts/rodent
+/datum/sprite_accessory/snouts/mam_snouts/rodent
name = "Rodent"
icon_state = "rodent"
-/datum/sprite_accessory/mam_snouts/husky
- name = "Husky"
- icon_state = "husky"
+/datum/sprite_accessory/snouts/mam_snouts/lcanid
+ name = "Mammal, Long"
+ icon_state = "lcanid"
-/datum/sprite_accessory/mam_snouts/otie
+/datum/sprite_accessory/snouts/mam_snouts/lcanidalt
+ name = "Mammal, Long ALT"
+ icon_state = "lcanidalt"
+
+/datum/sprite_accessory/snouts/mam_snouts/scanid
+ name = "Mammal, Short"
+ icon_state = "scanid"
+
+/datum/sprite_accessory/snouts/mam_snouts/scanidalt
+ name = "Mammal, Short ALT"
+ icon_state = "scanidalt"
+
+/datum/sprite_accessory/snouts/mam_snouts/scanidalt2
+ name = "Mammal, Short ALT 2"
+ icon_state = "scanidalt2"
+
+/datum/sprite_accessory/snouts/mam_snouts/wolf
+ name = "Mammal, Thick"
+ icon_state = "wolf"
+
+/datum/sprite_accessory/snouts/mam_snouts/wolfalt
+ name = "Mammal, Thick ALT"
+ icon_state = "wolfalt"
+
+/datum/sprite_accessory/snouts/mam_snouts/otie
name = "Otie"
icon_state = "otie"
-/datum/sprite_accessory/mam_snouts/pede
- name = "Scolipede"
- icon_state = "pede"
-
-/datum/sprite_accessory/mam_snouts/sergal
- name = "Sergal"
- icon_state = "sergal"
-
-/datum/sprite_accessory/mam_snouts/shark
- name = "Shark"
- icon_state = "shark"
-
-/datum/sprite_accessory/mam_snouts/hshark
- name = "hShark"
- icon_state = "hshark"
-
-/datum/sprite_accessory/mam_snouts/toucan
- name = "Toucan"
- icon_state = "toucan"
-
-/datum/sprite_accessory/mam_snouts/sharp
- name = "Sharp"
- icon_state = "sharp"
- color_src = MUTCOLORS
-
-/datum/sprite_accessory/mam_snouts/round
+/datum/sprite_accessory/snouts/mam_snouts/round
name = "Round"
icon_state = "round"
color_src = MUTCOLORS
-/datum/sprite_accessory/mam_snouts/sharplight
- name = "Sharp + Light"
- icon_state = "sharplight"
- color_src = MUTCOLORS
-
-/datum/sprite_accessory/mam_snouts/roundlight
+/datum/sprite_accessory/snouts/mam_snouts/roundlight
name = "Round + Light"
icon_state = "roundlight"
color_src = MUTCOLORS
+/datum/sprite_accessory/snouts/mam_snouts/pede
+ name = "Scolipede"
+ icon_state = "pede"
+
+/datum/sprite_accessory/snouts/mam_snouts/sergal
+ name = "Sergal"
+ icon_state = "sergal"
+
+/datum/sprite_accessory/snouts/mam_snouts/shark
+ name = "Shark"
+ icon_state = "shark"
+
+/datum/sprite_accessory/snouts/mam_snouts/hshark
+ name = "hShark"
+ icon_state = "hshark"
+
+/datum/sprite_accessory/snouts/mam_snouts/sharp
+ name = "Sharp"
+ icon_state = "sharp"
+ color_src = MUTCOLORS
+
+/datum/sprite_accessory/snouts/mam_snouts/sharplight
+ name = "Sharp + Light"
+ icon_state = "sharplight"
+ color_src = MUTCOLORS
+
+/datum/sprite_accessory/snouts/mam_snouts/skulldog
+ name = "Skulldog"
+ icon_state = "skulldog"
+ extra = TRUE
+ extra_color_src = MATRIXED
+
+/datum/sprite_accessory/snouts/mam_snouts/toucan
+ name = "Toucan"
+ icon_state = "toucan"
+
+/datum/sprite_accessory/snouts/mam_snouts/redpanda
+ name = "WahCoon"
+ icon_state = "wah"
+
+/datum/sprite_accessory/snouts/mam_snouts/redpandaalt
+ name = "WahCoon ALT"
+ icon_state = "wahalt"
/******************************************
**************** Snouts *******************
*************but higher up*****************/
-/datum/sprite_accessory/mam_snouts/fbird
+/datum/sprite_accessory/snouts/mam_snouts/fbird
name = "Beak (Top)"
icon_state = "fbird"
-/datum/sprite_accessory/mam_snouts/fbigbeak
+/datum/sprite_accessory/snouts/mam_snouts/fbigbeak
name = "Big Beak (Top)"
icon_state = "fbigbeak"
-/datum/sprite_accessory/mam_snouts/fbug
+/datum/sprite_accessory/snouts/mam_snouts/fbug
name = "Bug (Top)"
icon_state = "fbug"
color_src = MUTCOLORS
extra2 = TRUE
extra2_color_src = MUTCOLORS3
-/datum/sprite_accessory/mam_snouts/felephant
+/datum/sprite_accessory/snouts/mam_snouts/felephant
name = "Elephant (Top)"
icon_state = "felephant"
extra = TRUE
extra_color_src = MUTCOLORS3
-/datum/sprite_accessory/mam_snouts/flcanid
- name = "Mammal, Long (Top)"
- icon_state = "flcanid"
-
-/datum/sprite_accessory/mam_snouts/flcanidalt
- name = "Mammal, Long ALT (Top)"
- icon_state = "flcanidalt"
-
-/datum/sprite_accessory/mam_snouts/fscanid
- name = "Mammal, Short (Top)"
- icon_state = "fscanid"
-
-/datum/sprite_accessory/mam_snouts/fscanidalt
- name = "Mammal, Short ALT (Top)"
- icon_state = "fscanidalt"
-
-/datum/sprite_accessory/mam_snouts/fscanidalt2
- name = "Mammal, Short ALT 2 (Top)"
- icon_state = "fscanidalt2"
-
-/datum/sprite_accessory/mam_snouts/fwolf
- name = "Mammal, Thick (Top)"
- icon_state = "fwolf"
-
-/datum/sprite_accessory/mam_snouts/fwolfalt
- name = "Mammal, Thick ALT (Top)"
- icon_state = "fwolfalt"
-
-/datum/sprite_accessory/mam_snouts/fredpanda
- name = "WahCoon (Top)"
- icon_state = "fwah"
-
-/datum/sprite_accessory/mam_snouts/frhino
+/datum/sprite_accessory/snouts/mam_snouts/frhino
name = "Horn (Top)"
icon_state = "frhino"
extra = TRUE
extra = MUTCOLORS3
-/datum/sprite_accessory/mam_snouts/frodent
- name = "Rodent (Top)"
- icon_state = "frodent"
-
-/datum/sprite_accessory/mam_snouts/fhusky
+/datum/sprite_accessory/snouts/mam_snouts/fhusky
name = "Husky (Top)"
icon_state = "fhusky"
-/datum/sprite_accessory/mam_snouts/fotie
+/datum/sprite_accessory/snouts/mam_snouts/flcanid
+ name = "Mammal, Long (Top)"
+ icon_state = "flcanid"
+
+/datum/sprite_accessory/snouts/mam_snouts/flcanidalt
+ name = "Mammal, Long ALT (Top)"
+ icon_state = "flcanidalt"
+
+/datum/sprite_accessory/snouts/mam_snouts/fscanid
+ name = "Mammal, Short (Top)"
+ icon_state = "fscanid"
+
+/datum/sprite_accessory/snouts/mam_snouts/fscanidalt
+ name = "Mammal, Short ALT (Top)"
+ icon_state = "fscanidalt"
+
+/datum/sprite_accessory/snouts/mam_snouts/fscanidalt2
+ name = "Mammal, Short ALT 2 (Top)"
+ icon_state = "fscanidalt2"
+
+/datum/sprite_accessory/snouts/mam_snouts/fwolf
+ name = "Mammal, Thick (Top)"
+ icon_state = "fwolf"
+
+/datum/sprite_accessory/snouts/mam_snouts/fwolfalt
+ name = "Mammal, Thick ALT (Top)"
+ icon_state = "fwolfalt"
+
+/datum/sprite_accessory/snouts/mam_snouts/fotie
name = "Otie (Top)"
icon_state = "fotie"
-/datum/sprite_accessory/mam_snouts/fpede
- name = "Scolipede (Top)"
- icon_state = "fpede"
+/datum/sprite_accessory/snouts/mam_snouts/frodent
+ name = "Rodent (Top)"
+ icon_state = "frodent"
-/datum/sprite_accessory/mam_snouts/fsergal
- name = "Sergal (Top)"
- icon_state = "fsergal"
-
-/datum/sprite_accessory/mam_snouts/fshark
- name = "Shark (Top)"
- icon_state = "fshark"
-
-/datum/sprite_accessory/mam_snouts/ftoucan
- name = "Toucan (Top)"
- icon_state = "ftoucan"
-
-/datum/sprite_accessory/mam_snouts/fsharp
- name = "Sharp (Top)"
- icon_state = "fsharp"
- color_src = MUTCOLORS
-
-/datum/sprite_accessory/mam_snouts/fround
+/datum/sprite_accessory/snouts/mam_snouts/fround
name = "Round (Top)"
icon_state = "fround"
color_src = MUTCOLORS
-/datum/sprite_accessory/mam_snouts/fsharplight
+/datum/sprite_accessory/snouts/mam_snouts/froundlight
+ name = "Round + Light (Top)"
+ icon_state = "froundlight"
+ color_src = MUTCOLORS
+
+/datum/sprite_accessory/snouts/mam_snouts/fpede
+ name = "Scolipede (Top)"
+ icon_state = "fpede"
+
+/datum/sprite_accessory/snouts/mam_snouts/fsergal
+ name = "Sergal (Top)"
+ icon_state = "fsergal"
+
+/datum/sprite_accessory/snouts/mam_snouts/fshark
+ name = "Shark (Top)"
+ icon_state = "fshark"
+
+/datum/sprite_accessory/snouts/mam_snouts/fsharp
+ name = "Sharp (Top)"
+ icon_state = "fsharp"
+ color_src = MUTCOLORS
+
+/datum/sprite_accessory/snouts/mam_snouts/fsharplight
name = "Sharp + Light (Top)"
icon_state = "fsharplight"
color_src = MUTCOLORS
-/datum/sprite_accessory/mam_snouts/froundlight
- name = "Round + Light (Top)"
- icon_state = "froundlight"
- color_src = MUTCOLORS
+/datum/sprite_accessory/snouts/mam_snouts/ftoucan
+ name = "Toucan (Top)"
+ icon_state = "ftoucan"
+
+/datum/sprite_accessory/snouts/mam_snouts/fredpanda
+ name = "WahCoon (Top)"
+ icon_state = "fwah"
diff --git a/code/modules/mob/dead/new_player/sprite_accessories/socks.dm b/code/modules/mob/dead/new_player/sprite_accessories/socks.dm
index 19ec677a72..ffb808eede 100644
--- a/code/modules/mob/dead/new_player/sprite_accessories/socks.dm
+++ b/code/modules/mob/dead/new_player/sprite_accessories/socks.dm
@@ -20,6 +20,10 @@
name = "Knee-high - Bee"
icon_state = "bee_knee"
+/datum/sprite_accessory/underwear/socks/christmas_knee
+ name = "Knee-High - Christmas"
+ icon_state = "christmas_knee"
+
/datum/sprite_accessory/underwear/socks/commie_knee
name = "Knee-High - Commie"
icon_state = "commie_knee"
@@ -32,6 +36,14 @@
name = "Knee-high - Rainbow"
icon_state = "rainbow_knee"
+/datum/sprite_accessory/underwear/socks/candycaner_knee
+ name = "Knee-High - Red Candy Cane"
+ icon_state = "candycaner_knee"
+
+/datum/sprite_accessory/underwear/socks/candycaneg_knee //ignore alphabetisation for ease of use in scenarios like this
+ name = "Knee-High - Green Candy Cane"
+ icon_state = "candycaneg_knee"
+
/datum/sprite_accessory/underwear/socks/striped_knee
name = "Knee-high - Striped"
icon_state = "striped_knee"
@@ -46,18 +58,6 @@
name = "Knee-High - UK"
icon_state = "uk_knee"
-/datum/sprite_accessory/underwear/socks/christmas_knee
- name = "Knee-High - Christmas"
- icon_state = "christmas_knee"
-
-/datum/sprite_accessory/underwear/socks/candycaner_knee
- name = "Knee-High - Red Candy Cane"
- icon_state = "candycaner_knee"
-
-/datum/sprite_accessory/underwear/socks/candycaneg_knee
- name = "Knee-High - Green Candy Cane"
- icon_state = "candycaneg_knee"
-
/datum/sprite_accessory/underwear/socks/socks_norm
name = "Normal"
icon_state = "socks_norm"
@@ -129,22 +129,34 @@
name = "Thigh-high - Bee"
icon_state = "bee_thigh"
+/datum/sprite_accessory/underwear/socks/christmas_thigh
+ name = "Thigh-high - Christmas"
+ icon_state = "christmas_thigh"
+
/datum/sprite_accessory/underwear/socks/commie_thigh
name = "Thigh-high - Commie"
icon_state = "commie_thigh"
-/datum/sprite_accessory/underwear/socks/usa_thigh
- name = "Thigh-high - Freedom"
- icon_state = "assblastusa_thigh"
-
/datum/sprite_accessory/underwear/socks/fishnet
name = "Thigh-high - Fishnet"
icon_state = "fishnet"
+/datum/sprite_accessory/underwear/socks/usa_thigh
+ name = "Thigh-high - Freedom"
+ icon_state = "assblastusa_thigh"
+
/datum/sprite_accessory/underwear/socks/rainbow_thigh
name = "Thigh-high - Rainbow"
icon_state = "rainbow_thigh"
+/datum/sprite_accessory/underwear/socks/candycaner_thigh
+ name = "Thigh-high - Red Candy Cane"
+ icon_state = "candycaner_thigh"
+
+/datum/sprite_accessory/underwear/socks/candycaneg_thigh
+ name = "Thigh-high - Green Candy Cane"
+ icon_state = "candycaneg_thigh"
+
/datum/sprite_accessory/underwear/socks/striped_thigh
name = "Thigh-high - Striped"
icon_state = "striped_thigh"
@@ -157,16 +169,4 @@
/datum/sprite_accessory/underwear/socks/uk_thigh
name = "Thigh-high - UK"
- icon_state = "uk_thigh"
-
-/datum/sprite_accessory/underwear/socks/christmas_thigh
- name = "Thigh-high - Christmas"
- icon_state = "christmas_thigh"
-
-/datum/sprite_accessory/underwear/socks/candycaner_thigh
- name = "Thigh-high - Red Candy Cane"
- icon_state = "candycaner_thigh"
-
-/datum/sprite_accessory/underwear/socks/candycaneg_thigh
- name = "Thigh-high - Green Candy Cane"
- icon_state = "candycaneg_thigh"
\ No newline at end of file
+ icon_state = "uk_thigh"
\ No newline at end of file
diff --git a/code/modules/mob/dead/new_player/sprite_accessories/spines.dm b/code/modules/mob/dead/new_player/sprite_accessories/spines.dm
index 54749d5ea9..5d7207c934 100644
--- a/code/modules/mob/dead/new_player/sprite_accessories/spines.dm
+++ b/code/modules/mob/dead/new_player/sprite_accessories/spines.dm
@@ -2,10 +2,16 @@
icon = 'icons/mob/mutant_bodyparts.dmi'
relevant_layers = list(BODY_BEHIND_LAYER, BODY_ADJ_LAYER)
+/datum/sprite_accessory/spines/is_not_visible(var/mob/living/carbon/human/H, var/tauric)
+ return (!H.dna.features["spines"] || H.dna.features["spines"] == "None" || H.wear_suit && (H.wear_suit.flags_inv & HIDETAUR))
+
/datum/sprite_accessory/spines_animated
icon = 'icons/mob/mutant_bodyparts.dmi'
relevant_layers = list(BODY_BEHIND_LAYER, BODY_ADJ_LAYER)
+/datum/sprite_accessory/spines_animated/is_not_visible(var/mob/living/carbon/human/H, var/tauric)
+ return ((!H.dna.features["spines"] || H.dna.features["spines"] == "None" || H.wear_suit && (H.wear_suit.flags_inv & HIDETAUR)) || H.dna.species.mutant_bodyparts["tail"])
+
/datum/sprite_accessory/spines/none
name = "None"
icon_state = "none"
@@ -15,21 +21,13 @@
name = "None"
icon_state = "none"
-/datum/sprite_accessory/spines/short
- name = "Short"
- icon_state = "short"
+/datum/sprite_accessory/spines/aqautic
+ name = "Aquatic"
+ icon_state = "aqua"
-/datum/sprite_accessory/spines_animated/short
- name = "Short"
- icon_state = "short"
-
-/datum/sprite_accessory/spines/shortmeme
- name = "Short + Membrane"
- icon_state = "shortmeme"
-
-/datum/sprite_accessory/spines_animated/shortmeme
- name = "Short + Membrane"
- icon_state = "shortmeme"
+/datum/sprite_accessory/spines_animated/aqautic
+ name = "Aquatic"
+ icon_state = "aqua"
/datum/sprite_accessory/spines/long
name = "Long"
@@ -47,10 +45,18 @@
name = "Long + Membrane"
icon_state = "longmeme"
-/datum/sprite_accessory/spines/aqautic
- name = "Aquatic"
- icon_state = "aqua"
+/datum/sprite_accessory/spines/short
+ name = "Short"
+ icon_state = "short"
-/datum/sprite_accessory/spines_animated/aqautic
- name = "Aquatic"
- icon_state = "aqua"
+/datum/sprite_accessory/spines_animated/short
+ name = "Short"
+ icon_state = "short"
+
+/datum/sprite_accessory/spines/shortmeme
+ name = "Short + Membrane"
+ icon_state = "shortmeme"
+
+/datum/sprite_accessory/spines_animated/shortmeme
+ name = "Short + Membrane"
+ icon_state = "shortmeme"
diff --git a/code/modules/mob/dead/new_player/sprite_accessories/synthliz.dm b/code/modules/mob/dead/new_player/sprite_accessories/synthliz.dm
index a2884ab944..9addd15dca 100644
--- a/code/modules/mob/dead/new_player/sprite_accessories/synthliz.dm
+++ b/code/modules/mob/dead/new_player/sprite_accessories/synthliz.dm
@@ -1,36 +1,30 @@
//Synth snouts (This is the most important part)
-/datum/sprite_accessory/mam_snouts/synthliz
+/datum/sprite_accessory/snouts/mam_snouts/synthliz
recommended_species = list("synthliz")
icon = 'modular_citadel/icons/mob/synthliz_snouts.dmi'
color_src = MUTCOLORS
name = "Synthetic Lizard - Snout"
icon_state = "synthliz_basic"
-/datum/sprite_accessory/mam_snouts/synthliz/synthliz_under
+/datum/sprite_accessory/snouts/mam_snouts/synthliz/synthliz_under
icon = 'modular_citadel/icons/mob/synthliz_snouts.dmi'
color_src = MATRIXED
name = "Synthetic Lizard - Snout Under"
icon_state = "synthliz_under"
-/datum/sprite_accessory/mam_snouts/synthliz/synthliz_tert
+/datum/sprite_accessory/snouts/mam_snouts/synthliz/synthliz_tert
icon = 'modular_citadel/icons/mob/synthliz_snouts.dmi'
color_src = MATRIXED
name = "Synthetic Lizard - Snout Tertiary"
icon_state = "synthliz_tert"
-/datum/sprite_accessory/mam_snouts/synthliz/synthliz_tertunder
+/datum/sprite_accessory/snouts/mam_snouts/synthliz/synthliz_tertunder
icon = 'modular_citadel/icons/mob/synthliz_snouts.dmi'
color_src = MATRIXED
name = "Synthetic Lizard - Snout Tertiary Under"
icon_state = "synthliz_tertunder"
//Synth body markings
-/datum/sprite_accessory/mam_body_markings/synthliz
- recommended_species = list("synthliz")
- icon = 'modular_citadel/icons/mob/synthliz_body_markings.dmi'
- name = "Synthetic Lizard - Plates"
- icon_state = "synthlizscutes"
-
/datum/sprite_accessory/mam_body_markings/synthliz/synthliz_pecs
icon = 'modular_citadel/icons/mob/synthliz_body_markings.dmi'
name = "Synthetic Lizard - Pecs"
@@ -41,15 +35,21 @@
name = "Synthetic Lizard - Pecs Light"
icon_state = "synthlizpecslight"
+/datum/sprite_accessory/mam_body_markings/synthliz
+ recommended_species = list("synthliz")
+ icon = 'modular_citadel/icons/mob/synthliz_body_markings.dmi'
+ name = "Synthetic Lizard - Plates"
+ icon_state = "synthlizscutes"
+
//Synth tails
-/datum/sprite_accessory/mam_tails/synthliz
+/datum/sprite_accessory/tails/mam_tails/synthliz
recommended_species = list("synthliz")
icon = 'modular_citadel/icons/mob/synthliz_tails.dmi'
color_src = MUTCOLORS
name = "Synthetic Lizard"
icon_state = "synthliz"
-/datum/sprite_accessory/mam_tails_animated/synthliz
+/datum/sprite_accessory/tails_animated/mam_tails_animated/synthliz
recommended_species = list("synthliz")
icon = 'modular_citadel/icons/mob/synthliz_tails.dmi'
color_src = MUTCOLORS
@@ -70,17 +70,17 @@
name = "Synthetic Lizard - Curled"
icon_state = "synth_curled"
-/datum/sprite_accessory/antenna/synthliz/synthliz_thick
+/datum/sprite_accessory/antenna/synthliz/synth_horns
icon = 'modular_citadel/icons/mob/synthliz_antennas.dmi'
color_src = MUTCOLORS
- name = "Synthetic Lizard - Thick"
- icon_state = "synth_thick"
+ name = "Synthetic Lizard - Horns"
+ icon_state = "synth_horns"
-/datum/sprite_accessory/antenna/synthliz/synth_thicklight
+/datum/sprite_accessory/antenna/synthliz/synth_hornslight
icon = 'modular_citadel/icons/mob/synthliz_antennas.dmi'
color_src = MATRIXED
- name = "Synthetic Lizard - Thick Light"
- icon_state = "synth_thicklight"
+ name = "Synthetic Lizard - Horns Light"
+ icon_state = "synth_hornslight"
/datum/sprite_accessory/antenna/synthliz/synth_short
icon = 'modular_citadel/icons/mob/synthliz_antennas.dmi'
@@ -100,17 +100,17 @@
name = "Synthetic Lizard - Sharp Light"
icon_state = "synth_sharplight"
-/datum/sprite_accessory/antenna/synthliz/synth_horns
+/datum/sprite_accessory/antenna/synthliz/synthliz_thick
icon = 'modular_citadel/icons/mob/synthliz_antennas.dmi'
color_src = MUTCOLORS
- name = "Synthetic Lizard - Horns"
- icon_state = "synth_horns"
+ name = "Synthetic Lizard - Thick"
+ icon_state = "synth_thick"
-/datum/sprite_accessory/antenna/synthliz/synth_hornslight
+/datum/sprite_accessory/antenna/synthliz/synth_thicklight
icon = 'modular_citadel/icons/mob/synthliz_antennas.dmi'
color_src = MATRIXED
- name = "Synthetic Lizard - Horns Light"
- icon_state = "synth_hornslight"
+ name = "Synthetic Lizard - Thick Light"
+ icon_state = "synth_thicklight"
//Synth Taurs (Ported from Virgo)
/datum/sprite_accessory/taur/synthliz
diff --git a/code/modules/mob/dead/new_player/sprite_accessories/tails.dm b/code/modules/mob/dead/new_player/sprite_accessories/tails.dm
index 33dbd7059f..d9e2de1525 100644
--- a/code/modules/mob/dead/new_player/sprite_accessories/tails.dm
+++ b/code/modules/mob/dead/new_player/sprite_accessories/tails.dm
@@ -3,6 +3,9 @@
mutant_part_string = "tail"
relevant_layers = list(BODY_BEHIND_LAYER, BODY_FRONT_LAYER)
+/datum/sprite_accessory/tails/is_not_visible(var/mob/living/carbon/human/H, var/tauric)
+ return ((H.wear_suit && (H.wear_suit.flags_inv & HIDETAUR)) || tauric)
+
/datum/sprite_accessory/tails_animated
icon = 'icons/mob/mutant_bodyparts.dmi'
mutant_part_string = "tailwag"
@@ -12,38 +15,10 @@
************* Lizard Tails ****************
*******************************************/
-/datum/sprite_accessory/tails/lizard/smooth
- name = "Smooth"
- icon_state = "smooth"
-
-/datum/sprite_accessory/tails_animated/lizard/smooth
- name = "Smooth"
- icon_state = "smooth"
-
-/datum/sprite_accessory/tails/lizard/dtiger
- name = "Dark Tiger"
- icon_state = "dtiger"
-
-/datum/sprite_accessory/tails_animated/lizard/dtiger
- name = "Dark Tiger"
- icon_state = "dtiger"
-
-/datum/sprite_accessory/tails/lizard/ltiger
- name = "Light Tiger"
- icon_state = "ltiger"
-
-/datum/sprite_accessory/tails_animated/lizard/ltiger
- name = "Light Tiger"
- icon_state = "ltiger"
-
-/datum/sprite_accessory/tails/lizard/spikes
- name = "Spikes"
- icon_state = "spikes"
-
-/datum/sprite_accessory/tails_animated/lizard/spikes
- name = "Spikes"
- icon_state = "spikes"
+/datum/sprite_accessory/tails_animated/lizard/is_not_visible(var/mob/living/carbon/human/H, var/tauric)
+ return (((H.wear_suit && (H.wear_suit.flags_inv & HIDETAUR)) || tauric) || H.dna.species.mutant_bodyparts["tail_lizard"])
+//this goes first regardless of alphabetical order
/datum/sprite_accessory/tails/lizard/none
name = "None"
icon_state = "None"
@@ -66,11 +41,13 @@
color_src = MATRIXED
icon = 'modular_citadel/icons/mob/mam_tails.dmi'
-/datum/sprite_accessory/body_markings/guilmon
- name = "Guilmon"
- icon_state = "guilmon"
- color_src = MATRIXED
- icon = 'modular_citadel/icons/mob/markings_notmammals.dmi'
+/datum/sprite_accessory/tails/lizard/dtiger
+ name = "Dark Tiger"
+ icon_state = "dtiger"
+
+/datum/sprite_accessory/tails_animated/lizard/dtiger
+ name = "Dark Tiger"
+ icon_state = "dtiger"
/datum/sprite_accessory/tails/lizard/guilmon
name = "Guilmon"
@@ -84,6 +61,30 @@
color_src = MATRIXED
icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+/datum/sprite_accessory/tails/lizard/ltiger
+ name = "Light Tiger"
+ icon_state = "ltiger"
+
+/datum/sprite_accessory/tails_animated/lizard/ltiger
+ name = "Light Tiger"
+ icon_state = "ltiger"
+
+/datum/sprite_accessory/tails/lizard/smooth
+ name = "Smooth"
+ icon_state = "smooth"
+
+/datum/sprite_accessory/tails_animated/lizard/smooth
+ name = "Smooth"
+ icon_state = "smooth"
+
+/datum/sprite_accessory/tails/lizard/spikes
+ name = "Spikes"
+ icon_state = "spikes"
+
+/datum/sprite_accessory/tails_animated/lizard/spikes
+ name = "Spikes"
+ icon_state = "spikes"
+
/******************************************
************** Human Tails ****************
*******************************************/
@@ -98,17 +99,8 @@
icon_state = "none"
relevant_layers = null
-/datum/sprite_accessory/tails/human/ailurus
- name = "Red Panda"
- icon_state = "wah"
- icon = 'modular_citadel/icons/mob/mam_tails.dmi'
- color_src = MATRIXED
-
-/datum/sprite_accessory/tails_animated/human/ailurus
- name = "Red Panda"
- icon_state = "wah"
- icon = 'modular_citadel/icons/mob/mam_tails.dmi'
- color_src = MATRIXED
+/datum/sprite_accessory/tails_animated/human/is_not_visible(var/mob/living/carbon/human/H, var/tauric)
+ return (((H.wear_suit && (H.wear_suit.flags_inv & HIDETAUR)) || tauric)|| H.dna.species.mutant_bodyparts["tail_human"])
/datum/sprite_accessory/tails/human/axolotl
name = "Axolotl"
@@ -122,22 +114,22 @@
icon = 'modular_citadel/icons/mob/mam_tails.dmi'
color_src = MATRIXED
-/datum/sprite_accessory/mam_tails/batl
+/datum/sprite_accessory/tails/mam_tails/batl
name = "Bat (Long)"
icon = 'modular_citadel/icons/mob/mam_tails.dmi'
icon_state = "batl"
-/datum/sprite_accessory/mam_tails_animated/batl
+/datum/sprite_accessory/tails_animated/mam_tails_animated/batl
name = "Bat (Long)"
icon = 'modular_citadel/icons/mob/mam_tails.dmi'
icon_state = "batl"
-/datum/sprite_accessory/mam_tails/bats
+/datum/sprite_accessory/tails/mam_tails/bats
name = "Bat (Short)"
icon = 'modular_citadel/icons/mob/mam_tails.dmi'
icon_state = "bats"
-/datum/sprite_accessory/mam_tails_animated/bats
+/datum/sprite_accessory/tails_animated/mam_tails_animated/bats
name = "Bat (Short)"
icon = 'modular_citadel/icons/mob/mam_tails.dmi'
icon_state = "bats"
@@ -190,6 +182,14 @@
icon = 'modular_citadel/icons/mob/mam_tails.dmi'
color_src = MATRIXED
+/datum/sprite_accessory/tails/human/corvid
+ name = "Corvid"
+ icon_state = "crow"
+
+/datum/sprite_accessory/tails_animated/human/corvid
+ name = "Corvid"
+ icon_state = "crow"
+
/datum/sprite_accessory/tails/human/cow
name = "Cow"
icon_state = "cow"
@@ -202,13 +202,25 @@
icon = 'modular_citadel/icons/mob/mam_tails.dmi'
color_src = MATRIXED
-/datum/sprite_accessory/tails/human/corvid
- name = "Corvid"
- icon_state = "crow"
+/datum/sprite_accessory/tails/human/dtiger
+ name = "Dark Tiger"
+ icon_state = "dtiger"
-/datum/sprite_accessory/tails_animated/human/corvid
- name = "Corvid"
- icon_state = "crow"
+/datum/sprite_accessory/tails_animated/human/dtiger
+ name = "Dark Tiger"
+ icon_state = "dtiger"
+
+/datum/sprite_accessory/tails/human/datashark
+ name = "datashark"
+ icon_state = "datashark"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+
+/datum/sprite_accessory/tails_animated/human/datashark
+ name = "datashark"
+ icon_state = "datashark"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
/datum/sprite_accessory/tails/human/eevee
name = "Eevee"
@@ -289,7 +301,7 @@
color_src = MATRIXED
/datum/sprite_accessory/tails_animated/human/insect
- name = "insect"
+ name = "Insect"
icon_state = "insect"
icon = 'modular_citadel/icons/mob/mam_tails.dmi'
color_src = MATRIXED
@@ -306,6 +318,14 @@
color_src = MATRIXED
icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+/datum/sprite_accessory/tails/human/ltiger
+ name = "Light Tiger"
+ icon_state = "ltiger"
+
+/datum/sprite_accessory/tails_animated/human/ltiger
+ name = "Light Tiger"
+ icon_state = "ltiger"
+
/datum/sprite_accessory/tails/human/murid
name = "Murid"
icon_state = "murid"
@@ -318,18 +338,6 @@
color_src = MATRIXED
icon = 'modular_citadel/icons/mob/mam_tails.dmi'
-/datum/sprite_accessory/tails/human/otie
- name = "Otusian"
- icon_state = "otie"
- color_src = MATRIXED
- icon = 'modular_citadel/icons/mob/mam_tails.dmi'
-
-/datum/sprite_accessory/tails_animated/human/otie
- name = "Otusian"
- icon_state = "otie"
- color_src = MATRIXED
- icon = 'modular_citadel/icons/mob/mam_tails.dmi'
-
/datum/sprite_accessory/tails/orca
name = "Orca"
icon_state = "orca"
@@ -342,15 +350,15 @@
color_src = MATRIXED
icon = 'modular_citadel/icons/mob/mam_tails.dmi'
-/datum/sprite_accessory/tails/human/pede
- name = "Scolipede"
- icon_state = "pede"
+/datum/sprite_accessory/tails/human/otie
+ name = "Otusian"
+ icon_state = "otie"
color_src = MATRIXED
icon = 'modular_citadel/icons/mob/mam_tails.dmi'
-/datum/sprite_accessory/tails_animated/human/pede
- name = "Scolipede"
- icon_state = "pede"
+/datum/sprite_accessory/tails_animated/human/otie
+ name = "Otusian"
+ icon_state = "otie"
color_src = MATRIXED
icon = 'modular_citadel/icons/mob/mam_tails.dmi'
@@ -366,6 +374,30 @@
color_src = MATRIXED
icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+/datum/sprite_accessory/tails/human/ailurus
+ name = "Red Panda"
+ icon_state = "wah"
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/tails_animated/human/ailurus
+ name = "Red Panda"
+ icon_state = "wah"
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+ color_src = MATRIXED
+
+/datum/sprite_accessory/tails/human/pede
+ name = "Scolipede"
+ icon_state = "pede"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+
+/datum/sprite_accessory/tails_animated/human/pede
+ name = "Scolipede"
+ icon_state = "pede"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+
/datum/sprite_accessory/tails/human/sergal
name = "Sergal"
icon_state = "sergal"
@@ -378,6 +410,18 @@
color_src = MATRIXED
icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+/datum/sprite_accessory/tails/human/shark
+ name = "Shark"
+ icon_state = "shark"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+
+/datum/sprite_accessory/tails_animated/human/shark
+ name = "Shark"
+ icon_state = "shark"
+ color_src = MATRIXED
+ icon = 'modular_citadel/icons/mob/mam_tails.dmi'
+
/datum/sprite_accessory/tails/human/skunk
name = "skunk"
icon_state = "skunk"
@@ -406,30 +450,6 @@
name = "Spikes"
icon_state = "spikes"
-/datum/sprite_accessory/tails/human/shark
- name = "Shark"
- icon_state = "shark"
- color_src = MATRIXED
- icon = 'modular_citadel/icons/mob/mam_tails.dmi'
-
-/datum/sprite_accessory/tails_animated/human/shark
- name = "Shark"
- icon_state = "shark"
- color_src = MATRIXED
- icon = 'modular_citadel/icons/mob/mam_tails.dmi'
-
-/datum/sprite_accessory/tails/human/datashark
- name = "datashark"
- icon_state = "datashark"
- color_src = MATRIXED
- icon = 'modular_citadel/icons/mob/mam_tails.dmi'
-
-/datum/sprite_accessory/tails_animated/human/datashark
- name = "datashark"
- icon_state = "datashark"
- color_src = MATRIXED
- icon = 'modular_citadel/icons/mob/mam_tails.dmi'
-
/datum/sprite_accessory/tails/human/straighttail
name = "Straight Tail"
icon_state = "straighttail"
@@ -486,22 +506,6 @@
color_src = MATRIXED
icon = 'modular_citadel/icons/mob/mam_tails.dmi'
-/datum/sprite_accessory/tails/human/dtiger
- name = "Dark Tiger"
- icon_state = "dtiger"
-
-/datum/sprite_accessory/tails_animated/human/dtiger
- name = "Dark Tiger"
- icon_state = "dtiger"
-
-/datum/sprite_accessory/tails/human/ltiger
- name = "Light Tiger"
- icon_state = "ltiger"
-
-/datum/sprite_accessory/tails_animated/human/ltiger
- name = "Light Tiger"
- icon_state = "ltiger"
-
/datum/sprite_accessory/tails/human/wolf
name = "Wolf"
icon_state = "wolf"
@@ -518,368 +522,371 @@
************** Furry Tails ****************
*******************************************/
-/datum/sprite_accessory/mam_tails
+/datum/sprite_accessory/tails/mam_tails
color_src = MATRIXED
icon = 'modular_citadel/icons/mob/mam_tails.dmi'
recommended_species = list("mammal", "slimeperson", "podweak", "felinid", "insect")
mutant_part_string = "tail"
relevant_layers = list(BODY_BEHIND_LAYER, BODY_FRONT_LAYER)
-/datum/sprite_accessory/mam_tails/none
+/datum/sprite_accessory/tails/mam_tails/none
name = "None"
icon_state = "none"
recommended_species = null
relevant_layers = null
-/datum/sprite_accessory/mam_tails_animated
+/datum/sprite_accessory/tails_animated/mam_tails_animated
color_src = MATRIXED
icon = 'modular_citadel/icons/mob/mam_tails.dmi'
mutant_part_string = "tailwag"
relevant_layers = list(BODY_BEHIND_LAYER, BODY_FRONT_LAYER)
-/datum/sprite_accessory/mam_tails_animated/none
+/datum/sprite_accessory/tails_animated/mam_tails_animated/is_not_visible(var/mob/living/carbon/human/H, var/tauric)
+ return (((H.wear_suit && (H.wear_suit.flags_inv & HIDETAUR)) || tauric) || H.dna.species.mutant_bodyparts["mam_tail"])
+
+/datum/sprite_accessory/tails_animated/mam_tails_animated/none
name = "None"
icon_state = "none"
relevant_layers = null
-/datum/sprite_accessory/mam_tails/ailurus
- name = "Red Panda"
- icon_state = "wah"
- extra = TRUE
-
-/datum/sprite_accessory/mam_tails_animated/ailurus
- name = "Red Panda"
- icon_state = "wah"
- extra = TRUE
-
-/datum/sprite_accessory/mam_tails/axolotl
+/datum/sprite_accessory/tails/mam_tails/axolotl
name = "Axolotl"
icon_state = "axolotl"
-/datum/sprite_accessory/mam_tails_animated/axolotl
+/datum/sprite_accessory/tails_animated/mam_tails_animated/axolotl
name = "Axolotl"
icon_state = "axolotl"
-/datum/sprite_accessory/mam_tails/batl
+/datum/sprite_accessory/tails/mam_tails/batl
name = "Bat (Long)"
icon_state = "batl"
-/datum/sprite_accessory/mam_tails_animated/batl
+/datum/sprite_accessory/tails_animated/mam_tails_animated/batl
name = "Bat (Long)"
icon_state = "batl"
-/datum/sprite_accessory/mam_tails/bats
+/datum/sprite_accessory/tails/mam_tails/bats
name = "Bat (Short)"
icon_state = "bats"
-/datum/sprite_accessory/mam_tails_animated/bats
+/datum/sprite_accessory/tails_animated/mam_tails_animated/bats
name = "Bat (Short)"
icon_state = "bats"
-/datum/sprite_accessory/mam_tails/bee
+/datum/sprite_accessory/tails/mam_tails/bee
name = "Bee"
icon_state = "bee"
-/datum/sprite_accessory/mam_tails_animated/bee
+/datum/sprite_accessory/tails_animated/mam_tails_animated/bee
name = "Bee"
icon_state = "bee"
-/datum/sprite_accessory/mam_tails/cat
+/datum/sprite_accessory/tails/mam_tails/cat
name = "Cat"
icon_state = "cat"
color_src = HAIR
-/datum/sprite_accessory/mam_tails_animated/cat
+/datum/sprite_accessory/tails_animated/mam_tails_animated/cat
name = "Cat"
icon_state = "cat"
color_src = HAIR
-/datum/sprite_accessory/mam_tails/catbig
+/datum/sprite_accessory/tails/mam_tails/catbig
name = "Cat, Big"
icon_state = "catbig"
-/datum/sprite_accessory/mam_tails_animated/catbig
+/datum/sprite_accessory/tails_animated/mam_tails_animated/catbig
name = "Cat, Big"
icon_state = "catbig"
-/datum/sprite_accessory/mam_tails/twocat
+/datum/sprite_accessory/tails/mam_tails/twocat
name = "Cat, Double"
icon_state = "twocat"
-/datum/sprite_accessory/mam_tails_animated/twocat
+/datum/sprite_accessory/tails_animated/mam_tails_animated/twocat
name = "Cat, Double"
icon_state = "twocat"
-/datum/sprite_accessory/mam_tails/corvid
+/datum/sprite_accessory/tails/mam_tails/corvid
name = "Corvid"
icon_state = "crow"
-/datum/sprite_accessory/mam_tails_animated/corvid
+/datum/sprite_accessory/tails_animated/mam_tails_animated/corvid
name = "Corvid"
icon_state = "crow"
-/datum/sprite_accessory/mam_tail/cow
+/datum/sprite_accessory/tails/mam_tail/cow
name = "Cow"
icon_state = "cow"
-/datum/sprite_accessory/mam_tails_animated/cow
+/datum/sprite_accessory/tails_animated/mam_tails_animated/cow
name = "Cow"
icon_state = "cow"
-/datum/sprite_accessory/mam_tails/eevee
- name = "Eevee"
- icon_state = "eevee"
-
-/datum/sprite_accessory/mam_tails_animated/eevee
- name = "Eevee"
- icon_state = "eevee"
-
-/datum/sprite_accessory/mam_tails/fennec
- name = "Fennec"
- icon_state = "fennec"
-
-/datum/sprite_accessory/mam_tails_animated/fennec
- name = "Fennec"
- icon_state = "fennec"
-
-/datum/sprite_accessory/mam_tails/human/fish
- name = "Fish"
- icon_state = "fish"
-
-/datum/sprite_accessory/mam_tails_animated/human/fish
- name = "Fish"
- icon_state = "fish"
-
-/datum/sprite_accessory/mam_tails/fox
- name = "Fox"
- icon_state = "fox"
-
-/datum/sprite_accessory/mam_tails_animated/fox
- name = "Fox"
- icon_state = "fox"
-
-/datum/sprite_accessory/mam_tails/hawk
- name = "Hawk"
- icon_state = "hawk"
-
-/datum/sprite_accessory/mam_tails_animated/hawk
- name = "Hawk"
- icon_state = "hawk"
-
-/datum/sprite_accessory/mam_tails/horse
- name = "Horse"
- icon_state = "horse"
- color_src = HAIR
-
-/datum/sprite_accessory/mam_tails_animated/horse
- name = "Horse"
- icon_state = "horse"
- color_src = HAIR
-
-/datum/sprite_accessory/mam_tails/husky
- name = "Husky"
- icon_state = "husky"
-
-/datum/sprite_accessory/mam_tails_animated/husky
- name = "Husky"
- icon_state = "husky"
-
-datum/sprite_accessory/mam_tails/insect
- name = "Insect"
- icon_state = "insect"
-
-/datum/sprite_accessory/mam_tails_animated/insect
- name = "Insect"
- icon_state = "insect"
-
-/datum/sprite_accessory/mam_tails/kangaroo
- name = "kangaroo"
- icon_state = "kangaroo"
-
-/datum/sprite_accessory/mam_tails_animated/kangaroo
- name = "kangaroo"
- icon_state = "kangaroo"
-
-/datum/sprite_accessory/mam_tails/kitsune
- name = "Kitsune"
- icon_state = "kitsune"
-
-/datum/sprite_accessory/mam_tails_animated/kitsune
- name = "Kitsune"
- icon_state = "kitsune"
-
-/datum/sprite_accessory/mam_tails/lab
- name = "Lab"
- icon_state = "lab"
-
-/datum/sprite_accessory/mam_tails_animated/lab
- name = "Lab"
- icon_state = "lab"
-
-/datum/sprite_accessory/mam_tails/murid
- name = "Murid"
- icon_state = "murid"
-
-/datum/sprite_accessory/mam_tails_animated/murid
- name = "Murid"
- icon_state = "murid"
-
-/datum/sprite_accessory/mam_tails/otie
- name = "Otusian"
- icon_state = "otie"
-
-/datum/sprite_accessory/mam_tails_animated/otie
- name = "Otusian"
- icon_state = "otie"
-
-/datum/sprite_accessory/mam_tails/orca
- name = "Orca"
- icon_state = "orca"
-
-/datum/sprite_accessory/mam_tails_animated/orca
- name = "Orca"
- icon_state = "orca"
-
-/datum/sprite_accessory/mam_tails/pede
- name = "Scolipede"
- icon_state = "pede"
-
-/datum/sprite_accessory/mam_tails_animated/pede
- name = "Scolipede"
- icon_state = "pede"
-
-/datum/sprite_accessory/mam_tails/rabbit
- name = "Rabbit"
- icon_state = "rabbit"
-
-/datum/sprite_accessory/mam_tails_animated/rabbit
- name = "Rabbit"
- icon_state = "rabbit"
-
-/datum/sprite_accessory/mam_tails/sergal
- name = "Sergal"
- icon_state = "sergal"
-
-/datum/sprite_accessory/mam_tails_animated/sergal
- name = "Sergal"
- icon_state = "sergal"
-
-/datum/sprite_accessory/mam_tails/skunk
- name = "Skunk"
- icon_state = "skunk"
-
-/datum/sprite_accessory/mam_tails_animated/skunk
- name = "Skunk"
- icon_state = "skunk"
-
-/datum/sprite_accessory/mam_tails/smooth
- name = "Smooth"
- icon_state = "smooth"
- color_src = MUTCOLORS
- icon = 'icons/mob/mutant_bodyparts.dmi'
-
-/datum/sprite_accessory/mam_tails_animated/smooth
- name = "Smooth"
- icon_state = "smooth"
- color_src = MUTCOLORS
- icon = 'icons/mob/mutant_bodyparts.dmi'
-
-/datum/sprite_accessory/mam_tails_animated/spikes
- name = "Spikes"
- icon_state = "spikes"
- color_src = MUTCOLORS
- icon = 'icons/mob/mutant_bodyparts.dmi'
-
-/datum/sprite_accessory/mam_tails/spikes
- name = "Spikes"
- icon_state = "spikes"
- color_src = MUTCOLORS
- icon = 'icons/mob/mutant_bodyparts.dmi'
-
-/datum/sprite_accessory/mam_tails/shark
- name = "Shark"
- icon_state = "shark"
-
-/datum/sprite_accessory/mam_tails_animated/shark
- name = "Shark"
- icon_state = "shark"
-
-/datum/sprite_accessory/mam_tails/shepherd
- name = "Shepherd"
- icon_state = "shepherd"
-
-/datum/sprite_accessory/mam_tails_animated/shepherd
- name = "Shepherd"
- icon_state = "shepherd"
-
-/datum/sprite_accessory/mam_tails/straighttail
- name = "Straight Tail"
- icon_state = "straighttail"
-
-/datum/sprite_accessory/mam_tails_animated/straighttail
- name = "Straight Tail"
- icon_state = "straighttail"
-
-/datum/sprite_accessory/mam_tails/squirrel
- name = "Squirrel"
- icon_state = "squirrel"
-
-/datum/sprite_accessory/mam_tails_animated/squirrel
- name = "Squirrel"
- icon_state = "squirrel"
-
-/datum/sprite_accessory/mam_tails/tamamo_kitsune
- name = "Tamamo Kitsune Tails"
- icon_state = "9sune"
-
-/datum/sprite_accessory/mam_tails_animated/tamamo_kitsune
- name = "Tamamo Kitsune Tails"
- icon_state = "9sune"
-
-/datum/sprite_accessory/mam_tails/tentacle
- name = "Tentacle"
- icon_state = "tentacle"
-
-/datum/sprite_accessory/mam_tails_animated/tentacle
- name = "Tentacle"
- icon_state = "tentacle"
-
-/datum/sprite_accessory/mam_tails/tiger
- name = "Tiger"
- icon_state = "tiger"
-
-/datum/sprite_accessory/mam_tails_animated/tiger
- name = "Tiger"
- icon_state = "tiger"
-
-/datum/sprite_accessory/mam_tails/dtiger
+/datum/sprite_accessory/tails/mam_tails/dtiger
name = "Dark Tiger"
icon_state = "dtiger"
color_src = MUTCOLORS
icon = 'icons/mob/mutant_bodyparts.dmi'
-/datum/sprite_accessory/mam_tails_animated/dtiger
+/datum/sprite_accessory/tails_animated/mam_tails_animated/dtiger
name = "Dark Tiger"
icon_state = "dtiger"
color_src = MUTCOLORS
icon = 'icons/mob/mutant_bodyparts.dmi'
-/datum/sprite_accessory/mam_tails/ltiger
+/datum/sprite_accessory/tails/mam_tails/eevee
+ name = "Eevee"
+ icon_state = "eevee"
+
+/datum/sprite_accessory/tails_animated/mam_tails_animated/eevee
+ name = "Eevee"
+ icon_state = "eevee"
+
+/datum/sprite_accessory/tails/mam_tails/fennec
+ name = "Fennec"
+ icon_state = "fennec"
+
+/datum/sprite_accessory/tails_animated/mam_tails_animated/fennec
+ name = "Fennec"
+ icon_state = "fennec"
+
+/datum/sprite_accessory/tails/mam_tails/human/fish
+ name = "Fish"
+ icon_state = "fish"
+
+/datum/sprite_accessory/tails_animated/mam_tails_animated/human/fish
+ name = "Fish"
+ icon_state = "fish"
+
+/datum/sprite_accessory/tails/mam_tails/fox
+ name = "Fox"
+ icon_state = "fox"
+
+/datum/sprite_accessory/tails_animated/mam_tails_animated/fox
+ name = "Fox"
+ icon_state = "fox"
+
+/datum/sprite_accessory/tails/mam_tails/hawk
+ name = "Hawk"
+ icon_state = "hawk"
+
+/datum/sprite_accessory/tails_animated/mam_tails_animated/hawk
+ name = "Hawk"
+ icon_state = "hawk"
+
+/datum/sprite_accessory/tails/mam_tails/horse
+ name = "Horse"
+ icon_state = "horse"
+ color_src = HAIR
+
+/datum/sprite_accessory/tails_animated/mam_tails_animated/horse
+ name = "Horse"
+ icon_state = "horse"
+ color_src = HAIR
+
+/datum/sprite_accessory/tails/mam_tails/husky
+ name = "Husky"
+ icon_state = "husky"
+
+/datum/sprite_accessory/tails_animated/mam_tails_animated/husky
+ name = "Husky"
+ icon_state = "husky"
+
+datum/sprite_accessory/tails/mam_tails/insect
+ name = "Insect"
+ icon_state = "insect"
+
+/datum/sprite_accessory/tails_animated/mam_tails_animated/insect
+ name = "Insect"
+ icon_state = "insect"
+
+/datum/sprite_accessory/tails/mam_tails/kangaroo
+ name = "kangaroo"
+ icon_state = "kangaroo"
+
+/datum/sprite_accessory/tails_animated/mam_tails_animated/kangaroo
+ name = "kangaroo"
+ icon_state = "kangaroo"
+
+/datum/sprite_accessory/tails/mam_tails/kitsune
+ name = "Kitsune"
+ icon_state = "kitsune"
+
+/datum/sprite_accessory/tails_animated/mam_tails_animated/kitsune
+ name = "Kitsune"
+ icon_state = "kitsune"
+
+/datum/sprite_accessory/tails/mam_tails/lab
+ name = "Lab"
+ icon_state = "lab"
+
+/datum/sprite_accessory/tails_animated/mam_tails_animated/lab
+ name = "Lab"
+ icon_state = "lab"
+
+/datum/sprite_accessory/tails/mam_tails/ltiger
name = "Light Tiger"
icon_state = "ltiger"
color_src = MUTCOLORS
icon = 'icons/mob/mutant_bodyparts.dmi'
-/datum/sprite_accessory/mam_tails_animated/ltiger
+/datum/sprite_accessory/tails_animated/mam_tails_animated/ltiger
name = "Light Tiger"
icon_state = "ltiger"
color_src = MUTCOLORS
icon = 'icons/mob/mutant_bodyparts.dmi'
-/datum/sprite_accessory/mam_tails/wolf
+/datum/sprite_accessory/tails/mam_tails/murid
+ name = "Murid"
+ icon_state = "murid"
+
+/datum/sprite_accessory/tails_animated/mam_tails_animated/murid
+ name = "Murid"
+ icon_state = "murid"
+
+/datum/sprite_accessory/tails/mam_tails/orca
+ name = "Orca"
+ icon_state = "orca"
+
+/datum/sprite_accessory/tails_animated/mam_tails_animated/orca
+ name = "Orca"
+ icon_state = "orca"
+
+/datum/sprite_accessory/tails/mam_tails/otie
+ name = "Otusian"
+ icon_state = "otie"
+
+/datum/sprite_accessory/tails_animated/mam_tails_animated/otie
+ name = "Otusian"
+ icon_state = "otie"
+
+/datum/sprite_accessory/tails/mam_tails/rabbit
+ name = "Rabbit"
+ icon_state = "rabbit"
+
+/datum/sprite_accessory/tails_animated/mam_tails_animated/rabbit
+ name = "Rabbit"
+ icon_state = "rabbit"
+
+/datum/sprite_accessory/tails/mam_tails/ailurus
+ name = "Red Panda"
+ icon_state = "wah"
+ extra = TRUE
+
+/datum/sprite_accessory/tails_animated/mam_tails_animated/ailurus
+ name = "Red Panda"
+ icon_state = "wah"
+ extra = TRUE
+
+/datum/sprite_accessory/tails/mam_tails/pede
+ name = "Scolipede"
+ icon_state = "pede"
+
+/datum/sprite_accessory/tails_animated/mam_tails_animated/pede
+ name = "Scolipede"
+ icon_state = "pede"
+
+/datum/sprite_accessory/tails/mam_tails/sergal
+ name = "Sergal"
+ icon_state = "sergal"
+
+/datum/sprite_accessory/tails_animated/mam_tails_animated/sergal
+ name = "Sergal"
+ icon_state = "sergal"
+
+/datum/sprite_accessory/tails/mam_tails/shark
+ name = "Shark"
+ icon_state = "shark"
+
+/datum/sprite_accessory/tails_animated/mam_tails_animated/shark
+ name = "Shark"
+ icon_state = "shark"
+
+/datum/sprite_accessory/tails/mam_tails/shepherd
+ name = "Shepherd"
+ icon_state = "shepherd"
+
+/datum/sprite_accessory/tails_animated/mam_tails_animated/shepherd
+ name = "Shepherd"
+ icon_state = "shepherd"
+
+/datum/sprite_accessory/tails/mam_tails/skunk
+ name = "Skunk"
+ icon_state = "skunk"
+
+/datum/sprite_accessory/tails_animated/mam_tails_animated/skunk
+ name = "Skunk"
+ icon_state = "skunk"
+
+/datum/sprite_accessory/tails/mam_tails/smooth
+ name = "Smooth"
+ icon_state = "smooth"
+ color_src = MUTCOLORS
+ icon = 'icons/mob/mutant_bodyparts.dmi'
+
+/datum/sprite_accessory/tails_animated/mam_tails_animated/smooth
+ name = "Smooth"
+ icon_state = "smooth"
+ color_src = MUTCOLORS
+ icon = 'icons/mob/mutant_bodyparts.dmi'
+
+/datum/sprite_accessory/tails_animated/mam_tails_animated/spikes
+ name = "Spikes"
+ icon_state = "spikes"
+ color_src = MUTCOLORS
+ icon = 'icons/mob/mutant_bodyparts.dmi'
+
+/datum/sprite_accessory/tails/mam_tails/spikes
+ name = "Spikes"
+ icon_state = "spikes"
+ color_src = MUTCOLORS
+ icon = 'icons/mob/mutant_bodyparts.dmi'
+
+/datum/sprite_accessory/tails/mam_tails/straighttail
+ name = "Straight Tail"
+ icon_state = "straighttail"
+
+/datum/sprite_accessory/tails_animated/mam_tails_animated/straighttail
+ name = "Straight Tail"
+ icon_state = "straighttail"
+
+/datum/sprite_accessory/tails/mam_tails/squirrel
+ name = "Squirrel"
+ icon_state = "squirrel"
+
+/datum/sprite_accessory/tails_animated/mam_tails_animated/squirrel
+ name = "Squirrel"
+ icon_state = "squirrel"
+
+/datum/sprite_accessory/tails/mam_tails/tamamo_kitsune
+ name = "Tamamo Kitsune Tails"
+ icon_state = "9sune"
+
+/datum/sprite_accessory/tails_animated/mam_tails_animated/tamamo_kitsune
+ name = "Tamamo Kitsune Tails"
+ icon_state = "9sune"
+
+/datum/sprite_accessory/tails/mam_tails/tentacle
+ name = "Tentacle"
+ icon_state = "tentacle"
+
+/datum/sprite_accessory/tails_animated/mam_tails_animated/tentacle
+ name = "Tentacle"
+ icon_state = "tentacle"
+
+/datum/sprite_accessory/tails/mam_tails/tiger
+ name = "Tiger"
+ icon_state = "tiger"
+
+/datum/sprite_accessory/tails_animated/mam_tails_animated/tiger
+ name = "Tiger"
+ icon_state = "tiger"
+
+/datum/sprite_accessory/tails/mam_tails/wolf
name = "Wolf"
icon_state = "wolf"
-/datum/sprite_accessory/mam_tails_animated/wolf
+/datum/sprite_accessory/tails_animated/mam_tails_animated/wolf
name = "Wolf"
icon_state = "wolf"
diff --git a/code/modules/mob/dead/new_player/sprite_accessories/undershirt.dm b/code/modules/mob/dead/new_player/sprite_accessories/undershirt.dm
index 73233f3e09..1be02c207e 100644
--- a/code/modules/mob/dead/new_player/sprite_accessories/undershirt.dm
+++ b/code/modules/mob/dead/new_player/sprite_accessories/undershirt.dm
@@ -12,6 +12,38 @@
// please make sure they're sorted alphabetically and categorized
+/datum/sprite_accessory/underwear/top/cowboyshirt
+ name = "Cowboy Shirt Black"
+ icon_state = "cowboyshirt"
+
+/datum/sprite_accessory/underwear/top/cowboyshirt/red
+ name = "Cowboy Shirt Red"
+ icon_state = "cowboyshirt_red"
+
+/datum/sprite_accessory/underwear/top/cowboyshirt/navy
+ name = "Cowboy Shirt Navy"
+ icon_state = "cowboyshirt_navy"
+
+/datum/sprite_accessory/underwear/top/cowboyshirt/white
+ name = "Cowboy Shirt White"
+ icon_state = "cowboyshirt_white"
+
+/datum/sprite_accessory/underwear/top/cowboyshirt/s
+ name = "Cowboy Shirt Shortsleeved Black"
+ icon_state = "cowboyshirt_s"
+
+/datum/sprite_accessory/underwear/top/cowboyshirt/red/s
+ name = "Cowboy Shirt Shortsleeved Red"
+ icon_state = "cowboyshirt_reds"
+
+/datum/sprite_accessory/underwear/top/cowboyshirt/navy/s
+ name = "Cowboy Shirt Shortsleeved Navy"
+ icon_state = "cowboyshirt_navys"
+
+/datum/sprite_accessory/underwear/top/cowboyshirt/white/s
+ name = "Cowboy Shirt Shortsleeved White"
+ icon_state = "cowboyshirt_whites"
+
/datum/sprite_accessory/underwear/top/longjon
name = "Long John Shirt"
icon_state = "ljont"
@@ -30,36 +62,6 @@
icon_state = "undershirt"
has_color = TRUE
-/datum/sprite_accessory/underwear/top/bowlingw
- name = "Shirt - Bowling"
- icon_state = "bowlingw"
- has_color = TRUE
-
-/datum/sprite_accessory/underwear/top/bowling
- name = "Shirt, Bowling - Red"
- icon_state = "bowling"
-
-/datum/sprite_accessory/underwear/top/bowlingp
- name = "Shirt, Bowling - Pink"
- icon_state = "bowlingp"
-
-/datum/sprite_accessory/underwear/top/bowlinga
- name = "Shirt, Bowling - Aqua"
- icon_state = "bowlinga"
-
-/datum/sprite_accessory/underwear/top/bluejersey
- name = "Shirt, Jersey - Blue"
- icon_state = "shirt_bluejersey"
-
-/datum/sprite_accessory/underwear/top/redjersey
- name = "Shirt, Jersey - Red"
- icon_state = "shirt_redjersey"
-
-/datum/sprite_accessory/underwear/top/polo
- name = "Shirt - Polo"
- icon_state = "polo"
- has_color = TRUE
-
/datum/sprite_accessory/underwear/top/alienshirt
name = "Shirt - Alien"
icon_state = "shirt_alien"
@@ -72,6 +74,23 @@
name = "Shirt - Bee"
icon_state = "bee_shirt"
+/datum/sprite_accessory/underwear/top/bowlingw
+ name = "Shirt - Bowling"
+ icon_state = "bowlingw"
+ has_color = TRUE
+
+/datum/sprite_accessory/underwear/top/bowlinga
+ name = "Shirt, Bowling - Aqua"
+ icon_state = "bowlinga"
+
+/datum/sprite_accessory/underwear/top/bowling
+ name = "Shirt, Bowling - Red"
+ icon_state = "bowling"
+
+/datum/sprite_accessory/underwear/top/bowlingp
+ name = "Shirt, Bowling - Pink"
+ icon_state = "bowlingp"
+
/datum/sprite_accessory/underwear/top/clownshirt
name = "Shirt - Clown"
icon_state = "shirt_clown"
@@ -88,6 +107,14 @@
name = "Shirt - I Love NT"
icon_state = "ilovent"
+/datum/sprite_accessory/underwear/top/bluejersey
+ name = "Shirt, Jersey - Blue"
+ icon_state = "shirt_bluejersey"
+
+/datum/sprite_accessory/underwear/top/redjersey
+ name = "Shirt, Jersey - Red"
+ icon_state = "shirt_redjersey"
+
/datum/sprite_accessory/underwear/top/lover
name = "Shirt - Lover"
icon_state = "lover"
@@ -112,6 +139,11 @@
name = "Shirt - Pogoman"
icon_state = "pogoman"
+/datum/sprite_accessory/underwear/top/polo
+ name = "Shirt - Polo"
+ icon_state = "polo"
+ has_color = TRUE
+
/datum/sprite_accessory/underwear/top/question
name = "Shirt - Question"
icon_state = "shirt_question"
@@ -120,6 +152,23 @@
name = "Shirt - Skull"
icon_state = "shirt_skull"
+/datum/sprite_accessory/underwear/top/shortsleeve
+ name = "Shirt - Short Sleeved"
+ icon_state = "shortsleeve"
+ has_color = TRUE
+
+/datum/sprite_accessory/underwear/top/blueshirtsport
+ name = "Shirt, Sports - Blue"
+ icon_state = "blueshirtsport"
+
+/datum/sprite_accessory/underwear/top/greenshirtsport
+ name = "Shirt, Sports - Green"
+ icon_state = "greenshirtsport"
+
+/datum/sprite_accessory/underwear/top/redshirtsport
+ name = "Shirt, Sports - Red"
+ icon_state = "redshirtsport"
+
/datum/sprite_accessory/underwear/top/ss13
name = "Shirt - SS13"
icon_state = "shirt_ss13"
@@ -141,27 +190,6 @@
name = "Shirt - USA"
icon_state = "shirt_assblastusa"
-/datum/sprite_accessory/underwear/top/shortsleeve
- name = "Shirt - Short Sleeved"
- icon_state = "shortsleeve"
- has_color = TRUE
-
-/datum/sprite_accessory/underwear/top/blueshirtsport
- name = "Shirt, Sports - Blue"
- icon_state = "blueshirtsport"
-
-/datum/sprite_accessory/underwear/top/greenshirtsport
- name = "Shirt, Sports - Green"
- icon_state = "greenshirtsport"
-
-/datum/sprite_accessory/underwear/top/redshirtsport
- name = "Shirt, Sports - Red"
- icon_state = "redshirtsport"
-
-/datum/sprite_accessory/underwear/top/tankfire
- name = "Tank Top - Fire"
- icon_state = "tank_fire"
-
/datum/sprite_accessory/underwear/top/tanktop
name = "Tank Top"
icon_state = "tanktop"
@@ -172,6 +200,10 @@
icon_state = "tanktop_alt"
has_color = TRUE
+/datum/sprite_accessory/underwear/top/tankfire
+ name = "Tank Top - Fire"
+ icon_state = "tank_fire"
+
/datum/sprite_accessory/underwear/top/tanktop_midriff
name = "Tank Top - Midriff"
icon_state = "tank_midriff"
@@ -192,6 +224,8 @@
name = "Tank top - Sun"
icon_state = "tank_sun"
+//feminine accessories from here on
+
/datum/sprite_accessory/underwear/top/babydoll
name = "Baby-Doll"
icon_state = "babydoll"
@@ -210,15 +244,25 @@
has_color = TRUE
gender = FEMALE
-/datum/sprite_accessory/underwear/top/bra_thin
- name = "Bra - Thin"
- icon_state = "bra_thin"
- has_color = TRUE
+/datum/sprite_accessory/underwear/top/bra_beekini
+ name = "Bra - Bee-kini"
+ icon_state = "bra_bee-kini"
gender = FEMALE
-/datum/sprite_accessory/underwear/top/bra_kinky
- name = "Bra - Kinky Black"
- icon_state = "bra_kinky"
+/datum/sprite_accessory/underwear/top/bra_binder
+ name = "Bra (binder)"
+ icon_state = "bra_binder"
+ has_color = TRUE
+
+/datum/sprite_accessory/underwear/top/bra_binder_strapless
+ name = "Bra (binder, strapless)"
+ icon_state = "bra_binder_strapless"
+ has_color = TRUE
+
+
+/datum/sprite_accessory/underwear/top/bra_commie
+ name = "Bra - Commie"
+ icon_state = "bra_commie"
gender = FEMALE
/datum/sprite_accessory/underwear/top/bra_freedom
@@ -226,33 +270,17 @@
icon_state = "bra_assblastusa"
gender = FEMALE
-/datum/sprite_accessory/underwear/top/bra_commie
- name = "Bra - Commie"
- icon_state = "bra_commie"
- gender = FEMALE
-
-/datum/sprite_accessory/underwear/top/bra_beekini
- name = "Bra - Bee-kini"
- icon_state = "bra_bee-kini"
- gender = FEMALE
-
-/datum/sprite_accessory/underwear/top/bra_uk
- name = "Bra - UK"
- icon_state = "bra_uk"
- gender = FEMALE
-
-/datum/sprite_accessory/underwear/top/bra_neko
- name = "Bra - Neko"
- icon_state = "bra_neko"
- has_color = TRUE
- gender = FEMALE
-
/datum/sprite_accessory/underwear/top/halterneck_bra
name = "Bra - Halterneck"
icon_state = "halterneck_bra"
has_color = TRUE
gender = FEMALE
+/datum/sprite_accessory/underwear/top/bra_kinky
+ name = "Bra - Kinky Black"
+ icon_state = "bra_kinky"
+ gender = FEMALE
+
/datum/sprite_accessory/underwear/top/sports_bra
name = "Bra, Sports"
icon_state = "sports_bra"
@@ -283,9 +311,21 @@
has_color = TRUE
gender = FEMALE
-/datum/sprite_accessory/underwear/top/fishnet_sleeves
- name = "Fishnet - sleeves"
- icon_state = "fishnet_sleeves"
+/datum/sprite_accessory/underwear/top/bra_thin
+ name = "Bra - Thin"
+ icon_state = "bra_thin"
+ has_color = TRUE
+ gender = FEMALE
+
+/datum/sprite_accessory/underwear/top/bra_neko
+ name = "Bra - Neko"
+ icon_state = "bra_neko"
+ has_color = TRUE
+ gender = FEMALE
+
+/datum/sprite_accessory/underwear/top/bra_uk
+ name = "Bra - UK"
+ icon_state = "bra_uk"
gender = FEMALE
/datum/sprite_accessory/underwear/top/fishnet_gloves
@@ -293,6 +333,11 @@
icon_state = "fishnet_gloves"
gender = FEMALE
+/datum/sprite_accessory/underwear/top/fishnet_sleeves
+ name = "Fishnet - sleeves"
+ icon_state = "fishnet_sleeves"
+ gender = FEMALE
+
/datum/sprite_accessory/underwear/top/fishnet_base
name = "Fishnet - top"
icon_state = "fishnet_body"
@@ -315,39 +360,3 @@
icon_state = "tubetop"
has_color = TRUE
gender = FEMALE
-
-/datum/sprite_accessory/underwear/top/cowboyshirt
- name = "Cowboy Shirt Black"
- icon_state = "cowboyshirt"
-
-/datum/sprite_accessory/underwear/top/cowboyshirt/s
- name = "Cowboy Shirt Shortsleeved Black"
- icon_state = "cowboyshirt_s"
-
-/datum/sprite_accessory/underwear/top/cowboyshirt/white
- name = "Cowboy Shirt White"
- icon_state = "cowboyshirt_white"
-
-/datum/sprite_accessory/underwear/top/cowboyshirt/white/s
- name = "Cowboy Shirt Shortsleeved White"
- icon_state = "cowboyshirt_whites"
-
-/datum/sprite_accessory/underwear/top/cowboyshirt/navy
- name = "Cowboy Shirt Navy"
- icon_state = "cowboyshirt_navy"
-
-/datum/sprite_accessory/underwear/top/cowboyshirt/navy/s
- name = "Cowboy Shirt Shortsleeved Navy"
- icon_state = "cowboyshirt_navys"
-
-/datum/sprite_accessory/underwear/top/cowboyshirt/red
- name = "Cowboy Shirt Red"
- icon_state = "cowboyshirt_red"
-
-/datum/sprite_accessory/underwear/top/cowboyshirt/red/s
- name = "Cowboy Shirt Shortsleeved Red"
- icon_state = "cowboyshirt_reds"
-
-
-
-
diff --git a/code/modules/mob/dead/new_player/sprite_accessories/underwear.dm b/code/modules/mob/dead/new_player/sprite_accessories/underwear.dm
index 58d5e1ba88..edfeba79f1 100644
--- a/code/modules/mob/dead/new_player/sprite_accessories/underwear.dm
+++ b/code/modules/mob/dead/new_player/sprite_accessories/underwear.dm
@@ -10,18 +10,6 @@
icon_state = null
covers_groin = FALSE
-/datum/sprite_accessory/underwear/bottom/mankini
- name = "Mankini"
- icon_state = "mankini"
- has_color = TRUE
- gender = MALE
-
-/datum/sprite_accessory/underwear/bottom/male_kinky
- name = "Jockstrap"
- icon_state = "jockstrap"
- has_color = TRUE
- gender = MALE
-
/datum/sprite_accessory/underwear/bottom/briefs
name = "Briefs"
icon_state = "briefs"
@@ -77,6 +65,26 @@
has_digitigrade = TRUE
has_color = TRUE
+/datum/sprite_accessory/underwear/bottom/male_kinky
+ name = "Jockstrap"
+ icon_state = "jockstrap"
+ has_color = TRUE
+ gender = MALE
+
+/datum/sprite_accessory/underwear/bottom/longjon
+ name = "Long John Bottoms"
+ icon_state = "ljonb"
+ has_digitigrade = TRUE
+ has_color = TRUE
+
+/datum/sprite_accessory/underwear/bottom/mankini
+ name = "Mankini"
+ icon_state = "mankini"
+ has_color = TRUE
+ gender = MALE
+
+//feminine underwear from here on
+
/datum/sprite_accessory/underwear/bottom/panties
name = "Panties"
icon_state = "panties"
@@ -89,11 +97,6 @@
has_color = TRUE
gender = FEMALE
-/datum/sprite_accessory/underwear/bottom/fishnet_lower
- name = "Panties - Fishnet"
- icon_state = "fishnet_lower"
- gender = FEMALE
-
/datum/sprite_accessory/underwear/bottom/female_beekini
name = "Panties - Bee-kini"
icon_state = "panties_bee-kini"
@@ -104,6 +107,11 @@
icon_state = "panties_commie"
gender = FEMALE
+/datum/sprite_accessory/underwear/bottom/fishnet_lower
+ name = "Panties - Fishnet"
+ icon_state = "fishnet_lower"
+ gender = FEMALE
+
/datum/sprite_accessory/underwear/bottom/female_usastripe
name = "Panties - Freedom"
icon_state = "panties_assblastusa"
@@ -114,11 +122,6 @@
icon_state = "panties_kinky"
gender = FEMALE
-/datum/sprite_accessory/underwear/bottom/panties_uk
- name = "Panties - UK"
- icon_state = "panties_uk"
- gender = FEMALE
-
/datum/sprite_accessory/underwear/bottom/panties_neko
name = "Panties - Neko"
icon_state = "panties_neko"
@@ -149,17 +152,10 @@
has_color = TRUE
gender = FEMALE
-/datum/sprite_accessory/underwear/bottom/longjon
- name = "Long John Bottoms"
- icon_state = "ljonb"
- has_digitigrade = TRUE
- has_color = TRUE
-
-/datum/sprite_accessory/underwear/bottom/swimsuit_red
- name = "Swimsuit, One Piece - Red"
- icon_state = "swimming_red"
+/datum/sprite_accessory/underwear/bottom/panties_uk
+ name = "Panties - UK"
+ icon_state = "panties_uk"
gender = FEMALE
- covers_chest = TRUE
/datum/sprite_accessory/underwear/bottom/swimsuit
name = "Swimsuit, One Piece - Black"
@@ -173,6 +169,12 @@
gender = FEMALE
covers_chest = TRUE
+/datum/sprite_accessory/underwear/bottom/swimsuit_red
+ name = "Swimsuit, One Piece - Red"
+ icon_state = "swimming_red"
+ gender = FEMALE
+ covers_chest = TRUE
+
/datum/sprite_accessory/underwear/bottom/thong
name = "Thong"
icon_state = "thong"
@@ -184,5 +186,3 @@
icon_state = "thong_babydoll"
has_color = TRUE
gender = FEMALE
-
-
diff --git a/code/modules/mob/dead/new_player/sprite_accessories/wings.dm b/code/modules/mob/dead/new_player/sprite_accessories/wings.dm
index 34767a10f1..fb71bb483d 100644
--- a/code/modules/mob/dead/new_player/sprite_accessories/wings.dm
+++ b/code/modules/mob/dead/new_player/sprite_accessories/wings.dm
@@ -5,10 +5,16 @@
icon_state = "none"
relevant_layers = null
+/datum/sprite_accessory/wings/is_not_visible(var/mob/living/carbon/human/H, var/tauric)
+ return (!H.dna.features["wings"] || H.dna.features["wings"] == "None" || (H.wear_suit && (H.wear_suit.flags_inv & HIDEJUMPSUIT) && (!H.wear_suit.species_exception || !is_type_in_list(src, H.wear_suit.species_exception))))
+
/datum/sprite_accessory/wings_open
icon = 'icons/mob/wings.dmi'
relevant_layers = list(BODY_BEHIND_LAYER, BODY_ADJ_LAYER, BODY_FRONT_LAYER)
+/datum/sprite_accessory/wings_open/is_not_visible(var/mob/living/carbon/human/H, var/tauric)
+ return (H.wear_suit && (H.wear_suit.flags_inv & HIDEJUMPSUIT) && (!H.wear_suit.species_exception || !is_type_in_list(src, H.wear_suit.species_exception)) || H.dna.species.mutant_bodyparts["wings"])
+
/datum/sprite_accessory/wings_open/angel
name = "Angel"
icon_state = "angel"
@@ -52,6 +58,10 @@
dimension_y = 34
relevant_layers = list(BODY_BEHIND_LAYER, BODY_ADJ_LAYER, BODY_FRONT_LAYER)
+/datum/sprite_accessory/deco_wings/atlas
+ name = "Atlas"
+ icon_state = "atlas"
+
/datum/sprite_accessory/deco_wings/bat
name = "Bat"
icon_state = "bat"
@@ -60,6 +70,10 @@
name = "Bee"
icon_state = "bee"
+/datum/sprite_accessory/deco_wings/deathhead
+ name = "Deathshead"
+ icon_state = "deathhead"
+
/datum/sprite_accessory/deco_wings/fairy
name = "Fairy"
icon_state = "fairy"
@@ -68,14 +82,6 @@
name = "Feathery"
icon_state = "feathery"
-/datum/sprite_accessory/deco_wings/atlas
- name = "Atlas"
- icon_state = "atlas"
-
-/datum/sprite_accessory/deco_wings/deathhead
- name = "Deathshead"
- icon_state = "deathhead"
-
/datum/sprite_accessory/deco_wings/firewatch
name = "Firewatch"
icon_state = "firewatch"
@@ -144,6 +150,10 @@
icon_state = "none"
relevant_layers = null
+/datum/sprite_accessory/insect_wings/atlas
+ name = "Atlas"
+ icon_state = "atlas"
+
/datum/sprite_accessory/insect_wings/bat
name = "Bat"
icon_state = "bat"
@@ -152,6 +162,10 @@
name = "Bee"
icon_state = "bee"
+/datum/sprite_accessory/insect_wings/deathhead
+ name = "Deathshead"
+ icon_state = "deathhead"
+
/datum/sprite_accessory/insect_wings/fairy
name = "Fairy"
icon_state = "fairy"
@@ -160,14 +174,6 @@
name = "Feathery"
icon_state = "feathery"
-/datum/sprite_accessory/insect_wings/atlas
- name = "Atlas"
- icon_state = "atlas"
-
-/datum/sprite_accessory/insect_wings/deathhead
- name = "Deathshead"
- icon_state = "deathhead"
-
/datum/sprite_accessory/insect_wings/firewatch
name = "Firewatch"
icon_state = "firewatch"
@@ -176,6 +182,10 @@
name = "Gothic"
icon_state = "gothic"
+/datum/sprite_accessory/insect_wings/jungle
+ name = "Jungle"
+ icon_state = "jungle"
+
/datum/sprite_accessory/insect_wings/lovers
name = "Lovers"
icon_state = "lovers"
@@ -192,6 +202,10 @@
name = "Moon Fly"
icon_state = "moonfly"
+/datum/sprite_accessory/insect_wings/oakworm
+ name = "Oak Worm"
+ icon_state = "oakworm"
+
/datum/sprite_accessory/insect_wings/plain
name = "Plain"
icon_state = "plain"
@@ -224,14 +238,6 @@
name = "White Fly"
icon_state = "whitefly"
-/datum/sprite_accessory/insect_wings/oakworm
- name = "Oak Worm"
- icon_state = "oakworm"
-
-/datum/sprite_accessory/insect_wings/jungle
- name = "Jungle"
- icon_state = "jungle"
-
/datum/sprite_accessory/insect_wings/witchwing
name = "Witch Wing"
icon_state = "witchwing"
diff --git a/code/modules/mob/dead/observer/notificationprefs.dm b/code/modules/mob/dead/observer/notificationprefs.dm
index 160abd57e1..524ff80d5d 100644
--- a/code/modules/mob/dead/observer/notificationprefs.dm
+++ b/code/modules/mob/dead/observer/notificationprefs.dm
@@ -3,12 +3,10 @@
set name = "Notification preferences"
set desc = "Notification preferences"
- var/datum/notificationpanel/panel = new(usr)
+ var/datum/notificationpanel/panel = new(usr)
panel.ui_interact(usr)
-
-
/datum/notificationpanel
var/client/user
@@ -21,10 +19,13 @@
else
src.user = user
-/datum/notificationpanel/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.observer_state)
- ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+/datum/notificationpanel/ui_state(mob/user)
+ return GLOB.observer_state
+
+/datum/notificationpanel/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
- ui = new(user, src, ui_key, "NotificationPreferences", "Notification Preferences", 270, 360, master_ui, state)
+ ui = new(user, src, "NotificationPreferences")
ui.open()
/datum/notificationpanel/ui_data(mob/user)
@@ -35,8 +36,7 @@
"key" = key,
"enabled" = (user.ckey in GLOB.poll_ignore[key]),
"desc" = GLOB.poll_ignore_desc[key]
- ))
-
+ ))
/datum/notificationpanel/ui_act(action, params)
if(..())
diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm
index bb39639ec1..a0df1ee938 100644
--- a/code/modules/mob/dead/observer/observer.dm
+++ b/code/modules/mob/dead/observer/observer.dm
@@ -905,6 +905,22 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
else
to_chat(usr, "Can't become a pAI candidate while not dead!")
+/mob/dead/observer/verb/mafia_game_signup()
+ set category = "Ghost"
+ set name = "Signup for Mafia"
+ set desc = "Sign up for a game of Mafia to pass the time while dead."
+ mafia_signup()
+/mob/dead/observer/proc/mafia_signup()
+ if(!client)
+ return
+ if(!isobserver(src))
+ to_chat(usr, "You must be a ghost to join mafia!")
+ return
+ var/datum/mafia_controller/game = GLOB.mafia_game //this needs to change if you want multiple mafia games up at once.
+ if(!game)
+ game = create_mafia_game("mafia")
+ game.ui_interact(usr)
+
/mob/dead/observer/CtrlShiftClick(mob/user)
if(isobserver(user) && check_rights(R_SPAWN))
change_mob_type( /mob/living/carbon/human , null, null, TRUE) //always delmob, ghosts shouldn't be left lingering
diff --git a/code/modules/mob/dead/observer/orbit.dm b/code/modules/mob/dead/observer/orbit.dm
index eb23a5fb67..3aa5f8e302 100644
--- a/code/modules/mob/dead/observer/orbit.dm
+++ b/code/modules/mob/dead/observer/orbit.dm
@@ -6,9 +6,12 @@
qdel(src)
owner = new_owner
-/datum/orbit_menu/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.observer_state)
+/datum/orbit_menu/ui_state(mob/user)
+ return GLOB.observer_state
+
+/datum/orbit_menu/ui_interact(mob/user, datum/tgui/ui)
if (!ui)
- ui = new(user, src, ui_key, "Orbit", "Orbit", 350, 700, master_ui, state)
+ ui = new(user, src, "Orbit")
ui.open()
/datum/orbit_menu/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
@@ -16,11 +19,10 @@
return
if (action == "orbit")
- var/list/pois = getpois(skip_mindless = 1)
- var/atom/movable/poi = pois[params["name"]]
+ var/ref = params["ref"]
+ var/atom/movable/poi = (locate(ref) in GLOB.mob_list) || (locate(ref) in GLOB.poi_list)
if (poi != null)
owner.ManualFollow(poi)
- ui.close()
/datum/orbit_menu/ui_data(mob/user)
var/list/data = list()
@@ -39,6 +41,8 @@
var/poi = pois[name]
+ serialized["ref"] = REF(poi)
+
var/mob/M = poi
if (istype(M))
if (isobserver(M))
@@ -76,3 +80,8 @@
data["npcs"] = npcs
return data
+
+/datum/orbit_menu/ui_assets()
+ . = ..() || list()
+ . += get_asset_datum(/datum/asset/simple/orbit)
+
diff --git a/code/modules/mob/inventory.dm b/code/modules/mob/inventory.dm
index caebb9cf10..9ac270bf34 100644
--- a/code/modules/mob/inventory.dm
+++ b/code/modules/mob/inventory.dm
@@ -382,10 +382,20 @@
return 0
-//Outdated but still in use apparently. This should at least be a human proc.
-//Daily reminder to murder this - Remie.
+/**
+ * Used to return a list of equipped items on a mob; does not include held items (use get_all_gear)
+ *
+ * Argument(s):
+ * * Optional - include_pockets (TRUE/FALSE), whether or not to include the pockets and suit storage in the returned list
+ */
+
/mob/living/proc/get_equipped_items(include_pockets = FALSE)
- return
+ var/list/items = list()
+ for(var/obj/item/I in contents)
+ if(I.item_flags & IN_INVENTORY)
+ items += I
+ items -= held_items
+ return items
/mob/living/proc/unequip_everything()
var/list/items = list()
@@ -476,5 +486,19 @@
hand_bodyparts[i] = BP
..() //Don't redraw hands until we have organs for them
+
+//GetAllContents that is reasonable and not stupid
+/mob/living/carbon/proc/get_all_gear()
+ var/list/processing_list = get_equipped_items(include_pockets = TRUE) + held_items
+ listclearnulls(processing_list) // handles empty hands
+ var/i = 0
+ while(i < length(processing_list) )
+ var/atom/A = processing_list[++i]
+ if(SEND_SIGNAL(A, COMSIG_CONTAINS_STORAGE))
+ var/list/item_stuff = list()
+ SEND_SIGNAL(A, COMSIG_TRY_STORAGE_RETURN_INVENTORY, item_stuff)
+ processing_list += item_stuff
+ return processing_list
+
/mob/canReachInto(atom/user, atom/target, list/next, view_only, obj/item/tool)
return ..() && (user == src)
diff --git a/code/modules/mob/living/blood.dm b/code/modules/mob/living/blood.dm
index 36a6f6296b..cf1a158fc5 100644
--- a/code/modules/mob/living/blood.dm
+++ b/code/modules/mob/living/blood.dm
@@ -62,10 +62,6 @@
//Effects of bloodloss
var/word = pick("dizzy","woozy","faint")
switch(blood_volume)
- if(BLOOD_VOLUME_EXCESS to BLOOD_VOLUME_MAX_LETHAL)
- if(prob(15))
- to_chat(src, "Blood starts to tear your skin apart. You're going to burst!")
- gib()
if(BLOOD_VOLUME_MAXIMUM to BLOOD_VOLUME_EXCESS)
if(prob(10))
to_chat(src, "You feel terribly bloated.")
diff --git a/code/modules/mob/living/bloodcrawl.dm b/code/modules/mob/living/bloodcrawl.dm
index d3dbfac8c8..24d456bf8f 100644
--- a/code/modules/mob/living/bloodcrawl.dm
+++ b/code/modules/mob/living/bloodcrawl.dm
@@ -73,7 +73,7 @@
if(victim.stat == CONSCIOUS)
src.visible_message("[victim] kicks free of the blood pool just before entering it!", null, "You hear splashing and struggling.")
- else if(victim.reagents && victim.reagents.has_reagent(/datum/reagent/consumable/ethanol/demonsblood))
+ else if(victim.reagents?.has_reagent(/datum/reagent/consumable/ethanol/demonsblood))
visible_message("Something prevents [victim] from entering the pool!", "A strange force is blocking [victim] from entering!", "You hear a splash and a thud.")
else
victim.forceMove(src)
@@ -104,7 +104,7 @@
if(!victim)
return FALSE
- if(victim.reagents && victim.reagents.has_reagent(/datum/reagent/consumable/ethanol/devilskiss))
+ if(victim.reagents?.has_reagent(/datum/reagent/consumable/ethanol/devilskiss))
to_chat(src, "AAH! THEIR FLESH! IT BURNS!")
adjustBruteLoss(25) //I can't use adjustHealth() here because bloodcrawl affects /mob/living and adjustHealth() only affects simple mobs
var/found_bloodpool = FALSE
diff --git a/code/modules/mob/living/brain/emote.dm b/code/modules/mob/living/brain/emote.dm
index 540c7b3949..e1cb1a29cc 100644
--- a/code/modules/mob/living/brain/emote.dm
+++ b/code/modules/mob/living/brain/emote.dm
@@ -1,13 +1,3 @@
-/datum/emote/brain
- mob_type_allowed_typecache = list(/mob/living/brain)
- mob_type_blacklist_typecache = list()
-
-/datum/emote/brain/can_run_emote(mob/user, status_check = TRUE)
- . = ..()
- var/mob/living/brain/B = user
- if(!istype(B) || (!(B.container && istype(B.container, /obj/item/mmi))))
- return FALSE
-
/datum/emote/brain/alarm
key = "alarm"
message = "sounds an alarm."
diff --git a/code/modules/mob/living/carbon/alien/emote.dm b/code/modules/mob/living/carbon/alien/emote.dm
index 0ab448d2c5..963981b51e 100644
--- a/code/modules/mob/living/carbon/alien/emote.dm
+++ b/code/modules/mob/living/carbon/alien/emote.dm
@@ -1,6 +1,3 @@
-/datum/emote/living/alien
- mob_type_allowed_typecache = list(/mob/living/carbon/alien)
-
/datum/emote/living/alien/gnarl
key = "gnarl"
key_third_person = "gnarls"
diff --git a/code/modules/mob/living/carbon/alien/life.dm b/code/modules/mob/living/carbon/alien/life.dm
index d63686691d..0a2434055f 100644
--- a/code/modules/mob/living/carbon/alien/life.dm
+++ b/code/modules/mob/living/carbon/alien/life.dm
@@ -40,9 +40,6 @@
if(move_delay_add > 0)
move_delay_add = max(0, move_delay_add - rand(1, 2))
-/mob/living/carbon/alien/handle_changeling()
- return
-
/mob/living/carbon/alien/handle_fire()//Aliens on fire code
if(..())
return
diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm
index 3638e50fdc..790dbfd9a6 100644
--- a/code/modules/mob/living/carbon/carbon.dm
+++ b/code/modules/mob/living/carbon/carbon.dm
@@ -87,24 +87,15 @@
if(user != src && (user.a_intent == INTENT_HELP || user.a_intent == INTENT_DISARM))
for(var/datum/surgery/S in surgeries)
if(S.next_step(user,user.a_intent))
- return 1
+ return STOP_ATTACK_PROC_CHAIN
if(!all_wounds || !(user.a_intent == INTENT_HELP || user == src))
return ..()
- // The following priority/nonpriority searching is so that if we have two wounds on a limb that use the same item for treatment (gauze can bandage cuts AND splint broken bones),
- // we prefer whichever wound is not already treated (ignore the splinted broken bone for the open cut). If there's no priority wounds that this can treat, go through the
- // non-priority ones randomly.
- var/list/nonpriority_wounds = list()
- for(var/datum/wound/W in shuffle(all_wounds))
- if(!W.treat_priority)
- nonpriority_wounds += W
- else if(W.treat_priority && W.try_treating(I, user))
- return 1
-
- for(var/datum/wound/W in shuffle(nonpriority_wounds))
+ for(var/i in shuffle(all_wounds))
+ var/datum/wound/W = i
if(W.try_treating(I, user))
- return 1
+ return STOP_ATTACK_PROC_CHAIN
return ..()
@@ -1222,16 +1213,16 @@
/**
* generate_fake_scars()- for when you want to scar someone, but you don't want to hurt them first. These scars don't count for temporal scarring (hence, fake)
*
- * If you want a specific wound scar, pass that wound type as the second arg, otherwise you can pass a list like WOUND_LIST_CUT to generate a random cut scar.
+ * If you want a specific wound scar, pass that wound type as the second arg, otherwise you can pass a list like WOUND_LIST_SLASH to generate a random cut scar.
*
* Arguments:
* * num_scars- A number for how many scars you want to add
- * * forced_type- Which wound or category of wounds you want to choose from, WOUND_LIST_BONE, WOUND_LIST_CUT, or WOUND_LIST_BURN (or some combination). If passed a list, picks randomly from the listed wounds. Defaults to all 3 types
+ * * forced_type- Which wound or category of wounds you want to choose from, WOUND_LIST_BLUNT, WOUND_LIST_SLASH, or WOUND_LIST_BURN (or some combination). If passed a list, picks randomly from the listed wounds. Defaults to all 3 types
*/
/mob/living/carbon/proc/generate_fake_scars(num_scars, forced_type)
for(var/i in 1 to num_scars)
- var/datum/scar/S = new
- var/obj/item/bodypart/BP = pick(bodyparts)
+ var/datum/scar/scaries = new
+ var/obj/item/bodypart/scar_part = pick(bodyparts)
var/wound_type
if(forced_type)
@@ -1240,9 +1231,17 @@
else
wound_type = forced_type
else
- wound_type = pick(WOUND_LIST_BONE + WOUND_LIST_CUT + WOUND_LIST_BURN)
+ wound_type = pick(GLOB.global_all_wound_types)
- var/datum/wound/W = new wound_type
- S.generate(BP, W)
- S.fake = TRUE
- QDEL_NULL(W)
+ var/datum/wound/phantom_wound = new wound_type
+ scaries.generate(scar_part, phantom_wound)
+ scaries.fake = TRUE
+ QDEL_NULL(phantom_wound)
+
+/**
+ * get_biological_state is a helper used to see what kind of wounds we roll for. By default we just assume carbons (read:monkeys) are flesh and bone, but humans rely on their species datums
+ *
+ * go look at the species def for more info [/datum/species/proc/get_biological_state]
+ */
+/mob/living/carbon/proc/get_biological_state()
+ return BIO_FLESH_BONE
diff --git a/code/modules/mob/living/carbon/carbon_defense.dm b/code/modules/mob/living/carbon/carbon_defense.dm
index ac261e6d5b..566c004142 100644
--- a/code/modules/mob/living/carbon/carbon_defense.dm
+++ b/code/modules/mob/living/carbon/carbon_defense.dm
@@ -76,7 +76,7 @@
if(!affecting) //missing limb? we select the first bodypart (you can never have zero, because of chest)
affecting = bodyparts[1]
SEND_SIGNAL(I, COMSIG_ITEM_ATTACK_ZONE, src, user, affecting)
- send_item_attack_message(I, user, affecting.name, totitemdamage)
+ send_item_attack_message(I, user, affecting.name, affecting, totitemdamage)
I.do_stagger_action(src, user, totitemdamage)
if(I.force)
apply_damage(totitemdamage, I.damtype, affecting, wound_bonus = I.wound_bonus, bare_wound_bonus = I.bare_wound_bonus, sharpness = I.get_sharpness()) //CIT CHANGE - replaces I.force with totitemdamage
@@ -100,12 +100,6 @@
head.add_mob_blood(src)
update_inv_head()
- //dismemberment
- var/probability = I.get_dismemberment_chance(affecting)
- if(prob(probability))
- if(affecting.dismember(I.damtype))
- I.add_mob_blood(src)
- playsound(get_turf(src), I.get_dismember_sound(), 80, 1)
return TRUE //successful attack
/mob/living/carbon/attack_drone(mob/living/simple_animal/drone/user)
@@ -131,9 +125,10 @@
if(S.next_step(user, act_intent))
return TRUE
- for(var/datum/wound/W in all_wounds)
+ for(var/i in all_wounds)
+ var/datum/wound/W = i
if(W.try_handling(user))
- return 1
+ return TRUE
/mob/living/carbon/attack_paw(mob/living/carbon/monkey/M)
@@ -157,6 +152,7 @@
for(var/thing in M.diseases)
var/datum/disease/D = thing
ForceContractDisease(D)
+ return TRUE
/mob/living/carbon/attack_slime(mob/living/simple_animal/slime/M)
. = ..()
@@ -295,12 +291,12 @@
target_message = "[M] gives you a pat on the head to make you feel better!")
SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "headpat", /datum/mood_event/headpat)
friendly_check = TRUE
- if(S?.can_wag_tail(src) && !dna.species.is_wagging_tail())
- var/static/list/many_tails = list("tail_human", "tail_lizard", "mam_tail")
- for(var/T in many_tails)
- if(S.mutant_bodyparts[T] && dna.features[T] != "None")
- emote("wag")
- break
+ if(!(client?.prefs.cit_toggles & NO_AUTO_WAG))
+ if(S?.can_wag_tail(src) && !dna.species.is_wagging_tail())
+ var/static/list/many_tails = list("tail_human", "tail_lizard", "mam_tail")
+ for(var/T in many_tails)
+ if(S.mutant_bodyparts[T] && dna.features[T] != "None")
+ emote("wag")
else if(check_zone(M.zone_selected) == BODY_ZONE_R_ARM || check_zone(M.zone_selected) == BODY_ZONE_L_ARM)
M.visible_message( \
@@ -397,7 +393,7 @@
to_chat(src, "Your eyes are really starting to hurt. This can't be good for you!")
if(has_bane(BANE_LIGHT))
mind.disrupt_spells(-500)
- return 1
+ return TRUE
else if(damage == 0) // just enough protection
if(prob(20))
to_chat(src, "Something bright flashes in the corner of your vision!")
@@ -472,3 +468,35 @@
var/obj/item/bodypart/limb = get_bodypart(zone)
if(!limb)
return
+
+/mob/living/carbon/send_item_attack_message(obj/item/I, mob/living/user, hit_area, obj/item/bodypart/hit_bodypart, totitemdamage)
+ var/message_verb = "attacked"
+ if(length(I.attack_verb))
+ message_verb = "[pick(I.attack_verb)]"
+ else if(!I.force)
+ return
+
+ var/extra_wound_details = ""
+ if(I.damtype == BRUTE && hit_bodypart.can_dismember())
+ var/mangled_state = hit_bodypart.get_mangled_state()
+ var/bio_state = get_biological_state()
+ if(mangled_state == BODYPART_MANGLED_BOTH)
+ extra_wound_details = ", threatening to sever it entirely"
+ else if((mangled_state == BODYPART_MANGLED_FLESH && I.get_sharpness()) || (mangled_state & BODYPART_MANGLED_BONE && bio_state == BIO_JUST_BONE))
+ extra_wound_details = ", [I.get_sharpness() == SHARP_EDGED ? "slicing" : "piercing"] through to the bone"
+ else if((mangled_state == BODYPART_MANGLED_BONE && I.get_sharpness()) || (mangled_state & BODYPART_MANGLED_FLESH && bio_state == BIO_JUST_FLESH))
+ extra_wound_details = ", [I.get_sharpness() == SHARP_EDGED ? "slicing" : "piercing"] at the remaining tissue"
+
+ var/message_hit_area = ""
+ if(hit_area)
+ message_hit_area = " in the [hit_area]"
+ var/attack_message = "[src] is [message_verb][message_hit_area] with [I][extra_wound_details]!"
+ var/attack_message_local = "You're [message_verb][message_hit_area] with [I][extra_wound_details]!"
+ if(user in viewers(src, null))
+ attack_message = "[user] [message_verb] [src][message_hit_area] with [I][extra_wound_details]!"
+ attack_message_local = "[user] [message_verb] you[message_hit_area] with [I][extra_wound_details]!"
+ if(user == src)
+ attack_message_local = "You [message_verb] yourself[message_hit_area] with [I][extra_wound_details]"
+ visible_message("[attack_message]",\
+ "[attack_message_local]", null, COMBAT_MESSAGE_RANGE)
+ return TRUE
diff --git a/code/modules/mob/living/carbon/damage_procs.dm b/code/modules/mob/living/carbon/damage_procs.dm
index ddf1019f2a..5c5a1d6d52 100644
--- a/code/modules/mob/living/carbon/damage_procs.dm
+++ b/code/modules/mob/living/carbon/damage_procs.dm
@@ -1,6 +1,6 @@
-/mob/living/carbon/apply_damage(damage, damagetype = BRUTE, def_zone = null, blocked = FALSE, forced = FALSE, spread_damage = FALSE, wound_bonus = 0, bare_wound_bonus = 0, sharpness = FALSE)
+/mob/living/carbon/apply_damage(damage, damagetype = BRUTE, def_zone = null, blocked = FALSE, forced = FALSE, spread_damage = FALSE, wound_bonus = 0, bare_wound_bonus = 0, sharpness = SHARP_NONE)
SEND_SIGNAL(src, COMSIG_MOB_APPLY_DAMGE, damage, damagetype, def_zone)
var/hit_percent = (100-blocked)/100
if(!forced && hit_percent <= 0)
@@ -202,7 +202,7 @@
//Damages ONE bodypart randomly selected from damagable ones.
//It automatically updates damage overlays if necessary
//It automatically updates health status
-/mob/living/carbon/take_bodypart_damage(brute = 0, burn = 0, stamina = 0, updating_health = TRUE, required_status, check_armor = FALSE, wound_bonus = 0, bare_wound_bonus = 0, sharpness = FALSE)
+/mob/living/carbon/take_bodypart_damage(brute = 0, burn = 0, stamina = 0, updating_health = TRUE, required_status, check_armor = FALSE, wound_bonus = 0, bare_wound_bonus = 0, sharpness = SHARP_NONE)
var/list/obj/item/bodypart/parts = get_damageable_bodyparts()
if(!parts.len)
return
diff --git a/code/modules/mob/living/carbon/emote.dm b/code/modules/mob/living/carbon/emote.dm
index e3512e3929..49501e0315 100644
--- a/code/modules/mob/living/carbon/emote.dm
+++ b/code/modules/mob/living/carbon/emote.dm
@@ -1,6 +1,3 @@
-/datum/emote/living/carbon
- mob_type_allowed_typecache = list(/mob/living/carbon)
-
/datum/emote/living/carbon/airguitar
key = "airguitar"
message = "is strumming the air and headbanging like a safari chimp."
@@ -22,7 +19,6 @@
muzzle_ignore = TRUE
restraint_check = TRUE
emote_type = EMOTE_AUDIBLE
- mob_type_allowed_typecache = list(/mob/living/carbon, /mob/living/silicon/pai)
/datum/emote/living/carbon/clap/run_emote(mob/living/user, params)
. = ..()
@@ -41,7 +37,6 @@
key = "gnarl"
key_third_person = "gnarls"
message = "gnarls and shows its teeth..."
- mob_type_allowed_typecache = list(/mob/living/carbon/monkey, /mob/living/carbon/alien)
/datum/emote/living/carbon/moan
key = "moan"
@@ -55,27 +50,23 @@
key = "roll"
key_third_person = "rolls"
message = "rolls."
- mob_type_allowed_typecache = list(/mob/living/carbon/monkey, /mob/living/carbon/alien)
restraint_check = TRUE
/datum/emote/living/carbon/scratch
key = "scratch"
key_third_person = "scratches"
message = "scratches."
- mob_type_allowed_typecache = list(/mob/living/carbon/monkey, /mob/living/carbon/alien)
restraint_check = TRUE
/datum/emote/living/carbon/screech
key = "screech"
key_third_person = "screeches"
message = "screeches."
- mob_type_allowed_typecache = list(/mob/living/carbon/monkey, /mob/living/carbon/alien)
/datum/emote/living/carbon/sign
key = "sign"
key_third_person = "signs"
message_param = "signs the number %t."
- mob_type_allowed_typecache = list(/mob/living/carbon/monkey, /mob/living/carbon/alien)
restraint_check = TRUE
/datum/emote/living/carbon/sign/select_param(mob/user, params)
@@ -87,13 +78,11 @@
key = "signal"
key_third_person = "signals"
message_param = "raises %t fingers."
- mob_type_allowed_typecache = list(/mob/living/carbon/human)
restraint_check = TRUE
/datum/emote/living/carbon/tail
key = "tail"
message = "waves their tail."
- mob_type_allowed_typecache = list(/mob/living/carbon/monkey, /mob/living/carbon/alien)
/datum/emote/living/carbon/wink
key = "wink"
diff --git a/code/modules/mob/living/carbon/examine.dm b/code/modules/mob/living/carbon/examine.dm
index be046a3713..5eabae16f7 100644
--- a/code/modules/mob/living/carbon/examine.dm
+++ b/code/modules/mob/living/carbon/examine.dm
@@ -44,7 +44,8 @@
msg += "[t_He] [t_has] \a [icon2html(I, user)] [I] stuck to [t_his] [BP.name]!\n"
else
msg += "[t_He] [t_has] \a [icon2html(I, user)] [I] embedded in [t_his] [BP.name]!\n"
- for(var/datum/wound/W in BP.wounds)
+ for(var/i in BP.wounds)
+ var/datum/wound/W = i
msg += "[W.get_examine_description(user)]\n"
for(var/X in disabled)
@@ -109,7 +110,7 @@
switch(scar_severity)
if(1 to 2)
- msg += "[t_He] [t_has] visible scarring, you can look again to take a closer look...\n"
+ msg += "[t_He] [t_has] visible scarring, you can look again to take a closer look...\n"
if(3 to 4)
msg += "[t_He] [t_has] several bad scars, you can look again to take a closer look...\n"
if(5 to 6)
diff --git a/code/modules/mob/living/carbon/human/damage_procs.dm b/code/modules/mob/living/carbon/human/damage_procs.dm
index 5cd00b7e6a..04ec1196fd 100644
--- a/code/modules/mob/living/carbon/human/damage_procs.dm
+++ b/code/modules/mob/living/carbon/human/damage_procs.dm
@@ -1,5 +1,5 @@
// depending on the species, it will run the corresponding apply_damage code there
-/mob/living/carbon/human/apply_damage(damage = 0,damagetype = BRUTE, def_zone = null, blocked = FALSE, forced = FALSE, spread_damage = FALSE, wound_bonus = 0, bare_wound_bonus = 0, sharpness = FALSE)
+/mob/living/carbon/human/apply_damage(damage = 0,damagetype = BRUTE, def_zone = null, blocked = FALSE, forced = FALSE, spread_damage = FALSE, wound_bonus = 0, bare_wound_bonus = 0, sharpness = SHARP_NONE)
return dna.species.apply_damage(damage, damagetype, def_zone, blocked, src, forced, spread_damage, wound_bonus, bare_wound_bonus, sharpness)
diff --git a/code/modules/mob/living/carbon/human/dummy.dm b/code/modules/mob/living/carbon/human/dummy.dm
index 96517d5ea2..ed1ba3852f 100644
--- a/code/modules/mob/living/carbon/human/dummy.dm
+++ b/code/modules/mob/living/carbon/human/dummy.dm
@@ -18,10 +18,13 @@ INITIALIZE_IMMEDIATE(/mob/living/carbon/human/dummy)
/mob/living/carbon/human/dummy/Life()
return
+/mob/living/carbon/human/dummy/update_mobility()
+ return
+
/mob/living/carbon/human/dummy/proc/wipe_state()
delete_equipment()
icon_render_key = null
- cut_overlays(TRUE)
+ cut_overlays()
//Inefficient pooling/caching way.
GLOBAL_LIST_EMPTY(human_dummy_list)
@@ -47,6 +50,5 @@ GLOBAL_LIST_EMPTY(dummy_mob_list)
return
var/mob/living/carbon/human/dummy/D = GLOB.human_dummy_list[slotnumber]
if(istype(D))
- D.set_species(/datum/species/human,icon_update = TRUE, pref_load = TRUE) //for some fucking reason, if you don't change the species every time, some species will dafault certain things when it's their own species on the mannequin two times in a row, like lizards losing spines and tails setting to smooth. If you can find a fix for this that isn't this, good on you
D.wipe_state()
D.in_use = FALSE
diff --git a/code/modules/mob/living/carbon/human/emote.dm b/code/modules/mob/living/carbon/human/emote.dm
index cb0dbef332..837b4f73cf 100644
--- a/code/modules/mob/living/carbon/human/emote.dm
+++ b/code/modules/mob/living/carbon/human/emote.dm
@@ -1,12 +1,14 @@
-/datum/emote/living/carbon/human
- mob_type_allowed_typecache = list(/mob/living/carbon/human)
-
/datum/emote/living/carbon/human/cry
key = "cry"
key_third_person = "cries"
message = "cries."
emote_type = EMOTE_AUDIBLE
+/datum/emote/living/carbon/human/cry/run_emote(mob/user, params)
+ . = ..()
+ if(. && isipcperson(user))
+ do_fake_sparks(5,FALSE,user)
+
/datum/emote/living/carbon/human/dap
key = "dap"
key_third_person = "daps"
@@ -187,3 +189,71 @@
key_third_person = "chimes"
message = "chimes."
sound = 'sound/machines/chime.ogg'
+
+//rock paper scissors emote handling
+/mob/living/carbon/human/proc/beginRockPaperScissors(var/chosen_move)
+ GLOB.rockpaperscissors_players[src] = list(chosen_move, ROCKPAPERSCISSORS_NOT_DECIDED)
+ do_after_advanced(src, ROCKPAPERSCISSORS_TIME_LIMIT, src, DO_AFTER_REQUIRES_USER_ON_TURF|DO_AFTER_NO_COEFFICIENT|DO_AFTER_NO_PROGRESSBAR|DO_AFTER_DISALLOW_MOVING_ABSOLUTE_USER, CALLBACK(src, .proc/rockpaperscissors_tick))
+ var/new_entry = GLOB.rockpaperscissors_players[src]
+ if(new_entry[2] == ROCKPAPERSCISSORS_NOT_DECIDED)
+ to_chat(src, "You put your hand back down.")
+ GLOB.rockpaperscissors_players -= src
+
+/mob/living/carbon/human/proc/rockpaperscissors_tick() //called every cycle of the progress bar for rock paper scissors while waiting for an opponent
+ var/mob/living/carbon/human/opponent
+ for(var/mob/living/carbon/human/potential_opponent in (GLOB.rockpaperscissors_players - src)) //dont play against yourself
+ if(get_dist(src, potential_opponent) <= ROCKPAPERSCISSORS_RANGE)
+ opponent = potential_opponent
+ break
+ if(opponent)
+ //we found an opponent before they found us
+ var/move_to_number = list("rock" = 0, "paper" = 1, "scissors" = 2)
+ var/our_move = move_to_number[GLOB.rockpaperscissors_players[src][1]]
+ var/their_move = move_to_number[GLOB.rockpaperscissors_players[opponent][1]]
+ var/result_us = ROCKPAPERSCISSORS_WIN
+ var/result_them = ROCKPAPERSCISSORS_LOSE
+ if(our_move == their_move)
+ result_us = ROCKPAPERSCISSORS_TIE
+ result_them = ROCKPAPERSCISSORS_TIE
+ else
+ if(((our_move + 1) % 3) == their_move)
+ result_us = ROCKPAPERSCISSORS_LOSE
+ result_them = ROCKPAPERSCISSORS_WIN
+ //we decided our results so set them in the list
+ GLOB.rockpaperscissors_players[src][2] = result_us
+ GLOB.rockpaperscissors_players[opponent][2] = result_them
+
+ //show what happened
+ src.visible_message("[src] makes [GLOB.rockpaperscissors_players[src][1]] with their hand!")
+ opponent.visible_message("[opponent] makes [GLOB.rockpaperscissors_players[opponent][1]] with their hands!")
+ switch(result_us)
+ if(ROCKPAPERSCISSORS_TIE)
+ src.visible_message("It was a tie!")
+ if(ROCKPAPERSCISSORS_WIN)
+ src.visible_message("[src] wins!")
+ if(ROCKPAPERSCISSORS_LOSE)
+ src.visible_message("[opponent] wins!")
+
+ //make the progress bar end so that each player can handle the result
+ return DO_AFTER_STOP
+
+ //no opponent was found, so keep searching
+ return DO_AFTER_PROCEED
+
+//the actual emotes
+/datum/emote/living/carbon/human/rockpaperscissors
+ message = "is attempting to play rock paper scissors!"
+
+/datum/emote/living/carbon/human/rockpaperscissors/rock
+ key = "rock"
+
+/datum/emote/living/carbon/human/rockpaperscissors/paper
+ key = "paper"
+
+/datum/emote/living/carbon/human/rockpaperscissors/scissors
+ key = "scissors"
+
+/datum/emote/living/carbon/human/rockpaperscissors/run_emote(mob/living/carbon/human/user, params)
+ if(!(user in GLOB.rockpaperscissors_players)) //no using the emote again while already playing!
+ . = ..()
+ user.beginRockPaperScissors(key)
diff --git a/code/modules/mob/living/carbon/human/examine.dm b/code/modules/mob/living/carbon/human/examine.dm
index cbea9293f5..16470d81ce 100644
--- a/code/modules/mob/living/carbon/human/examine.dm
+++ b/code/modules/mob/living/carbon/human/examine.dm
@@ -113,14 +113,14 @@
. += effects_exam
//CIT CHANGES START HERE - adds genital details to examine text
- if(LAZYLEN(internal_organs))
+ if(LAZYLEN(internal_organs) && CHECK_BITFIELD(user.client?.prefs.cit_toggles, GENITAL_EXAMINE))
for(var/obj/item/organ/genital/dicc in internal_organs)
if(istype(dicc) && dicc.is_exposed())
. += "[dicc.desc]"
-
- var/cursed_stuff = attempt_vr(src,"examine_bellies",args) //vore Code
- if(cursed_stuff)
- . += cursed_stuff
+ if(CHECK_BITFIELD(user.client?.prefs.cit_toggles, VORE_EXAMINE))
+ var/cursed_stuff = attempt_vr(src,"examine_bellies",args) //vore Code
+ if(cursed_stuff)
+ . += cursed_stuff
//END OF CIT CHANGES
//Jitters
@@ -163,8 +163,9 @@
msg += "[t_He] [t_has] \a [icon2html(I, user)] [I] stuck to [t_his] [BP.name]!\n"
else
msg += "[t_He] [t_has] \a [icon2html(I, user)] [I] embedded in [t_his] [BP.name]!\n"
- for(var/datum/wound/W in BP.wounds)
- msg += "[W.get_examine_description(user)]\n"
+ for(var/i in BP.wounds)
+ var/datum/wound/iter_wound = i
+ msg += "[iter_wound.get_examine_description(user)]\n"
for(var/X in disabled)
var/obj/item/bodypart/BP = X
@@ -270,7 +271,12 @@
bleeding_limbs += BP
var/num_bleeds = LAZYLEN(bleeding_limbs)
- var/bleed_text = "[t_He] [t_is] bleeding from [t_his]"
+ var/list/bleed_text
+ if(appears_dead)
+ bleed_text = list("Blood is visible in [t_his] open")
+ else
+ bleed_text = list("[t_He] [t_is] bleeding from [t_his]")
+
switch(num_bleeds)
if(1 to 2)
bleed_text += " [bleeding_limbs[1].name][num_bleeds == 2 ? " and [bleeding_limbs[2].name]" : ""]"
@@ -280,8 +286,15 @@
bleed_text += " [BP.name],"
bleed_text += " and [bleeding_limbs[num_bleeds].name]"
- bleed_text += "!\n"
- msg += bleed_text
+
+ if(appears_dead)
+ bleed_text += ", but it has pooled and is not flowing.\n"
+ else
+ if(reagents.has_reagent(/datum/reagent/toxin/heparin))
+ bleed_text += " incredibly quickly"
+
+ bleed_text += "!\n"
+ msg += bleed_text.Join()
if(reagents.has_reagent(/datum/reagent/teslium))
msg += "[t_He] [t_is] emitting a gentle blue glow!\n"
@@ -365,7 +378,7 @@
switch(scar_severity)
if(1 to 2)
- msg += "[t_He] [t_has] visible scarring, you can look again to take a closer look...\n"
+ msg += "[t_He] [t_has] visible scarring, you can look again to take a closer look...\n"
if(3 to 4)
msg += "[t_He] [t_has] several bad scars, you can look again to take a closer look...\n"
if(5 to 6)
diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm
index b76f3139bd..b0c4e01359 100644
--- a/code/modules/mob/living/carbon/human/human.dm
+++ b/code/modules/mob/living/carbon/human/human.dm
@@ -1040,15 +1040,9 @@
return TRUE
return FALSE
-/mob/living/carbon/human/proc/clear_shove_slowdown()
- remove_movespeed_modifier(/datum/movespeed_modifier/shove)
- var/active_item = get_active_held_item()
- if(is_type_in_typecache(active_item, GLOB.shove_disarming_types))
- visible_message("[src.name] regains their grip on \the [active_item]!", "You regain your grip on \the [active_item]", null, COMBAT_MESSAGE_RANGE)
-
/mob/living/carbon/human/updatehealth()
. = ..()
-
+ dna?.species.spec_updatehealth(src)
if(HAS_TRAIT(src, TRAIT_IGNORESLOWDOWN)) //if we want to ignore slowdown from damage and equipment
remove_movespeed_modifier(/datum/movespeed_modifier/damage_slowdown)
remove_movespeed_modifier(/datum/movespeed_modifier/damage_slowdown_flying)
@@ -1204,6 +1198,9 @@
/mob/living/carbon/human/species/lizard
race = /datum/species/lizard
+/mob/living/carbon/human/species/ethereal
+ race = /datum/species/ethereal
+
/mob/living/carbon/human/species/lizard/ashwalker
race = /datum/species/lizard/ashwalker
diff --git a/code/modules/mob/living/carbon/human/human_defense.dm b/code/modules/mob/living/carbon/human/human_defense.dm
index 1673c57a80..18501b4ca2 100644
--- a/code/modules/mob/living/carbon/human/human_defense.dm
+++ b/code/modules/mob/living/carbon/human/human_defense.dm
@@ -299,10 +299,10 @@
/mob/living/carbon/human/ex_act(severity, target, origin)
- if(origin && istype(origin, /datum/spacevine_mutation) && isvineimmune(src))
+ if(TRAIT_BOMBIMMUNE in dna.species.species_traits)
return
..()
- if (!severity)
+ if (!severity || QDELETED(src))
return
var/brute_loss = 0
var/burn_loss = 0
@@ -336,7 +336,8 @@
if (!istype(ears, /obj/item/clothing/ears/earmuffs))
adjustEarDamage(30, 120)
Unconscious(20) //short amount of time for follow up attacks against elusive enemies like wizards
- Knockdown(200 - (bomb_armor * 1.6)) //between ~4 and ~20 seconds of knockdown depending on bomb armor
+ Knockdown((200 - (bomb_armor * 1.6)) / 4) //between ~1 and ~5 seconds of knockdown depending on bomb armor
+ adjustStaminaLoss(brute_loss)
if(EXPLODE_LIGHT)
brute_loss = 30
@@ -345,7 +346,8 @@
damage_clothes(max(50 - bomb_armor, 0), BRUTE, "bomb")
if (!istype(ears, /obj/item/clothing/ears/earmuffs))
adjustEarDamage(15,60)
- Knockdown(160 - (bomb_armor * 1.6)) //100 bomb armor will prevent knockdown altogether
+ Knockdown((160 - (bomb_armor * 1.6)) / 4) //100 bomb armor will prevent knockdown altogether
+ adjustStaminaLoss(brute_loss)
take_overall_damage(brute_loss,burn_loss)
diff --git a/code/modules/mob/living/carbon/human/human_helpers.dm b/code/modules/mob/living/carbon/human/human_helpers.dm
index 8672c0e83d..5f30d902b1 100644
--- a/code/modules/mob/living/carbon/human/human_helpers.dm
+++ b/code/modules/mob/living/carbon/human/human_helpers.dm
@@ -75,12 +75,8 @@
. = pda.owner
else if(istype(tablet))
var/obj/item/computer_hardware/card_slot/card_slot = tablet.all_components[MC_CARD]
- if(card_slot && (card_slot.stored_card2 || card_slot.stored_card))
- if(card_slot.stored_card2) //The second card is the one used for authorization in the ID changing program, so we prioritize it here for consistency
- . = card_slot.stored_card2.registered_name
- else
- if(card_slot.stored_card)
- . = card_slot.stored_card.registered_name
+ if(card_slot?.stored_card)
+ . = card_slot.stored_card.registered_name
if(!.)
. = if_no_id //to prevent null-names making the mob unclickable
return
@@ -154,19 +150,29 @@
/// For use formatting all of the scars this human has for saving for persistent scarring
/mob/living/carbon/human/proc/format_scars()
- if(!all_scars)
+ var/list/missing_bodyparts = get_missing_limbs()
+ if(!all_scars && !length(missing_bodyparts))
return
var/scars = ""
+ for(var/i in missing_bodyparts)
+ var/datum/scar/scaries = new
+ scars += "[scaries.format_amputated(i)]"
for(var/i in all_scars)
- var/datum/scar/S = i
- scars += "[S.format()];"
+ var/datum/scar/scaries = i
+ scars += "[scaries.format()];"
return scars
/// Takes a single scar from the persistent scar loader and recreates it from the saved data
/mob/living/carbon/human/proc/load_scar(scar_line)
var/list/scar_data = splittext(scar_line, "|")
- if(LAZYLEN(scar_data) != 4)
+ if(LAZYLEN(scar_data) != SCAR_SAVE_LENGTH)
return // invalid, should delete
- var/obj/item/bodypart/BP = get_bodypart("[scar_data[SCAR_SAVE_ZONE]]")
- var/datum/scar/S = new
- return S.load(BP, scar_data[SCAR_SAVE_DESC], scar_data[SCAR_SAVE_PRECISE_LOCATION], text2num(scar_data[SCAR_SAVE_SEVERITY]))
+ var/version = text2num(scar_data[SCAR_SAVE_VERS])
+ if(!version || version < SCAR_CURRENT_VERSION) // get rid of old scars
+ return
+ var/obj/item/bodypart/the_part = get_bodypart("[scar_data[SCAR_SAVE_ZONE]]")
+ var/datum/scar/scaries = new
+ return scaries.load(the_part, scar_data[SCAR_SAVE_VERS], scar_data[SCAR_SAVE_DESC], scar_data[SCAR_SAVE_PRECISE_LOCATION], text2num(scar_data[SCAR_SAVE_SEVERITY]))
+
+/mob/living/carbon/human/get_biological_state()
+ return dna.species.get_biological_state()
diff --git a/code/modules/mob/living/carbon/human/inventory.dm b/code/modules/mob/living/carbon/human/inventory.dm
index a7593ca0cb..523369d10a 100644
--- a/code/modules/mob/living/carbon/human/inventory.dm
+++ b/code/modules/mob/living/carbon/human/inventory.dm
@@ -1,31 +1,17 @@
/mob/living/carbon/human/can_equip(obj/item/I, slot, disable_warning = FALSE, bypass_equip_delay_self = FALSE, clothing_check = FALSE, list/return_warning)
return dna.species.can_equip(I, slot, disable_warning, src, bypass_equip_delay_self, clothing_check, return_warning)
+/**
+ * Used to return a list of equipped items on a human mob; does not include held items (use get_all_gear)
+ *
+ * Argument(s):
+ * * Optional - include_pockets (TRUE/FALSE), whether or not to include the pockets and suit storage in the returned list
+ */
+
/mob/living/carbon/human/get_equipped_items(include_pockets = FALSE)
var/list/items = ..()
- if(belt)
- items += belt
- if(ears)
- items += ears
- if(glasses)
- items += glasses
- if(gloves)
- items += gloves
- if(shoes)
- items += shoes
- if(wear_id)
- items += wear_id
- if(wear_suit)
- items += wear_suit
- if(w_uniform)
- items += w_uniform
- if(include_pockets)
- if(l_store)
- items += l_store
- if(r_store)
- items += r_store
- if(s_store)
- items += s_store
+ if(!include_pockets)
+ items -= list(l_store, r_store, s_store)
return items
// Return the item currently in the slot ID
@@ -169,7 +155,7 @@
//Item is handled and in slot, valid to call callback, for this proc should always be true
if(!not_handled)
I.equipped(src, slot)
-
+ update_genitals()
return not_handled //For future deeper overrides
/mob/living/carbon/human/equipped_speed_mods()
@@ -257,6 +243,7 @@
s_store = null
if(!QDELETED(src))
update_inv_s_store()
+ update_genitals()
/mob/living/carbon/human/wear_mask_update(obj/item/clothing/C, toggle_off = 1)
if((C.flags_inv & (HIDEHAIR|HIDEFACIALHAIR)) || (initial(C.flags_inv) & (HIDEHAIR|HIDEFACIALHAIR)))
diff --git a/code/modules/mob/living/carbon/human/species.dm b/code/modules/mob/living/carbon/human/species.dm
index ffa1aa989a..65dff1c539 100644
--- a/code/modules/mob/living/carbon/human/species.dm
+++ b/code/modules/mob/living/carbon/human/species.dm
@@ -56,6 +56,7 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
var/list/mutant_organs = list() //Internal organs that are unique to this race.
var/speedmod = 0 // this affects the race's speed. positive numbers make it move slower, negative numbers make it move faster
var/armor = 0 // overall defense for the race... or less defense, if it's negative.
+ var/attack_type = BRUTE // the type of damage unarmed attacks from this species do
var/brutemod = 1 // multiplier for brute damage
var/burnmod = 1 // multiplier for burn damage
var/coldmod = 1 // multiplier for cold damage
@@ -73,7 +74,7 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
var/datum/outfit/outfit_important_for_life // A path to an outfit that is important for species life e.g. plasmaman outfit
// species-only traits. Can be found in DNA.dm
- var/list/species_traits = list(CAN_SCAR) //by default they can scar unless set to something else
+ var/list/species_traits = list(HAS_FLESH,HAS_BONE) //by default they can scar and have bones/flesh unless set to something else
// generic traits tied to having the species
var/list/inherent_traits = list()
var/inherent_biotypes = MOB_ORGANIC|MOB_HUMANOID
@@ -113,6 +114,9 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
/// Our default override for typing indicator state
var/typing_indicator_state
+ //the ids you can use for your species, if empty, it means default only and not changeable
+ var/list/allowed_limb_ids
+
///////////
// PROCS //
///////////
@@ -120,9 +124,14 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
/datum/species/New()
if(!limbs_id) //if we havent set a limbs id to use, just use our own id
- limbs_id = id
+ mutant_bodyparts["limbs_id"] = id //done this way to be non-intrusive to the existing system
+ else
+ mutant_bodyparts["limbs_id"] = limbs_id
..()
+ //update our mutant bodyparts to include unlocked ones
+ mutant_bodyparts += GLOB.unlocked_mutant_parts
+
/proc/generate_selectable_species(clear = FALSE)
if(clear)
GLOB.roundstart_races = list()
@@ -638,106 +647,19 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
if(!mutant_bodyparts)
return
- var/obj/item/bodypart/head/HD = H.get_bodypart(BODY_ZONE_HEAD)
var/tauric = mutant_bodyparts["taur"] && H.dna.features["taur"] && H.dna.features["taur"] != "None"
- if(mutant_bodyparts["tail_lizard"])
- if((H.wear_suit && (H.wear_suit.flags_inv & HIDETAUR)) || tauric)
- bodyparts_to_add -= "tail_lizard"
-
- if(mutant_bodyparts["waggingtail_lizard"])
- if((H.wear_suit && (H.wear_suit.flags_inv & HIDETAUR)) || tauric)
- bodyparts_to_add -= "waggingtail_lizard"
- else if (mutant_bodyparts["tail_lizard"])
- bodyparts_to_add -= "waggingtail_lizard"
-
- if(mutant_bodyparts["tail_human"])
- if((H.wear_suit && (H.wear_suit.flags_inv & HIDETAUR)) || tauric)
- bodyparts_to_add -= "tail_human"
-
- if(mutant_bodyparts["waggingtail_human"])
- if((H.wear_suit && (H.wear_suit.flags_inv & HIDETAUR)) || tauric)
- bodyparts_to_add -= "waggingtail_human"
- else if (mutant_bodyparts["tail_human"])
- bodyparts_to_add -= "waggingtail_human"
-
- if(mutant_bodyparts["spines"])
- if(!H.dna.features["spines"] || H.dna.features["spines"] == "None" || H.wear_suit && (H.wear_suit.flags_inv & HIDETAUR))
- bodyparts_to_add -= "spines"
-
- if(mutant_bodyparts["waggingspines"])
- if(!H.dna.features["spines"] || H.dna.features["spines"] == "None" || H.wear_suit && (H.wear_suit.flags_inv & HIDETAUR))
- bodyparts_to_add -= "waggingspines"
- else if (mutant_bodyparts["tail"])
- bodyparts_to_add -= "waggingspines"
-
- if(mutant_bodyparts["snout"]) //Take a closer look at that snout!
- if((H.wear_mask && (H.wear_mask.flags_inv & HIDESNOUT)) || (H.head && (H.head.flags_inv & HIDESNOUT)) || !HD || HD.status == BODYPART_ROBOTIC)
- bodyparts_to_add -= "snout"
-
- if(mutant_bodyparts["frills"])
- if(!H.dna.features["frills"] || H.dna.features["frills"] == "None" || H.head && (H.head.flags_inv & HIDEEARS) || !HD || HD.status == BODYPART_ROBOTIC)
- bodyparts_to_add -= "frills"
-
- if(mutant_bodyparts["horns"])
- if(!H.dna.features["horns"] || H.dna.features["horns"] == "None" || H.head && (H.head.flags_inv & HIDEHAIR) || (H.wear_mask && (H.wear_mask.flags_inv & HIDEHAIR)) || !HD || HD.status == BODYPART_ROBOTIC)
- bodyparts_to_add -= "horns"
-
- if(mutant_bodyparts["ears"])
- if(!H.dna.features["ears"] || H.dna.features["ears"] == "None" || H.head && (H.head.flags_inv & HIDEEARS) || (H.wear_mask && (H.wear_mask.flags_inv & HIDEEARS)) || !HD || HD.status == BODYPART_ROBOTIC)
- bodyparts_to_add -= "ears"
-
- if(mutant_bodyparts["wings"])
- if(!H.dna.features["wings"] || H.dna.features["wings"] == "None" || (H.wear_suit && (H.wear_suit.flags_inv & HIDEJUMPSUIT) && (!H.wear_suit.species_exception || !is_type_in_list(src, H.wear_suit.species_exception))))
- bodyparts_to_add -= "wings"
-
- if(mutant_bodyparts["wings_open"])
- if(H.wear_suit && (H.wear_suit.flags_inv & HIDEJUMPSUIT) && (!H.wear_suit.species_exception || !is_type_in_list(src, H.wear_suit.species_exception)))
- bodyparts_to_add -= "wings_open"
- else if (mutant_bodyparts["wings"])
- bodyparts_to_add -= "wings_open"
-
- if(mutant_bodyparts["insect_fluff"])
- if(!H.dna.features["insect_fluff"] || H.dna.features["insect_fluff"] == "None" || H.wear_suit && (H.wear_suit.flags_inv & HIDEJUMPSUIT))
- bodyparts_to_add -= "insect_fluff"
-
-//CITADEL EDIT
- //Race specific bodyparts:
- //Xenos
- if(mutant_bodyparts["xenodorsal"])
- if(!H.dna.features["xenodorsal"] || H.dna.features["xenodorsal"] == "None" || (H.wear_suit && (H.wear_suit.flags_inv & HIDEJUMPSUIT)))
- bodyparts_to_add -= "xenodorsal"
- if(mutant_bodyparts["xenohead"])//This is an overlay for different castes using different head crests
- if(!H.dna.features["xenohead"] || H.dna.features["xenohead"] == "None" || H.head && (H.head.flags_inv & HIDEHAIR) || (H.wear_mask && (H.wear_mask.flags_inv & HIDEHAIR)) || !HD || HD.status == BODYPART_ROBOTIC)
- bodyparts_to_add -= "xenohead"
- if(mutant_bodyparts["xenotail"])
- if(!H.dna.features["xenotail"] || H.dna.features["xenotail"] == "None" || H.wear_suit && (H.wear_suit.flags_inv & HIDEJUMPSUIT))
- bodyparts_to_add -= "xenotail"
-
- //Other Races
- if(mutant_bodyparts["mam_tail"])
- if((H.wear_suit && (H.wear_suit.flags_inv & HIDETAUR)) || tauric)
- bodyparts_to_add -= "mam_tail"
-
- if(mutant_bodyparts["mam_waggingtail"])
- if((H.wear_suit && (H.wear_suit.flags_inv & HIDETAUR)) || tauric)
- bodyparts_to_add -= "mam_waggingtail"
- else if (mutant_bodyparts["mam_tail"])
- bodyparts_to_add -= "mam_waggingtail"
-
- if(mutant_bodyparts["mam_ears"])
- if(!H.dna.features["mam_ears"] || H.dna.features["mam_ears"] == "None" || H.head && (H.head.flags_inv & HIDEEARS) || (H.wear_mask && (H.wear_mask.flags_inv & HIDEEARS)) || !HD || HD.status == BODYPART_ROBOTIC)
- bodyparts_to_add -= "mam_ears"
-
- if(mutant_bodyparts["mam_snouts"]) //Take a closer look at that snout!
- if((H.wear_mask && (H.wear_mask.flags_inv & HIDESNOUT)) || (H.head && (H.head.flags_inv & HIDESNOUT)) || !HD || HD.status == BODYPART_ROBOTIC)
- bodyparts_to_add -= "mam_snouts"
-
- if(mutant_bodyparts["taur"])
- if(!tauric || (H.wear_suit && (H.wear_suit.flags_inv & HIDETAUR)))
- bodyparts_to_add -= "taur"
-
-//END EDIT
+ for(var/mutant_part in mutant_bodyparts)
+ var/reference_list = GLOB.mutant_reference_list[mutant_part]
+ if(reference_list)
+ var/datum/sprite_accessory/S
+ var/transformed_part = GLOB.mutant_transform_list[mutant_part]
+ if(transformed_part)
+ S = reference_list[H.dna.features[transformed_part]]
+ else
+ S = reference_list[H.dna.features[mutant_part]]
+ if(!S || S.is_not_visible(H, tauric))
+ bodyparts_to_add -= mutant_part
//Digitigrade legs are stuck in the phantom zone between true limbs and mutant bodyparts. Mainly it just needs more agressive updating than most limbs.
var/update_needed = FALSE
@@ -774,76 +696,22 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
var/list/dna_feature_as_text_string = list()
for(var/bodypart in bodyparts_to_add)
- var/datum/sprite_accessory/S
- switch(bodypart)
- if("tail_lizard")
- S = GLOB.tails_list_lizard[H.dna.features["tail_lizard"]]
- if("waggingtail_lizard")
- S = GLOB.animated_tails_list_lizard[H.dna.features["tail_lizard"]]
- if("tail_human")
- S = GLOB.tails_list_human[H.dna.features["tail_human"]]
- if("waggingtail_human")
- S = GLOB.animated_tails_list_human[H.dna.features["tail_human"]]
- if("spines")
- S = GLOB.spines_list[H.dna.features["spines"]]
- if("waggingspines")
- S = GLOB.animated_spines_list[H.dna.features["spines"]]
- if("snout")
- S = GLOB.snouts_list[H.dna.features["snout"]]
- if("frills")
- S = GLOB.frills_list[H.dna.features["frills"]]
- if("horns")
- S = GLOB.horns_list[H.dna.features["horns"]]
- if("ears")
- S = GLOB.ears_list[H.dna.features["ears"]]
- if("body_markings")
- S = GLOB.body_markings_list[H.dna.features["body_markings"]]
- if("wings")
- S = GLOB.wings_list[H.dna.features["wings"]]
- if("wingsopen")
- S = GLOB.wings_open_list[H.dna.features["wings"]]
- if("deco_wings")
- S = GLOB.deco_wings_list[H.dna.features["deco_wings"]]
- if("legs")
- S = GLOB.legs_list[H.dna.features["legs"]]
- if("insect_wings")
- S = GLOB.insect_wings_list[H.dna.features["insect_wings"]]
- if("insect_fluff")
- S = GLOB.insect_fluffs_list[H.dna.features["insect_fluff"]]
- if("insect_markings")
- S = GLOB.insect_markings_list[H.dna.features["insect_markings"]]
- if("caps")
- S = GLOB.caps_list[H.dna.features["caps"]]
- if("ipc_screen")
- S = GLOB.ipc_screens_list[H.dna.features["ipc_screen"]]
- if("ipc_antenna")
- S = GLOB.ipc_antennas_list[H.dna.features["ipc_antenna"]]
- if("mam_tail")
- S = GLOB.mam_tails_list[H.dna.features["mam_tail"]]
- if("mam_waggingtail")
- S = GLOB.mam_tails_animated_list[H.dna.features["mam_tail"]]
- if("mam_body_markings")
- S = GLOB.mam_body_markings_list[H.dna.features["mam_body_markings"]]
- if("mam_ears")
- S = GLOB.mam_ears_list[H.dna.features["mam_ears"]]
- if("mam_snouts")
- S = GLOB.mam_snouts_list[H.dna.features["mam_snouts"]]
- if("taur")
- S = GLOB.taur_list[H.dna.features["taur"]]
- if("xenodorsal")
- S = GLOB.xeno_dorsal_list[H.dna.features["xenodorsal"]]
- if("xenohead")
- S = GLOB.xeno_head_list[H.dna.features["xenohead"]]
- if("xenotail")
- S = GLOB.xeno_tail_list[H.dna.features["xenotail"]]
+ var/reference_list = GLOB.mutant_reference_list[bodypart]
+ if(reference_list)
+ var/datum/sprite_accessory/S
+ var/transformed_part = GLOB.mutant_transform_list[bodypart]
+ if(transformed_part)
+ S = reference_list[H.dna.features[transformed_part]]
+ else
+ S = reference_list[H.dna.features[bodypart]]
- if(!S || S.icon_state == "none")
- continue
+ if(!S || S.icon_state == "none")
+ continue
- for(var/L in S.relevant_layers)
- LAZYADD(relevant_layers["[L]"], S)
- if(!S.mutant_part_string)
- dna_feature_as_text_string[S] = bodypart
+ for(var/L in S.relevant_layers)
+ LAZYADD(relevant_layers["[L]"], S)
+ if(!S.mutant_part_string)
+ dna_feature_as_text_string[S] = bodypart
var/static/list/layer_text = list(
"[BODY_BEHIND_LAYER]" = "BEHIND",
@@ -1364,6 +1232,10 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
var/hungry = (500 - H.nutrition) / 5 //So overeat would be 100 and default level would be 80
if(hungry >= 70)
H.add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/hunger, multiplicative_slowdown = (hungry / 50))
+ else if(isethereal(H))
+ var/datum/species/ethereal/E = H.dna.species
+ if(E.get_charge(H) <= ETHEREAL_CHARGE_NORMAL)
+ H.add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/hunger, multiplicative_slowdown = (1.5 * (1 - E.get_charge(H) / 100)))
else
H.remove_movespeed_modifier(/datum/movespeed_modifier/hunger)
@@ -1420,6 +1292,12 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
// ATTACK PROCS //
//////////////////
+/datum/species/proc/spec_updatehealth(mob/living/carbon/human/H)
+ return
+
+/datum/species/proc/spec_fully_heal(mob/living/carbon/human/H)
+ return
+
/datum/species/proc/help(mob/living/carbon/human/user, mob/living/carbon/human/target, datum/martial_art/attacker_style)
if(target.health >= 0 && !HAS_TRAIT(target, TRAIT_FAKEDEATH))
target.help_shake_act(user)
@@ -1502,6 +1380,9 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
var/obj/item/bodypart/affecting = target.get_bodypart(ran_zone(user.zone_selected))
+ if(!affecting) //Maybe the bodypart is missing? Or things just went wrong..
+ affecting = target.get_bodypart(BODY_ZONE_CHEST) //target chest instead, as failsafe. Or hugbox? You decide.
+
var/miss_chance = 100//calculate the odds that a punch misses entirely. considers stamina and brute damage of the puncher. punches miss by default to prevent weird cases
if(attackchain_flags & ATTACK_IS_PARRY_COUNTERATTACK)
miss_chance = 0
@@ -1539,11 +1420,11 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
target.dismembering_strike(user, affecting.body_zone)
if(atk_verb == ATTACK_EFFECT_KICK)//kicks deal 1.5x raw damage + 0.5x stamina damage
- target.apply_damage(damage*1.5, BRUTE, affecting, armor_block)
+ target.apply_damage(damage*1.5, attack_type, affecting, armor_block)
target.apply_damage(damage*0.5, STAMINA, affecting, armor_block)
log_combat(user, target, "kicked")
else//other attacks deal full raw damage + 2x in stamina damage
- target.apply_damage(damage, BRUTE, affecting, armor_block)
+ target.apply_damage(damage, attack_type, affecting, armor_block)
target.apply_damage(damage*2, STAMINA, affecting, armor_block)
log_combat(user, target, "punched")
@@ -1721,6 +1602,10 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
/datum/species/proc/spec_attacked_by(obj/item/I, mob/living/user, obj/item/bodypart/affecting, intent, mob/living/carbon/human/H, attackchain_flags = NONE, damage_multiplier = 1)
var/totitemdamage = H.pre_attacked_by(I, user) * damage_multiplier
+
+ if(!affecting) //Something went wrong. Maybe the limb is missing?
+ affecting = H.get_bodypart(BODY_ZONE_CHEST) //If the limb is missing, or something went terribly wrong, just hit the chest instead
+
// Allows you to put in item-specific reactions based on species
if(user != H)
var/list/block_return = list()
@@ -1732,8 +1617,6 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
return 0
var/hit_area
- if(!affecting) //Something went wrong. Maybe the limb is missing?
- affecting = H.bodyparts[1]
hit_area = affecting.name
var/def_zone = affecting.body_zone
@@ -1751,20 +1634,13 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
apply_damage(totitemdamage * weakness, I.damtype, def_zone, armor_block, H, wound_bonus = Iwound_bonus, bare_wound_bonus = I.bare_wound_bonus, sharpness = I.get_sharpness())
- H.send_item_attack_message(I, user, hit_area, totitemdamage)
+ H.send_item_attack_message(I, user, hit_area, affecting, totitemdamage)
I.do_stagger_action(H, user, totitemdamage)
if(!totitemdamage)
return 0 //item force is zero
- //dismemberment
- var/probability = I.get_dismemberment_chance(affecting)
- if(prob(probability) || (HAS_TRAIT(H, TRAIT_EASYDISMEMBER) && prob(probability))) //try twice
- if(affecting.dismember(I.damtype))
- I.add_mob_blood(H)
- playsound(get_turf(H), I.get_dismember_sound(), 80, 1)
-
var/bloody = 0
if(((I.damtype == BRUTE) && I.force && prob(25 + (I.force * 2))))
if(affecting.status == BODYPART_ORGANIC)
@@ -1949,11 +1825,11 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
target.visible_message("[user.name] shoves [target.name]!",
"[user.name] shoves you!", null, COMBAT_MESSAGE_RANGE, null,
user, "You shove [target.name]!")
+ target.Stagger(SHOVE_STAGGER_DURATION)
var/obj/item/target_held_item = target.get_active_held_item()
if(!is_type_in_typecache(target_held_item, GLOB.shove_disarming_types))
target_held_item = null
- if(!target.has_movespeed_modifier(/datum/movespeed_modifier/shove))
- target.add_movespeed_modifier(/datum/movespeed_modifier/shove)
+ if(!target.has_status_effect(STATUS_EFFECT_OFF_BALANCE))
if(target_held_item)
if(!HAS_TRAIT(target_held_item, TRAIT_NODROP))
target.visible_message("[target.name]'s grip on \the [target_held_item] loosens!",
@@ -1961,15 +1837,15 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
append_message += ", loosening their grip on [target_held_item]"
else
append_message += ", but couldn't loose their grip on [target_held_item]"
- addtimer(CALLBACK(target, /mob/living/carbon/human/proc/clear_shove_slowdown), SHOVE_SLOWDOWN_LENGTH)
else if(target_held_item)
if(target.dropItemToGround(target_held_item))
target.visible_message("[target.name] drops \the [target_held_item]!!",
"You drop \the [target_held_item]!!", null, COMBAT_MESSAGE_RANGE)
append_message += ", causing them to drop [target_held_item]"
+ target.ShoveOffBalance(SHOVE_OFFBALANCE_DURATION)
log_combat(user, target, "shoved", append_message)
-/datum/species/proc/apply_damage(damage, damagetype = BRUTE, def_zone = null, blocked, mob/living/carbon/human/H, forced = FALSE, spread_damage = FALSE, wound_bonus = 0, bare_wound_bonus = 0, sharpness = FALSE)
+/datum/species/proc/apply_damage(damage, damagetype = BRUTE, def_zone = null, blocked, mob/living/carbon/human/H, forced = FALSE, spread_damage = FALSE, wound_bonus = 0, bare_wound_bonus = 0, sharpness = SHARP_NONE)
SEND_SIGNAL(H, COMSIG_MOB_APPLY_DAMGE, damage, damagetype, def_zone, wound_bonus, bare_wound_bonus, sharpness) // make sure putting wound_bonus here doesn't screw up other signals or uses for this signal
var/hit_percent = (100-(blocked+armor))/100
hit_percent = (hit_percent * (100-H.physiology.damage_resistance))/100
@@ -2045,6 +1921,16 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
// called before a projectile hit
return
+/**
+ * The human species version of [/mob/living/carbon/proc/get_biological_state]. Depends on the HAS_FLESH and HAS_BONE species traits, having bones lets you have bone wounds, having flesh lets you have burn, slash, and piercing wounds
+ */
+/datum/species/proc/get_biological_state(mob/living/carbon/human/H)
+ . = BIO_INORGANIC
+ if(HAS_FLESH in species_traits)
+ . |= BIO_JUST_FLESH
+ if(HAS_BONE in species_traits)
+ . |= BIO_JUST_BONE
+
/////////////
//BREATHING//
/////////////
@@ -2053,7 +1939,6 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
if(HAS_TRAIT(H, TRAIT_NOBREATH))
return TRUE
-
/datum/species/proc/handle_environment(datum/gas_mixture/environment, mob/living/carbon/human/H)
if(!environment)
return
@@ -2083,19 +1968,19 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
H.adjust_bodytemperature(natural*(1/(thermal_protection+1)) + min(thermal_protection * (loc_temp - H.bodytemperature) / BODYTEMP_HEAT_DIVISOR, BODYTEMP_HEATING_MAX))
switch((loc_temp - H.bodytemperature)*thermal_protection)
if(-INFINITY to -50)
- H.throw_alert("temp", /obj/screen/alert/cold, 3)
+ H.throw_alert("tempfeel", /obj/screen/alert/cold, 3)
if(-50 to -35)
- H.throw_alert("temp", /obj/screen/alert/cold, 2)
+ H.throw_alert("tempfeel", /obj/screen/alert/cold, 2)
if(-35 to -20)
- H.throw_alert("temp", /obj/screen/alert/cold, 1)
+ H.throw_alert("tempfeel", /obj/screen/alert/cold, 1)
if(-20 to 0) //This is the sweet spot where air is considered normal
- H.clear_alert("temp")
+ H.clear_alert("tempfeel")
if(0 to 15) //When the air around you matches your body's temperature, you'll start to feel warm.
- H.throw_alert("temp", /obj/screen/alert/hot, 1)
+ H.throw_alert("tempfeel", /obj/screen/alert/hot, 1)
if(15 to 30)
- H.throw_alert("temp", /obj/screen/alert/hot, 2)
+ H.throw_alert("tempfeel", /obj/screen/alert/hot, 2)
if(30 to INFINITY)
- H.throw_alert("temp", /obj/screen/alert/hot, 3)
+ H.throw_alert("tempfeel", /obj/screen/alert/hot, 3)
// +/- 50 degrees from 310K is the 'safe' zone, where no damage is dealt.
if(H.bodytemperature > BODYTEMP_HEAT_DAMAGE_LIMIT && !HAS_TRAIT(H, TRAIT_RESISTHEAT))
@@ -2113,6 +1998,14 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
else
firemodifier = min(firemodifier, 0)
burn_damage = max(log(2-firemodifier,(H.bodytemperature-BODYTEMP_NORMAL))-5,0) // this can go below 5 at log 2.5
+ if (burn_damage)
+ switch(burn_damage)
+ if(0 to 2)
+ H.throw_alert("temp", /obj/screen/alert/sweat, 1)
+ if(2 to 4)
+ H.throw_alert("temp", /obj/screen/alert/sweat, 2)
+ else
+ H.throw_alert("temp", /obj/screen/alert/sweat, 3)
burn_damage = burn_damage * heatmod * H.physiology.heat_mod
if (H.stat < UNCONSCIOUS && (prob(burn_damage) * 10) / 4) //40% for level 3 damage on humans
H.emote("scream")
@@ -2125,14 +2018,18 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
H.add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/cold, multiplicative_slowdown = ((BODYTEMP_COLD_DAMAGE_LIMIT - H.bodytemperature) / COLD_SLOWDOWN_FACTOR))
switch(H.bodytemperature)
if(200 to BODYTEMP_COLD_DAMAGE_LIMIT)
+ H.throw_alert("temp", /obj/screen/alert/shiver, 1)
H.apply_damage(COLD_DAMAGE_LEVEL_1*coldmod*H.physiology.cold_mod, BURN)
if(120 to 200)
+ H.throw_alert("temp", /obj/screen/alert/shiver, 2)
H.apply_damage(COLD_DAMAGE_LEVEL_2*coldmod*H.physiology.cold_mod, BURN)
else
+ H.throw_alert("temp", /obj/screen/alert/shiver, 3)
H.apply_damage(COLD_DAMAGE_LEVEL_3*coldmod*H.physiology.cold_mod, BURN)
else
H.remove_movespeed_modifier(/datum/movespeed_modifier/cold)
+ H.clear_alert("temp")
SEND_SIGNAL(H, COMSIG_CLEAR_MOOD_EVENT, "cold")
SEND_SIGNAL(H, COMSIG_CLEAR_MOOD_EVENT, "hot")
@@ -2245,7 +2142,6 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
/datum/species/proc/spec_stun(mob/living/carbon/human/H,amount)
if(H)
stop_wagging_tail(H)
-
. = stunmod * H.physiology.stun_mod * amount
//////////////
diff --git a/code/modules/mob/living/carbon/human/species_types/abductors.dm b/code/modules/mob/living/carbon/human/species_types/abductors.dm
index 1e77775efa..38e10e8662 100644
--- a/code/modules/mob/living/carbon/human/species_types/abductors.dm
+++ b/code/modules/mob/living/carbon/human/species_types/abductors.dm
@@ -3,7 +3,7 @@
id = "abductor"
say_mod = "gibbers"
sexes = FALSE
- species_traits = list(NOBLOOD,NOEYES,NOGENITALS,NOAROUSAL,CAN_SCAR)
+ species_traits = list(NOBLOOD,NOEYES,NOGENITALS,NOAROUSAL,HAS_FLESH,HAS_BONE)
inherent_traits = list(TRAIT_VIRUSIMMUNE,TRAIT_CHUNKYFINGERS,TRAIT_NOHUNGER,TRAIT_NOBREATH)
mutanttongue = /obj/item/organ/tongue/abductor
species_type = "alien"
diff --git a/code/modules/mob/living/carbon/human/species_types/angel.dm b/code/modules/mob/living/carbon/human/species_types/angel.dm
index d6860e1249..2cf054c7b9 100644
--- a/code/modules/mob/living/carbon/human/species_types/angel.dm
+++ b/code/modules/mob/living/carbon/human/species_types/angel.dm
@@ -2,7 +2,7 @@
name = "Angel"
id = "angel"
default_color = "FFFFFF"
- species_traits = list(EYECOLOR,HAIR,FACEHAIR,LIPS,CAN_SCAR)
+ species_traits = list(EYECOLOR,HAIR,FACEHAIR,LIPS,HAS_FLESH,HAS_BONE)
mutant_bodyparts = list("tail_human" = "None", "ears" = "None", "wings" = "Angel")
use_skintones = USE_SKINTONES_GRAYSCALE_CUSTOM
no_equip = list(SLOT_BACK)
diff --git a/code/modules/mob/living/carbon/human/species_types/bugmen.dm b/code/modules/mob/living/carbon/human/species_types/bugmen.dm
index 25fb51aa87..16b371c772 100644
--- a/code/modules/mob/living/carbon/human/species_types/bugmen.dm
+++ b/code/modules/mob/living/carbon/human/species_types/bugmen.dm
@@ -3,7 +3,7 @@
id = "insect"
say_mod = "chitters"
default_color = "00FF00"
- species_traits = list(LIPS,EYECOLOR,HAIR,FACEHAIR,MUTCOLORS,HORNCOLOR,WINGCOLOR,CAN_SCAR)
+ species_traits = list(LIPS,EYECOLOR,HAIR,FACEHAIR,MUTCOLORS,HORNCOLOR,WINGCOLOR,HAS_FLESH,HAS_BONE)
inherent_biotypes = MOB_ORGANIC|MOB_HUMANOID|MOB_BUG
mutant_bodyparts = list("mcolor" = "FFFFFF","mcolor2" = "FFFFFF","mcolor3" = "FFFFFF", "mam_tail" = "None", "mam_ears" = "None",
"insect_wings" = "None", "insect_fluff" = "None", "mam_snouts" = "None", "taur" = "None", "insect_markings" = "None")
@@ -13,10 +13,11 @@
meat = /obj/item/reagent_containers/food/snacks/meat/slab/human/mutant/insect
liked_food = MEAT | FRUIT
disliked_food = TOXIC
- icon_limbs = DEFAULT_BODYPART_ICON_CITADEL
exotic_bloodtype = "BUG"
exotic_blood_color = BLOOD_COLOR_BUG
tail_type = "mam_tail"
wagging_type = "mam_waggingtail"
- species_type = "insect"
\ No newline at end of file
+ species_type = "insect"
+
+ allowed_limb_ids = list("insect","apid","moth","moth_not_greyscale")
diff --git a/code/modules/mob/living/carbon/human/species_types/dullahan.dm b/code/modules/mob/living/carbon/human/species_types/dullahan.dm
index dcd81c90b9..f3b499093b 100644
--- a/code/modules/mob/living/carbon/human/species_types/dullahan.dm
+++ b/code/modules/mob/living/carbon/human/species_types/dullahan.dm
@@ -2,8 +2,8 @@
name = "Dullahan"
id = "dullahan"
default_color = "FFFFFF"
- species_traits = list(EYECOLOR,HAIR,FACEHAIR,LIPS)
- inherent_traits = list(TRAIT_NOHUNGER,TRAIT_NOBREATH,CAN_SCAR)
+ species_traits = list(EYECOLOR,HAIR,FACEHAIR,LIPS,HAS_FLESH,HAS_BONE)
+ inherent_traits = list(TRAIT_NOHUNGER,TRAIT_NOBREATH)
mutant_bodyparts = list("tail_human" = "None", "ears" = "None", "deco_wings" = "None")
use_skintones = USE_SKINTONES_GRAYSCALE_CUSTOM
mutant_brain = /obj/item/organ/brain/dullahan
@@ -83,17 +83,7 @@
/obj/item/organ/tongue/dullahan
zone = "abstract"
- modifies_speech = TRUE
-
-/obj/item/organ/tongue/dullahan/handle_speech(datum/source, list/speech_args)
- if(ishuman(owner))
- var/mob/living/carbon/human/H = owner
- if(isdullahan(H))
- var/datum/species/dullahan/D = H.dna.species
- if(isobj(D.myhead.loc))
- var/obj/O = D.myhead.loc
- O.say(speech_args[SPEECH_MESSAGE])
- speech_args[SPEECH_MESSAGE] = ""
+ accents = list(/datum/accent/dullahan)
/obj/item/organ/ears/dullahan
zone = "abstract"
diff --git a/code/modules/mob/living/carbon/human/species_types/dwarves.dm b/code/modules/mob/living/carbon/human/species_types/dwarves.dm
index 8eb0e0181c..a465e7c3a9 100644
--- a/code/modules/mob/living/carbon/human/species_types/dwarves.dm
+++ b/code/modules/mob/living/carbon/human/species_types/dwarves.dm
@@ -6,7 +6,7 @@ GLOBAL_LIST_INIT(dwarf_last, world.file2list("strings/names/dwarf_last.txt")) //
name = "Dwarf"
id = "dwarf" //Also called Homo sapiens pumilionis
default_color = "FFFFFF"
- species_traits = list(EYECOLOR,HAIR,FACEHAIR,LIPS,CAN_SCAR)
+ species_traits = list(EYECOLOR,HAIR,FACEHAIR,LIPS,HAS_FLESH,HAS_BONE)
inherent_traits = list(TRAIT_DWARF,TRAIT_SNOB)
limbs_id = "human"
use_skintones = USE_SKINTONES_GRAYSCALE_CUSTOM
diff --git a/code/modules/mob/living/carbon/human/species_types/ethereal.dm b/code/modules/mob/living/carbon/human/species_types/ethereal.dm
new file mode 100644
index 0000000000..cd9b3b80c7
--- /dev/null
+++ b/code/modules/mob/living/carbon/human/species_types/ethereal.dm
@@ -0,0 +1,182 @@
+#define ETHEREAL_COLORS list("#00ffff", "#ffc0cb", "#9400D3", "#4B0082", "#0000FF", "#00FF00", "#FFFF00", "#FF7F00", "#FF0000")
+
+/datum/species/ethereal
+ name = "Ethereal"
+ id = "ethereal"
+ attack_verb = "burn"
+ attack_sound = 'sound/weapons/etherealhit.ogg'
+ miss_sound = 'sound/weapons/etherealmiss.ogg'
+ meat = /obj/item/reagent_containers/food/snacks/meat/slab/human/mutant/ethereal
+ mutantstomach = /obj/item/organ/stomach/ethereal
+ mutanttongue = /obj/item/organ/tongue/ethereal
+ exotic_blood = /datum/reagent/consumable/liquidelectricity //Liquid Electricity. fuck you think of something better gamer
+ siemens_coeff = 0.5 //They thrive on energy
+ brutemod = 1.25 //They're weak to punches
+ attack_type = BURN //burn bish
+ damage_overlay_type = "" //We are too cool for regular damage overlays
+ species_traits = list(MUTCOLORS, HAIR, HAS_FLESH, HAS_BONE) // i mean i guess they have blood so they can have wounds too
+ species_language_holder = /datum/language_holder/ethereal
+ inherent_traits = list(TRAIT_NOHUNGER)
+ sexes = FALSE
+ toxic_food = NONE
+ /*
+ citadel doesn't have per-species temperatures, yet
+ // Body temperature for ethereals is much higher then humans as they like hotter environments
+ bodytemp_normal = (BODYTEMP_NORMAL + 50)
+ bodytemp_heat_damage_limit = FIRE_MINIMUM_TEMPERATURE_TO_SPREAD // about 150C
+ // Cold temperatures hurt faster as it is harder to move with out the heat energy
+ bodytemp_cold_damage_limit = (T20C - 10) // about 10c
+ */
+ hair_color = "mutcolor"
+ hair_alpha = 140
+ var/current_color
+ var/EMPeffect = FALSE
+ var/emageffect = FALSE
+ var/r1
+ var/g1
+ var/b1
+ var/static/r2 = 237
+ var/static/g2 = 164
+ var/static/b2 = 149
+ //this is shit but how do i fix it? no clue.
+ var/drain_time = 0 //used to keep ethereals from spam draining power sources
+
+/datum/species/ethereal/on_species_gain(mob/living/carbon/C, datum/species/old_species, pref_load)
+ .=..()
+ if(ishuman(C))
+ var/mob/living/carbon/human/H = C
+ default_color = "#" + H.dna.features["mcolor"]
+ r1 = GETREDPART(default_color)
+ g1 = GETGREENPART(default_color)
+ b1 = GETBLUEPART(default_color)
+ spec_updatehealth(H)
+ RegisterSignal(C, COMSIG_ATOM_EMAG_ACT, .proc/on_emag_act)
+ RegisterSignal(C, COMSIG_ATOM_EMP_ACT, .proc/on_emp_act)
+
+/datum/species/ethereal/on_species_loss(mob/living/carbon/human/C, datum/species/new_species, pref_load)
+ .=..()
+ C.set_light(0)
+ UnregisterSignal(C, COMSIG_ATOM_EMAG_ACT)
+ UnregisterSignal(C, COMSIG_ATOM_EMP_ACT)
+
+/datum/species/ethereal/random_name(gender,unique,lastname)
+ if(unique)
+ return random_unique_ethereal_name()
+
+ var/randname = ethereal_name()
+
+ return randname
+
+/datum/species/ethereal/spec_updatehealth(mob/living/carbon/human/H)
+ .=..()
+ if(H.stat != DEAD && !EMPeffect)
+ var/healthpercent = max(H.health, 0) / 100
+ if(!emageffect)
+ current_color = rgb(r2 + ((r1-r2)*healthpercent), g2 + ((g1-g2)*healthpercent), b2 + ((b1-b2)*healthpercent))
+ H.set_light(1 + (2 * healthpercent), 1 + (1 * healthpercent), current_color)
+ fixed_mut_color = copytext_char(current_color, 2)
+ else
+ H.set_light(0)
+ fixed_mut_color = rgb(128,128,128)
+ H.update_body()
+
+/datum/species/ethereal/proc/on_emp_act(mob/living/carbon/human/H, severity)
+ EMPeffect = TRUE
+ spec_updatehealth(H)
+ to_chat(H, "You feel the light of your body leave you.")
+ switch(severity)
+ if(EMP_LIGHT)
+ addtimer(CALLBACK(src, .proc/stop_emp, H), 10 SECONDS, TIMER_UNIQUE|TIMER_OVERRIDE) //We're out for 10 seconds
+ if(EMP_HEAVY)
+ addtimer(CALLBACK(src, .proc/stop_emp, H), 20 SECONDS, TIMER_UNIQUE|TIMER_OVERRIDE) //We're out for 20 seconds
+
+/datum/species/ethereal/proc/on_emag_act(mob/living/carbon/human/H, mob/user)
+ if(emageffect)
+ return
+ emageffect = TRUE
+ if(user)
+ to_chat(user, "You tap [H] on the back with your card.")
+ H.visible_message("[H] starts flickering in an array of colors!")
+ handle_emag(H)
+ addtimer(CALLBACK(src, .proc/stop_emag, H), 30 SECONDS) //Disco mode for 30 seconds! This doesn't affect the ethereal at all besides either annoying some players, or making someone look badass.
+
+
+/datum/species/ethereal/spec_life(mob/living/carbon/human/H)
+ .=..()
+ handle_charge(H)
+
+
+/datum/species/ethereal/proc/stop_emp(mob/living/carbon/human/H)
+ EMPeffect = FALSE
+ spec_updatehealth(H)
+ to_chat(H, "You feel more energized as your shine comes back.")
+
+
+/datum/species/ethereal/proc/handle_emag(mob/living/carbon/human/H)
+ if(!emageffect)
+ return
+ current_color = pick(ETHEREAL_COLORS)
+ spec_updatehealth(H)
+ addtimer(CALLBACK(src, .proc/handle_emag, H), 5) //Call ourselves every 0.5 seconds to change color
+
+/datum/species/ethereal/proc/stop_emag(mob/living/carbon/human/H)
+ emageffect = FALSE
+ spec_updatehealth(H)
+ H.visible_message("[H] stops flickering and goes back to their normal state!")
+
+/datum/species/ethereal/proc/handle_charge(mob/living/carbon/human/H)
+ brutemod = 1.25
+ switch(get_charge(H))
+ if(ETHEREAL_CHARGE_NONE)
+ H.throw_alert("ethereal_charge", /obj/screen/alert/etherealcharge, 3)
+ if(ETHEREAL_CHARGE_NONE to ETHEREAL_CHARGE_LOWPOWER)
+ H.throw_alert("ethereal_charge", /obj/screen/alert/etherealcharge, 2)
+ if(H.health > 10.5)
+ apply_damage(0.65, TOX, null, null, H)
+ brutemod = 1.75
+ if(ETHEREAL_CHARGE_LOWPOWER to ETHEREAL_CHARGE_NORMAL)
+ H.throw_alert("ethereal_charge", /obj/screen/alert/etherealcharge, 1)
+ brutemod = 1.5
+ if(ETHEREAL_CHARGE_FULL to ETHEREAL_CHARGE_OVERLOAD)
+ H.throw_alert("ethereal_overcharge", /obj/screen/alert/ethereal_overcharge, 1)
+ apply_damage(0.2, TOX, null, null, H)
+ brutemod = 1.5
+ if(ETHEREAL_CHARGE_OVERLOAD to ETHEREAL_CHARGE_DANGEROUS)
+ H.throw_alert("ethereal_overcharge", /obj/screen/alert/ethereal_overcharge, 2)
+ apply_damage(0.65, TOX, null, null, H)
+ brutemod = 1.75
+ if(prob(10)) //10% each tick for ethereals to explosively release excess energy if it reaches dangerous levels
+ discharge_process(H)
+ else
+ H.clear_alert("ethereal_charge")
+ H.clear_alert("ethereal_overcharge")
+
+/datum/species/ethereal/proc/discharge_process(mob/living/carbon/human/H)
+ to_chat(H, "You begin to lose control over your charge!")
+ H.visible_message("[H] begins to spark violently!")
+ var/static/mutable_appearance/overcharge //shameless copycode from lightning spell
+ overcharge = overcharge || mutable_appearance('icons/effects/effects.dmi', "electricity", EFFECTS_LAYER)
+ H.add_overlay(overcharge)
+ if(do_mob(H, H, 50, 1))
+ H.flash_lighting_fx(5, 7, current_color)
+ var/obj/item/organ/stomach/ethereal/stomach = H.getorganslot(ORGAN_SLOT_STOMACH)
+ playsound(H, 'sound/magic/lightningshock.ogg', 100, TRUE, extrarange = 5)
+ H.cut_overlay(overcharge)
+ tesla_zap(H, 2, stomach.crystal_charge*50, ZAP_OBJ_DAMAGE | ZAP_ALLOW_DUPLICATES)
+ if(istype(stomach))
+ stomach.adjust_charge(100 - stomach.crystal_charge)
+ to_chat(H, "You violently discharge energy!")
+ H.visible_message("[H] violently discharges energy!")
+ if(prob(10)) //chance of developing heart disease to dissuade overcharging oneself
+ var/datum/disease/D = new /datum/disease/heart_failure
+ H.ForceContractDisease(D)
+ to_chat(H, "You're pretty sure you just felt your heart stop for a second there..")
+ H.playsound_local(H, 'sound/effects/singlebeat.ogg', 100, 0)
+ H.Paralyze(100)
+ return
+
+/datum/species/ethereal/proc/get_charge(mob/living/carbon/H) //this feels like it should be somewhere else. Eh?
+ var/obj/item/organ/stomach/ethereal/stomach = H.getorganslot(ORGAN_SLOT_STOMACH)
+ if(istype(stomach))
+ return stomach.crystal_charge
+ return ETHEREAL_CHARGE_NONE
diff --git a/code/modules/mob/living/carbon/human/species_types/flypeople.dm b/code/modules/mob/living/carbon/human/species_types/flypeople.dm
index b99c3e2bc2..c56adaf1b0 100644
--- a/code/modules/mob/living/carbon/human/species_types/flypeople.dm
+++ b/code/modules/mob/living/carbon/human/species_types/flypeople.dm
@@ -2,7 +2,7 @@
name = "Anthromorphic Fly"
id = "fly"
say_mod = "buzzes"
- species_traits = list(NOEYES,CAN_SCAR)
+ species_traits = list(NOEYES,HAS_FLESH,HAS_BONE)
inherent_biotypes = MOB_ORGANIC|MOB_HUMANOID|MOB_BUG
mutanttongue = /obj/item/organ/tongue/fly
mutantliver = /obj/item/organ/liver/fly
diff --git a/code/modules/mob/living/carbon/human/species_types/furrypeople.dm b/code/modules/mob/living/carbon/human/species_types/furrypeople.dm
index 18359f415f..534536d6e7 100644
--- a/code/modules/mob/living/carbon/human/species_types/furrypeople.dm
+++ b/code/modules/mob/living/carbon/human/species_types/furrypeople.dm
@@ -2,8 +2,7 @@
name = "Anthromorph"
id = "mammal"
default_color = "4B4B4B"
- icon_limbs = DEFAULT_BODYPART_ICON_CITADEL
- species_traits = list(MUTCOLORS,EYECOLOR,LIPS,HAIR,HORNCOLOR,WINGCOLOR,CAN_SCAR)
+ species_traits = list(MUTCOLORS,EYECOLOR,LIPS,HAIR,HORNCOLOR,WINGCOLOR,HAS_FLESH,HAS_BONE)
inherent_biotypes = MOB_ORGANIC|MOB_HUMANOID|MOB_BEAST
mutant_bodyparts = list("mcolor" = "FFFFFF","mcolor2" = "FFFFFF","mcolor3" = "FFFFFF", "mam_snouts" = "Husky", "mam_tail" = "Husky", "mam_ears" = "Husky", "deco_wings" = "None",
"mam_body_markings" = "Husky", "taur" = "None", "horns" = "None", "legs" = "Plantigrade", "meat_type" = "Mammalian")
@@ -17,3 +16,5 @@
tail_type = "mam_tail"
wagging_type = "mam_waggingtail"
species_type = "furry"
+
+ allowed_limb_ids = list("mammal","aquatic","avian")
\ No newline at end of file
diff --git a/code/modules/mob/living/carbon/human/species_types/humans.dm b/code/modules/mob/living/carbon/human/species_types/humans.dm
index e93a5106c9..f175ddb921 100644
--- a/code/modules/mob/living/carbon/human/species_types/humans.dm
+++ b/code/modules/mob/living/carbon/human/species_types/humans.dm
@@ -3,7 +3,7 @@
id = "human"
default_color = "FFFFFF"
- species_traits = list(EYECOLOR,HAIR,FACEHAIR,LIPS,MUTCOLORS_PARTSONLY,WINGCOLOR,CAN_SCAR)
+ species_traits = list(EYECOLOR,HAIR,FACEHAIR,LIPS,MUTCOLORS_PARTSONLY,WINGCOLOR,HAS_FLESH,HAS_BONE)
mutant_bodyparts = list("mcolor" = "FFFFFF", "mcolor2" = "FFFFFF","mcolor3" = "FFFFFF","tail_human" = "None", "ears" = "None", "taur" = "None", "deco_wings" = "None")
use_skintones = USE_SKINTONES_GRAYSCALE_CUSTOM
skinned_type = /obj/item/stack/sheet/animalhide/human
diff --git a/code/modules/mob/living/carbon/human/species_types/ipc.dm b/code/modules/mob/living/carbon/human/species_types/ipc.dm
index b5862b2bd2..806782a8b2 100644
--- a/code/modules/mob/living/carbon/human/species_types/ipc.dm
+++ b/code/modules/mob/living/carbon/human/species_types/ipc.dm
@@ -3,10 +3,9 @@
id = "ipc"
say_mod = "beeps"
default_color = "00FF00"
- icon_limbs = DEFAULT_BODYPART_ICON_CITADEL
blacklisted = 0
sexes = 0
- species_traits = list(MUTCOLORS,NOEYES,NOTRANSSTING)
+ species_traits = list(MUTCOLORS,NOEYES,NOTRANSSTING,HAS_FLESH,HAS_BONE)
inherent_biotypes = MOB_ROBOTIC|MOB_HUMANOID
mutant_bodyparts = list("ipc_screen" = "Blank", "ipc_antenna" = "None")
meat = /obj/item/reagent_containers/food/snacks/meat/slab/human/mutant/ipc
diff --git a/code/modules/mob/living/carbon/human/species_types/jellypeople.dm b/code/modules/mob/living/carbon/human/species_types/jellypeople.dm
index dc821c11ef..43f5ca5526 100644
--- a/code/modules/mob/living/carbon/human/species_types/jellypeople.dm
+++ b/code/modules/mob/living/carbon/human/species_types/jellypeople.dm
@@ -4,7 +4,7 @@
id = "jelly"
default_color = "00FF90"
say_mod = "chirps"
- species_traits = list(MUTCOLORS,EYECOLOR,HAIR,FACEHAIR,WINGCOLOR)
+ species_traits = list(MUTCOLORS,EYECOLOR,HAIR,FACEHAIR,WINGCOLOR,HAS_FLESH)
mutantlungs = /obj/item/organ/lungs/slime
mutant_heart = /obj/item/organ/heart/slime
mutant_bodyparts = list("mcolor" = "FFFFFF", "mam_tail" = "None", "mam_ears" = "None", "mam_snouts" = "None", "taur" = "None", "deco_wings" = "None")
@@ -18,7 +18,8 @@
var/datum/action/innate/regenerate_limbs/regenerate_limbs
var/datum/action/innate/slime_change/slime_change //CIT CHANGE
liked_food = TOXIC | MEAT
- toxic_food = null
+ disliked_food = null
+ toxic_food = ANTITOXIC
coldmod = 6 // = 3x cold damage
heatmod = 0.5 // = 1/4x heat damage
burnmod = 0.5 // = 1/2x generic burn damage
@@ -287,11 +288,16 @@
else
ui_interact(owner)
-/datum/action/innate/swap_body/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.always_state)
+/datum/action/innate/swap_body/ui_host(mob/user)
+ return owner
- ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+/datum/action/innate/swap_body/ui_state(mob/user)
+ return GLOB.not_incapacitated_state
+
+/datum/action/innate/swap_body/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
- ui = new(user, src, ui_key, "SlimeBodySwapper", name, 400, 400, master_ui, state)
+ ui = new(user, src, "SlimeBodySwapper", name)
ui.open()
/datum/action/innate/swap_body/ui_data(mob/user)
@@ -361,7 +367,8 @@
return
switch(action)
if("swap")
- var/mob/living/carbon/human/selected = locate(params["ref"])
+ var/datum/species/jelly/slime/SS = H.dna.species
+ var/mob/living/carbon/human/selected = locate(params["ref"]) in SS.bodies
if(!can_swap(selected))
return
SStgui.close_uis(src)
@@ -498,7 +505,7 @@
else if (select_alteration == "Ears")
var/list/snowflake_ears_list = list("Normal" = null)
for(var/path in GLOB.mam_ears_list)
- var/datum/sprite_accessory/mam_ears/instance = GLOB.mam_ears_list[path]
+ var/datum/sprite_accessory/ears/mam_ears/instance = GLOB.mam_ears_list[path]
if(istype(instance, /datum/sprite_accessory))
var/datum/sprite_accessory/S = instance
if((!S.ckeys_allowed) || (S.ckeys_allowed.Find(H.client.ckey)))
@@ -512,7 +519,7 @@
else if (select_alteration == "Snout")
var/list/snowflake_snouts_list = list("Normal" = null)
for(var/path in GLOB.mam_snouts_list)
- var/datum/sprite_accessory/mam_snouts/instance = GLOB.mam_snouts_list[path]
+ var/datum/sprite_accessory/snouts/mam_snouts/instance = GLOB.mam_snouts_list[path]
if(istype(instance, /datum/sprite_accessory))
var/datum/sprite_accessory/S = instance
if((!S.ckeys_allowed) || (S.ckeys_allowed.Find(H.client.ckey)))
@@ -543,7 +550,7 @@
else if (select_alteration == "Tail")
var/list/snowflake_tails_list = list("Normal" = null)
for(var/path in GLOB.mam_tails_list)
- var/datum/sprite_accessory/mam_tails/instance = GLOB.mam_tails_list[path]
+ var/datum/sprite_accessory/tails/mam_tails/instance = GLOB.mam_tails_list[path]
if(istype(instance, /datum/sprite_accessory))
var/datum/sprite_accessory/S = instance
if((!S.ckeys_allowed) || (S.ckeys_allowed.Find(H.client.ckey)))
diff --git a/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm b/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm
index 7bcf6c89f4..a8e3f89957 100644
--- a/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm
+++ b/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm
@@ -4,14 +4,13 @@
id = "lizard"
say_mod = "hisses"
default_color = "00FF00"
- species_traits = list(MUTCOLORS,EYECOLOR,HAIR,FACEHAIR,LIPS,HORNCOLOR,WINGCOLOR,CAN_SCAR)
- mutant_bodyparts = list("tail_lizard", "snout", "spines", "horns", "frills", "body_markings", "legs", "taur", "deco_wings")
+ species_traits = list(MUTCOLORS,EYECOLOR,HAIR,FACEHAIR,LIPS,HORNCOLOR,WINGCOLOR,HAS_FLESH,HAS_BONE)
inherent_biotypes = MOB_ORGANIC|MOB_HUMANOID|MOB_REPTILE
mutanttongue = /obj/item/organ/tongue/lizard
mutanttail = /obj/item/organ/tail/lizard
coldmod = 1.5
heatmod = 0.67
- mutant_bodyparts = list("mcolor" = "0F0", "mcolor2" = "0F0", "mcolor3" = "0F0", "tail_lizard" = "Smooth", "snout" = "Round",
+ mutant_bodyparts = list("mcolor" = "0F0", "mcolor2" = "0F0", "mcolor3" = "0F0", "tail_lizard" = "Smooth", "mam_snouts" = "Round",
"horns" = "None", "frills" = "None", "spines" = "None", "body_markings" = "None",
"legs" = "Digitigrade", "taur" = "None", "deco_wings" = "None")
attack_verb = "slash"
@@ -52,6 +51,7 @@
species_traits = list(MUTCOLORS,EYECOLOR,LIPS,DIGITIGRADE)
inherent_traits = list(TRAIT_CHUNKYFINGERS)
mutantlungs = /obj/item/organ/lungs/ashwalker
+ mutanteyes = /obj/item/organ/eyes/night_vision
burnmod = 0.9
brutemod = 0.9
species_language_holder = /datum/language_holder/lizard/ash
diff --git a/code/modules/mob/living/carbon/human/species_types/mushpeople.dm b/code/modules/mob/living/carbon/human/species_types/mushpeople.dm
index 4cb5442bf8..f91c3518a3 100644
--- a/code/modules/mob/living/carbon/human/species_types/mushpeople.dm
+++ b/code/modules/mob/living/carbon/human/species_types/mushpeople.dm
@@ -8,7 +8,7 @@
nojumpsuit = TRUE
say_mod = "poofs" //what does a mushroom sound like
- species_traits = list(MUTCOLORS, NOEYES, NO_UNDERWEAR,NOGENITALS,NOAROUSAL,CAN_SCAR)
+ species_traits = list(MUTCOLORS, NOEYES, NO_UNDERWEAR,NOGENITALS,NOAROUSAL,HAS_FLESH,HAS_BONE)
inherent_traits = list(TRAIT_NOBREATH)
speedmod = 1.5 //faster than golems but not by much
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 5008fb13cd..8c30adb8f9 100644
--- a/code/modules/mob/living/carbon/human/species_types/plasmamen.dm
+++ b/code/modules/mob/living/carbon/human/species_types/plasmamen.dm
@@ -4,7 +4,7 @@
say_mod = "rattles"
sexes = 0
meat = /obj/item/stack/sheet/mineral/plasma
- species_traits = list(NOBLOOD,NOTRANSSTING,NOGENITALS)
+ species_traits = list(NOBLOOD,NOTRANSSTING,NOGENITALS,HAS_BONE)
inherent_traits = list(TRAIT_RESISTCOLD,TRAIT_RADIMMUNE,TRAIT_NOHUNGER,TRAIT_CALCIUM_HEALER)
inherent_biotypes = MOB_HUMANOID|MOB_MINERAL
mutantlungs = /obj/item/organ/lungs/plasmaman
@@ -57,89 +57,10 @@
..()
/datum/species/plasmaman/before_equip_job(datum/job/J, mob/living/carbon/human/H, visualsOnly = FALSE)
- var/current_job = J?.title
var/datum/outfit/plasmaman/O = new /datum/outfit/plasmaman
- switch(current_job)
- if("Chaplain")
- O = new /datum/outfit/plasmaman/chaplain
-
- if("Curator")
- O = new /datum/outfit/plasmaman/curator
-
- if("Janitor")
- O = new /datum/outfit/plasmaman/janitor
-
- if("Botanist")
- O = new /datum/outfit/plasmaman/botany
-
- if("Bartender", "Lawyer")
- O = new /datum/outfit/plasmaman/bar
-
- if("Cook")
- O = new /datum/outfit/plasmaman/chef
-
- if("Security Officer")
- O = new /datum/outfit/plasmaman/security
-
- if("Detective")
- O = new /datum/outfit/plasmaman/detective
-
- if("Warden")
- O = new /datum/outfit/plasmaman/warden
-
- if("Cargo Technician", "Quartermaster")
- O = new /datum/outfit/plasmaman/cargo
-
- if("Shaft Miner")
- O = new /datum/outfit/plasmaman/mining
-
- if("Medical Doctor")
- O = new /datum/outfit/plasmaman/medical
-
- if("Chemist")
- O = new /datum/outfit/plasmaman/chemist
-
- if("Geneticist")
- O = new /datum/outfit/plasmaman/genetics
-
- if("Roboticist")
- O = new /datum/outfit/plasmaman/robotics
-
- if("Virologist")
- O = new /datum/outfit/plasmaman/viro
-
- if("Scientist")
- O = new /datum/outfit/plasmaman/science
-
- if("Station Engineer")
- O = new /datum/outfit/plasmaman/engineering
-
- if("Atmospheric Technician")
- O = new /datum/outfit/plasmaman/atmospherics
-
- if("Captain")
- O = new /datum/outfit/plasmaman/captain
-
- if("Head of Personnel")
- O = new /datum/outfit/plasmaman/hop
-
- if("Head of Security")
- O = new /datum/outfit/plasmaman/hos
-
- if("Chief Engineer")
- O = new /datum/outfit/plasmaman/ce
-
- if("Chief Medical Officer")
- O = new /datum/outfit/plasmaman/cmo
-
- if("Research Director")
- O = new /datum/outfit/plasmaman/rd
-
- if("Mime")
- O = new /datum/outfit/plasmaman/mime
-
- if("Clown")
- O = new /datum/outfit/plasmaman/clown
+ if(J)
+ if(J.plasma_outfit)
+ O = new J.plasma_outfit
H.equipOutfit(O, visualsOnly)
H.internal = H.get_item_for_held_index(2)
diff --git a/code/modules/mob/living/carbon/human/species_types/podpeople.dm b/code/modules/mob/living/carbon/human/species_types/podpeople.dm
index 706995d611..0f62953e6c 100644
--- a/code/modules/mob/living/carbon/human/species_types/podpeople.dm
+++ b/code/modules/mob/living/carbon/human/species_types/podpeople.dm
@@ -3,7 +3,7 @@
name = "Anthromorphic Plant"
id = "pod"
default_color = "59CE00"
- species_traits = list(MUTCOLORS,EYECOLOR,CAN_SCAR)
+ species_traits = list(MUTCOLORS,EYECOLOR,CAN_SCAR,HAS_FLESH,HAS_BONE)
attack_verb = "slash"
attack_sound = 'sound/weapons/slice.ogg'
miss_sound = 'sound/weapons/slashmiss.ogg'
@@ -21,6 +21,8 @@
species_type = "plant"
+ allowed_limb_ids = list("pod","mush")
+
/datum/species/pod/on_species_gain(mob/living/carbon/C, datum/species/old_species)
. = ..()
C.faction |= "plants"
diff --git a/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm b/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm
index 9f7d165edf..228a69c30f 100644
--- a/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm
+++ b/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm
@@ -9,8 +9,8 @@
blacklisted = 1
ignored_by = list(/mob/living/simple_animal/hostile/faithless)
meat = /obj/item/reagent_containers/food/snacks/meat/slab/human/mutant/shadow
- species_traits = list(NOBLOOD,NOEYES,CAN_SCAR)
- inherent_traits = list(TRAIT_RADIMMUNE,TRAIT_VIRUSIMMUNE,TRAIT_NOBREATH,CAN_SCAR)
+ species_traits = list(NOBLOOD,NOEYES,HAS_FLESH,HAS_BONE)
+ inherent_traits = list(TRAIT_RADIMMUNE,TRAIT_VIRUSIMMUNE,TRAIT_NOBREATH)
dangerous_existence = 1
mutanteyes = /obj/item/organ/eyes/night_vision
@@ -82,13 +82,11 @@
M.AddSpell(SW)
shadowwalk = SW
-
/obj/item/organ/brain/nightmare/Remove(special = FALSE)
if(shadowwalk && owner)
owner.RemoveSpell(shadowwalk)
return ..()
-
/obj/item/organ/heart/nightmare
name = "heart of darkness"
desc = "An alien organ that twists and writhes when exposed to light."
@@ -166,7 +164,7 @@
righthand_file = 'icons/mob/inhands/antag/changeling_righthand.dmi'
item_flags = ABSTRACT | DROPDEL
w_class = WEIGHT_CLASS_HUGE
- sharpness = IS_SHARP
+ sharpness = SHARP_EDGED
total_mass = TOTAL_MASS_HAND_REPLACEMENT
/obj/item/light_eater/Initialize()
@@ -185,6 +183,8 @@
T.ScrapeAway(flags = CHANGETURF_INHERIT_AIR)
else if(isliving(AM))
var/mob/living/L = AM
+ if(isethereal(AM))
+ AM.emp_act(EMP_LIGHT)
if(iscyborg(AM))
var/mob/living/silicon/robot/borg = AM
if(borg.lamp_intensity)
diff --git a/code/modules/mob/living/carbon/human/species_types/skeletons.dm b/code/modules/mob/living/carbon/human/species_types/skeletons.dm
index 54d56fea06..1b6bef9dc6 100644
--- a/code/modules/mob/living/carbon/human/species_types/skeletons.dm
+++ b/code/modules/mob/living/carbon/human/species_types/skeletons.dm
@@ -5,8 +5,8 @@
blacklisted = 0
sexes = 0
meat = /obj/item/reagent_containers/food/snacks/meat/slab/human/mutant/skeleton
- species_traits = list(NOBLOOD,NOGENITALS,NOAROUSAL)
- inherent_traits = list(TRAIT_NOBREATH,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NOHUNGER,TRAIT_EASYDISMEMBER,TRAIT_LIMBATTACHMENT,TRAIT_FAKEDEATH, TRAIT_CALCIUM_HEALER)
+ species_traits = list(NOBLOOD,NOGENITALS,NOAROUSAL,HAS_BONE,NOTRANSSTING)
+ inherent_traits = list(TRAIT_EASYDISMEMBER,TRAIT_LIMBATTACHMENT,TRAIT_CALCIUM_HEALER)
inherent_biotypes = MOB_UNDEAD|MOB_HUMANOID
mutanttongue = /obj/item/organ/tongue/bone
damage_overlay_type = ""//let's not show bloody wounds or burns over bones.
@@ -19,12 +19,12 @@
/datum/species/skeleton/New()
if(SSevents.holidays && SSevents.holidays[HALLOWEEN]) //skeletons are stronger during the spooky season!
- inherent_traits |= list(TRAIT_RESISTHEAT,TRAIT_RESISTCOLD)
+ inherent_traits |= list(TRAIT_RESISTHEAT, TRAIT_NOBREATH, TRAIT_PIERCEIMMUNE, TRAIT_FAKEDEATH, TRAIT_RESISTCOLD, TRAIT_RADIMMUNE)
brutemod = 1
burnmod = 1
..()
-/datum/species/skeleton/greater/check_roundstart_eligible()
+/datum/species/skeleton/check_roundstart_eligible()
if(SSevents.holidays && SSevents.holidays[HALLOWEEN])
return TRUE
return ..()
diff --git a/code/modules/mob/living/carbon/human/species_types/synthliz.dm b/code/modules/mob/living/carbon/human/species_types/synthliz.dm
index 41b3d3a96a..70afa2f476 100644
--- a/code/modules/mob/living/carbon/human/species_types/synthliz.dm
+++ b/code/modules/mob/living/carbon/human/species_types/synthliz.dm
@@ -1,10 +1,9 @@
/datum/species/synthliz
name = "Synthetic Lizardperson"
id = "synthliz"
- icon_limbs = DEFAULT_BODYPART_ICON_CITADEL
say_mod = "beeps"
default_color = "00FF00"
- species_traits = list(MUTCOLORS,NOTRANSSTING,EYECOLOR,LIPS,HAIR)
+ species_traits = list(MUTCOLORS,NOTRANSSTING,EYECOLOR,LIPS,HAIR,HAS_FLESH,HAS_BONE)
inherent_biotypes = MOB_ROBOTIC|MOB_HUMANOID
mutant_bodyparts = list("ipc_antenna" = "Synthetic Lizard - Antennae","mam_tail" = "Synthetic Lizard", "mam_snouts" = "Synthetic Lizard - Snout", "legs" = "Digitigrade", "mam_body_markings" = "Synthetic Lizard - Plates", "taur" = "None")
meat = /obj/item/reagent_containers/food/snacks/meat/slab/human/mutant/ipc
@@ -22,4 +21,4 @@
tail_type = "mam_tail"
wagging_type = "mam_waggingtail"
- species_type = "robotic"
\ No newline at end of file
+ species_type = "robotic"
diff --git a/code/modules/mob/living/carbon/human/species_types/synths.dm b/code/modules/mob/living/carbon/human/species_types/synths.dm
index a1a2a33c54..3d55ce1027 100644
--- a/code/modules/mob/living/carbon/human/species_types/synths.dm
+++ b/code/modules/mob/living/carbon/human/species_types/synths.dm
@@ -61,7 +61,7 @@
mutant_organs = S.mutant_organs.Copy()
nojumpsuit = S.nojumpsuit
no_equip = S.no_equip.Copy()
- limbs_id = S.limbs_id
+ limbs_id = S.mutant_bodyparts["limbs_id"]
use_skintones = S.use_skintones
fixed_mut_color = S.fixed_mut_color
hair_color = S.hair_color
@@ -100,14 +100,12 @@
else
return ..()
-
/datum/species/synth/handle_body(mob/living/carbon/human/H)
if(fake_species)
fake_species.handle_body(H)
else
return ..()
-
/datum/species/synth/handle_mutant_bodyparts(mob/living/carbon/human/H, forced_colour)
if(fake_species)
fake_species.handle_body(H,forced_colour)
diff --git a/code/modules/mob/living/carbon/human/species_types/vampire.dm b/code/modules/mob/living/carbon/human/species_types/vampire.dm
index d8fc06b716..723c4848fe 100644
--- a/code/modules/mob/living/carbon/human/species_types/vampire.dm
+++ b/code/modules/mob/living/carbon/human/species_types/vampire.dm
@@ -2,7 +2,7 @@
name = "Vampire"
id = "vampire"
default_color = "FFFFFF"
- species_traits = list(EYECOLOR,HAIR,FACEHAIR,LIPS,DRINKSBLOOD)
+ species_traits = list(EYECOLOR,HAIR,FACEHAIR,LIPS,DRINKSBLOOD,HAS_FLESH,HAS_BONE)
inherent_traits = list(TRAIT_NOHUNGER,TRAIT_NOBREATH)
inherent_biotypes = MOB_UNDEAD|MOB_HUMANOID
mutant_bodyparts = list("mcolor" = "FFFFFF", "tail_human" = "None", "ears" = "None", "deco_wings" = "None")
diff --git a/code/modules/mob/living/carbon/human/species_types/xeno.dm b/code/modules/mob/living/carbon/human/species_types/xeno.dm
index db34d1ae45..ddd1c86f0a 100644
--- a/code/modules/mob/living/carbon/human/species_types/xeno.dm
+++ b/code/modules/mob/living/carbon/human/species_types/xeno.dm
@@ -4,7 +4,6 @@
id = "xeno"
say_mod = "hisses"
default_color = "00FF00"
- icon_limbs = DEFAULT_BODYPART_ICON_CITADEL
species_traits = list(MUTCOLORS,EYECOLOR,LIPS,CAN_SCAR)
mutant_bodyparts = list("xenotail"="Xenomorph Tail","xenohead"="Standard","xenodorsal"="Standard", "mam_body_markings" = "Xeno","mcolor" = "0F0","mcolor2" = "0F0","mcolor3" = "0F0","taur" = "None", "legs" = "Digitigrade")
attack_verb = "slash"
diff --git a/code/modules/mob/living/carbon/human/species_types/zombies.dm b/code/modules/mob/living/carbon/human/species_types/zombies.dm
index 990e2c9db3..8d86ad2fa9 100644
--- a/code/modules/mob/living/carbon/human/species_types/zombies.dm
+++ b/code/modules/mob/living/carbon/human/species_types/zombies.dm
@@ -8,7 +8,7 @@
sexes = 0
blacklisted = 1
meat = /obj/item/reagent_containers/food/snacks/meat/slab/human/mutant/zombie
- species_traits = list(NOBLOOD,NOZOMBIE,NOTRANSSTING,CAN_SCAR)
+ species_traits = list(NOBLOOD,NOZOMBIE,NOTRANSSTING,HAS_FLESH,HAS_BONE)
inherent_traits = list(TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_RADIMMUNE,TRAIT_EASYDISMEMBER,TRAIT_LIMBATTACHMENT,TRAIT_NOBREATH,TRAIT_NODEATH,TRAIT_FAKEDEATH)
inherent_biotypes = MOB_UNDEAD|MOB_HUMANOID
mutanttongue = /obj/item/organ/tongue/zombie
@@ -32,6 +32,7 @@
name = "Infectious Zombie"
id = "memezombies"
limbs_id = "zombie"
+ inherent_traits = list(TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_RADIMMUNE,TRAIT_EASYDISMEMBER,TRAIT_LIMBATTACHMENT,TRAIT_NOBREATH,TRAIT_NODEATH,TRAIT_NOSOFTCRIT, TRAIT_FAKEDEATH)
mutanthands = /obj/item/zombie_hand
armor = 20 // 120 damage to KO a zombie, which kills it
speedmod = 1.6 // they're very slow
@@ -45,7 +46,7 @@
/datum/species/zombie/infectious/spec_stun(mob/living/carbon/human/H,amount)
. = min(20, amount)
-/datum/species/zombie/infectious/apply_damage(damage, damagetype = BRUTE, def_zone = null, blocked, mob/living/carbon/human/H, forced = FALSE, wound_bonus = 0, bare_wound_bonus = 0, sharpness = FALSE)
+/datum/species/zombie/infectious/apply_damage(damage, damagetype = BRUTE, def_zone = null, blocked, mob/living/carbon/human/H, forced = FALSE, wound_bonus = 0, bare_wound_bonus = 0, sharpness = SHARP_NONE)
. = ..()
if(.)
regen_cooldown = world.time + REGENERATION_DELAY
@@ -63,9 +64,9 @@
C.heal_overall_damage(heal_amt,heal_amt)
C.adjustToxLoss(-heal_amt)
for(var/i in C.all_wounds)
- var/datum/wound/W = i
- if(prob(4-W.severity))
- W.remove_wound()
+ var/datum/wound/iter_wound = i
+ if(prob(4-iter_wound.severity))
+ iter_wound.remove_wound()
if(!C.InCritical() && prob(4))
playsound(C, pick(spooks), 50, TRUE, 10)
@@ -89,8 +90,8 @@
infection = new()
infection.Insert(C)
- //make their bodyparts stamina-resistant
- var/incoming_stam_mult = 0.7
+ //make their bodyparts stamina-immune, its a corpse.
+ var/incoming_stam_mult = 0
for(var/obj/item/bodypart/part in C.bodyparts)
part.incoming_stam_mult = incoming_stam_mult
//todo: add negative wound resistance to all parts when wounds is merged (zombies are physically weak in terms of limbs)
diff --git a/code/modules/mob/living/carbon/human/update_icons.dm b/code/modules/mob/living/carbon/human/update_icons.dm
index e399ddf872..9b39438563 100644
--- a/code/modules/mob/living/carbon/human/update_icons.dm
+++ b/code/modules/mob/living/carbon/human/update_icons.dm
@@ -660,7 +660,7 @@ use_mob_overlay_icon: if FALSE, it will always use the default_icon_file even if
//produces a key based on the human's limbs
/mob/living/carbon/human/generate_icon_render_key()
- . = "[dna.species.limbs_id]"
+ . = "[dna.species.mutant_bodyparts["limbs_id"]]"
if(dna.check_mutation(HULK))
. += "-coloured-hulk"
diff --git a/code/modules/mob/living/carbon/inventory.dm b/code/modules/mob/living/carbon/inventory.dm
index cc837a490c..50801e1c0d 100644
--- a/code/modules/mob/living/carbon/inventory.dm
+++ b/code/modules/mob/living/carbon/inventory.dm
@@ -117,18 +117,6 @@
if(!QDELETED(src))
update_inv_legcuffed()
-/mob/living/carbon/get_equipped_items(include_pockets = FALSE)
- var/list/items = list()
- if(back)
- items += back
- if(head)
- items += head
- if(wear_mask)
- items += wear_mask
- if(wear_neck)
- items += wear_neck
- return items
-
//handle stuff to update when a mob equips/unequips a mask.
/mob/living/proc/wear_mask_update(obj/item/clothing/C, toggle_off = 1)
update_inv_wear_mask()
diff --git a/code/modules/mob/living/carbon/life.dm b/code/modules/mob/living/carbon/life.dm
index 925172df49..6f5520a36b 100644
--- a/code/modules/mob/living/carbon/life.dm
+++ b/code/modules/mob/living/carbon/life.dm
@@ -1,8 +1,4 @@
/mob/living/carbon/BiologicalLife(seconds, times_fired)
- //Updates the number of stored chemicals for powers
- handle_changeling()
- //Handles the unique mentabolism of bloodsuckers, look at /datum/antagonist/bloodsucker/proc/LifeTick()
- handle_bloodsucker()
//Reagent processing needs to come before breathing, to prevent edge cases.
handle_organs()
. = ..() // if . is false, we are dead.
@@ -410,25 +406,7 @@
for(var/thing in all_wounds)
var/datum/wound/W = thing
if(W.processes) // meh
- W.handle_process()
-
-//todo generalize this and move hud out
-/mob/living/carbon/proc/handle_changeling()
- if(mind && hud_used && hud_used.lingchemdisplay)
- var/datum/antagonist/changeling/changeling = mind.has_antag_datum(/datum/antagonist/changeling)
- if(changeling)
- changeling.regenerate()
- hud_used.lingchemdisplay.invisibility = 0
- hud_used.lingchemdisplay.maptext = "
[round(changeling.chem_charges)]
"
- else
- hud_used.lingchemdisplay.invisibility = INVISIBILITY_ABSTRACT
-
-
-/mob/living/carbon/proc/handle_bloodsucker()
- if(mind && AmBloodsucker(src))
- var/datum/antagonist/bloodsucker/B = mind.has_antag_datum(ANTAG_DATUM_BLOODSUCKER)
- B.LifeTick()
-
+ W.handle_process()
/mob/living/carbon/handle_mutations_and_radiation()
if(dna && dna.temporary_mutations.len)
diff --git a/code/modules/mob/living/damage_procs.dm b/code/modules/mob/living/damage_procs.dm
index 012808647e..47c9062148 100644
--- a/code/modules/mob/living/damage_procs.dm
+++ b/code/modules/mob/living/damage_procs.dm
@@ -14,7 +14,7 @@
*
* Returns TRUE if damage applied
*/
-/mob/living/proc/apply_damage(damage = 0,damagetype = BRUTE, def_zone = null, blocked = FALSE, forced = FALSE, spread_damage = FALSE, wound_bonus = 0, bare_wound_bonus = 0, sharpness = FALSE)
+/mob/living/proc/apply_damage(damage = 0,damagetype = BRUTE, def_zone = null, blocked = FALSE, forced = FALSE, spread_damage = FALSE, wound_bonus = 0, bare_wound_bonus = 0, sharpness = SHARP_NONE)
var/hit_percent = (100-blocked)/100
if(!damage || (hit_percent <= 0))
return 0
@@ -245,7 +245,7 @@
update_stamina()
// damage ONE external organ, organ gets randomly selected from damaged ones.
-/mob/living/proc/take_bodypart_damage(brute = 0, burn = 0, stamina = 0, updating_health = TRUE, required_status, check_armor = FALSE, wound_bonus = 0, bare_wound_bonus = 0, sharpness = FALSE)
+/mob/living/proc/take_bodypart_damage(brute = 0, burn = 0, stamina = 0, updating_health = TRUE, required_status, check_armor = FALSE, wound_bonus = 0, bare_wound_bonus = 0, sharpness = SHARP_NONE)
adjustBruteLoss(brute, FALSE) //zero as argument for no instant health update
adjustFireLoss(burn, FALSE)
adjustStaminaLoss(stamina, FALSE)
diff --git a/code/modules/mob/living/death.dm b/code/modules/mob/living/death.dm
index ad1a3bc9b9..fe5a78de21 100644
--- a/code/modules/mob/living/death.dm
+++ b/code/modules/mob/living/death.dm
@@ -8,8 +8,6 @@
spill_organs(no_brain, no_organs, no_bodyparts)
- release_vore_contents(silent = TRUE) // return of the bomb safe internals.
-
if(!no_bodyparts)
spread_bodyparts(no_brain, no_organs)
@@ -46,7 +44,6 @@
buckled.unbuckle_mob(src, force = TRUE)
dust_animation()
- release_vore_contents(silent = TRUE) //technically grief protection, I guess? if they're SM'd it doesn't matter seconds after anyway.
spawn_dust(just_ash)
QDEL_IN(src,5) // since this is sometimes called in the middle of movement, allow half a second for movement to finish, ghosting to happen and animation to play. Looks much nicer and doesn't cause multiple runtimes.
@@ -103,5 +100,5 @@
for(var/s in sharedSoullinks)
var/datum/soullink/S = s
S.sharerDies(gibbed)
-
+ release_vore_contents(silent = TRUE)
return TRUE
diff --git a/code/modules/mob/living/emote.dm b/code/modules/mob/living/emote.dm
index a5de3a2fdc..55764d8d50 100644
--- a/code/modules/mob/living/emote.dm
+++ b/code/modules/mob/living/emote.dm
@@ -1,14 +1,13 @@
-
-/* EMOTE DATUMS */
-/datum/emote/living
- mob_type_allowed_typecache = /mob/living
- mob_type_blacklist_typecache = list(/mob/living/simple_animal/slime, /mob/living/brain)
-
/datum/emote/living/blush
key = "blush"
key_third_person = "blushes"
message = "blushes."
+/datum/emote/living/blush/run_emote(mob/user, params)
+ . = ..()
+ if(. && isipcperson(user))
+ do_fake_sparks(5,FALSE,user)
+
/datum/emote/living/bow
key = "bow"
key_third_person = "bows"
@@ -326,6 +325,11 @@
key_third_person = "smiles"
message = "smiles."
+/datum/emote/living/smirk
+ key = "smirk"
+ key_third_person = "smirks"
+ message = "smirks."
+
/datum/emote/living/sneeze
key = "sneeze"
key_third_person = "sneezes"
@@ -441,7 +445,7 @@
to_chat(user, "You cannot send IC messages (muted).")
return FALSE
else if(!params)
- var/custom_emote = stripped_multiline_input(user, "Choose an emote to display.", "Custom Emote", null, MAX_MESSAGE_LEN)
+ var/custom_emote = stripped_multiline_input_or_reflect(user, "Choose an emote to display.", "Custom Emote", null, MAX_MESSAGE_LEN)
if(custom_emote && !check_invalid(user, custom_emote))
var/type = input("Is this a visible or hearable emote?") as null|anything in list("Visible", "Hearable")
switch(type)
@@ -531,3 +535,29 @@
to_chat(user, "You ready your slapping hand.")
else
to_chat(user, "You're incapable of slapping in your current state.")
+
+/datum/emote/living/audio_emote/blorble
+ key = "blorble"
+ key_third_person = "blorbles"
+ message = "blorbles."
+ message_param = "blorbles at %t."
+
+/datum/emote/living/audio_emote/blorble/run_emote(mob/user, params)
+ . = ..()
+ if(. && iscarbon(user))
+ var/mob/living/carbon/C = user
+ if(isjellyperson(C))
+ pick(playsound(C, 'sound/effects/attackblob.ogg', 50, 1),playsound(C, 'sound/effects/blobattack.ogg', 50, 1))
+
+/datum/emote/living/audio_emote/blurp
+ key = "blurp"
+ key_third_person = "blurps"
+ message = "blurps."
+ message_param = "blurps at %t."
+
+/datum/emote/living/audio_emote/blurp/run_emote(mob/user, params)
+ . = ..()
+ if(. && iscarbon(user))
+ var/mob/living/carbon/C = user
+ if(isjellyperson(C))
+ pick(playsound(C, 'sound/effects/meatslap.ogg', 50, 1),playsound(C, 'sound/effects/gib_step.ogg', 50, 1))
diff --git a/code/modules/mob/living/life.dm b/code/modules/mob/living/life.dm
index 89321082c9..9de75702db 100644
--- a/code/modules/mob/living/life.dm
+++ b/code/modules/mob/living/life.dm
@@ -43,6 +43,7 @@
* Returns TRUE or FALSE based on if we were interrupted. This is used by overridden variants to check if they should stop.
*/
/mob/living/proc/BiologicalLife(seconds, times_fired)
+ SEND_SIGNAL(src,COMSIG_LIVING_BIOLOGICAL_LIFE, seconds, times_fired)
handle_diseases()// DEAD check is in the proc itself; we want it to spread even if the mob is dead, but to handle its disease-y properties only if you're not.
handle_wounds()
@@ -78,6 +79,7 @@
* Returns TRUE or FALSE based on if we were interrupted. This is used by overridden variants to check if they should stop.
*/
/mob/living/proc/PhysicalLife(seconds, times_fired)
+ SEND_SIGNAL(src,COMSIG_LIVING_PHYSICAL_LIFE, seconds, times_fired)
if(digitalinvis)
handle_diginvis() //AI becomes unable to see mob
diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm
index 4c9416fe46..096b6362fd 100644
--- a/code/modules/mob/living/living.dm
+++ b/code/modules/mob/living/living.dm
@@ -57,7 +57,7 @@
/mob/living/Bump(atom/A)
if(..()) //we are thrown onto something
return
- if (buckled || now_pushing)
+ if(buckled || now_pushing)
return
if(ismob(A))
var/mob/M = A
@@ -605,41 +605,55 @@
SEND_SIGNAL(item, COMSIG_ITEM_WEARERCROSSED, AM)
/mob/living/proc/makeTrail(turf/target_turf, turf/start, direction)
- if(!has_gravity())
+ if(!has_gravity() || !isturf(start) || !blood_volume)
return
- var/blood_exists = FALSE
+ var/blood_exists = locate(/obj/effect/decal/cleanable/trail_holder) in start
- for(var/obj/effect/decal/cleanable/trail_holder/C in start) //checks for blood splatter already on the floor
- blood_exists = TRUE
- if(isturf(start))
- var/trail_type = getTrail()
- if(trail_type)
- var/brute_ratio = round(getBruteLoss() / maxHealth, 0.1)
- if(blood_volume && blood_volume > max((BLOOD_VOLUME_NORMAL*blood_ratio)*(1 - brute_ratio * 0.25), 0))//don't leave trail if blood volume below a threshold
- blood_volume = max(blood_volume - max(1, brute_ratio * 2), 0) //that depends on our brute damage.
- var/newdir = get_dir(target_turf, start)
- if(newdir != direction)
- newdir = newdir | direction
- if(newdir == 3) //N + S
- newdir = NORTH
- else if(newdir == 12) //E + W
- newdir = EAST
- if((newdir in GLOB.cardinals) && (prob(50)))
- newdir = turn(get_dir(target_turf, start), 180)
- if(!blood_exists)
- new /obj/effect/decal/cleanable/trail_holder(start, get_static_viruses())
+ var/trail_type = getTrail()
+ if(!trail_type)
+ return
- for(var/obj/effect/decal/cleanable/trail_holder/TH in start)
- if((!(newdir in TH.existing_dirs) || trail_type == "trails_1" || trail_type == "trails_2") && TH.existing_dirs.len <= 16) //maximum amount of overlays is 16 (all light & heavy directions filled)
- TH.existing_dirs += newdir
- TH.add_overlay(image('icons/effects/blood.dmi', trail_type, dir = newdir))
- TH.transfer_mob_blood_dna(src)
+ var/brute_ratio = round(getBruteLoss() / maxHealth, 0.1)
+ if(blood_volume < max(BLOOD_VOLUME_NORMAL*(1 - brute_ratio * 0.25), 0))//don't leave trail if blood volume below a threshold
+ return
+
+ var/bleed_amount = bleedDragAmount()
+ blood_volume = max(blood_volume - bleed_amount, 0) //that depends on our brute damage.
+ var/newdir = get_dir(target_turf, start)
+ if(newdir != direction)
+ newdir = newdir | direction
+ if(newdir == (NORTH|SOUTH))
+ newdir = NORTH
+ else if(newdir == (EAST|WEST))
+ newdir = EAST
+ if((newdir in GLOB.cardinals) && (prob(50)))
+ newdir = turn(get_dir(target_turf, start), 180)
+ if(!blood_exists)
+ new /obj/effect/decal/cleanable/trail_holder(start, get_static_viruses())
+
+ for(var/obj/effect/decal/cleanable/trail_holder/TH in start)
+ if((!(newdir in TH.existing_dirs) || trail_type == "trails_1" || trail_type == "trails_2") && TH.existing_dirs.len <= 16) //maximum amount of overlays is 16 (all light & heavy directions filled)
+ TH.existing_dirs += newdir
+ TH.add_overlay(image('icons/effects/blood.dmi', trail_type, dir = newdir))
+ TH.transfer_mob_blood_dna(src)
/mob/living/carbon/human/makeTrail(turf/T)
if((NOBLOOD in dna.species.species_traits) || !is_bleeding() || bleedsuppress)
return
..()
+///Returns how much blood we're losing from being dragged a tile, from [mob/living/proc/makeTrail]
+/mob/living/proc/bleedDragAmount()
+ var/brute_ratio = round(getBruteLoss() / maxHealth, 0.1)
+ return max(1, brute_ratio * 2)
+
+/mob/living/carbon/bleedDragAmount()
+ var/bleed_amount = 0
+ for(var/i in all_wounds)
+ var/datum/wound/iter_wound = i
+ bleed_amount += iter_wound.drag_bleed_amount()
+ return bleed_amount
+
/mob/living/proc/getTrail()
if(getBruteLoss() < 300)
return pick("ltrails_1", "ltrails_2")
diff --git a/code/modules/mob/living/living_active_block.dm b/code/modules/mob/living/living_active_block.dm
index 25541c4a44..46b7c52075 100644
--- a/code/modules/mob/living/living_active_block.dm
+++ b/code/modules/mob/living/living_active_block.dm
@@ -13,7 +13,7 @@
DelayNextAction(data.block_end_click_cd_add)
return TRUE
-/mob/living/proc/ACTIVE_BLOCK_START(obj/item/I)
+/mob/living/proc/active_block_start(obj/item/I)
if(combat_flags & (COMBAT_FLAG_ACTIVE_BLOCK_STARTING | COMBAT_FLAG_ACTIVE_BLOCKING))
return FALSE
if(!(I in held_items))
@@ -109,7 +109,7 @@
animate(src, pixel_x = get_standard_pixel_x_offset(), pixel_y = get_standard_pixel_y_offset(), time = 2.5, FALSE, SINE_EASING | EASE_IN, ANIMATION_END_NOW)
return
combat_flags &= ~(COMBAT_FLAG_ACTIVE_BLOCK_STARTING)
- ACTIVE_BLOCK_START(I)
+ active_block_start(I)
/**
* Gets the first item we can that can block, but if that fails, default to active held item.COMSIG_ENABLE_COMBAT_MODE
@@ -180,6 +180,12 @@
/// Apply the stamina damage to our user, notice how damage argument is stamina_amount.
/obj/item/proc/active_block_do_stamina_damage(mob/living/owner, atom/object, stamina_amount, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return)
+ if(istype(object, /obj/item/projectile))
+ var/obj/item/projectile/P = object
+ if(P.stamina)
+ var/blocked = active_block_calculate_final_damage(owner, object, P.stamina, attack_text, attack_type, armour_penetration, attacker, def_zone, final_block_chance, block_return)
+ var/stam = active_block_stamina_cost(owner, object, blocked, attack_text, ATTACK_TYPE_PROJECTILE, armour_penetration, attacker, def_zone, final_block_chance, block_return)
+ stamina_amount += stam
var/datum/block_parry_data/data = get_block_parry_data()
if(iscarbon(owner))
var/mob/living/carbon/C = owner
diff --git a/code/modules/mob/living/living_active_parry.dm b/code/modules/mob/living/living_active_parry.dm
index e02bde2abc..aa3c97b4e3 100644
--- a/code/modules/mob/living/living_active_parry.dm
+++ b/code/modules/mob/living/living_active_parry.dm
@@ -246,7 +246,7 @@
. |= BLOCK_SUCCESS
var/list/effect_text
if(efficiency >= data.parry_efficiency_to_counterattack)
- run_parry_countereffects(object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, return_list, efficiency)
+ effect_text = run_parry_countereffects(object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, return_list, efficiency)
if(data.parry_flags & PARRY_DEFAULT_HANDLE_FEEDBACK)
handle_parry_feedback(object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, return_list, efficiency, effect_text)
successful_parries += efficiency
@@ -255,9 +255,12 @@
/mob/living/proc/handle_parry_feedback(atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, list/return_list = list(), parry_efficiency, list/effect_text)
var/datum/block_parry_data/data = get_parry_data()
+ var/knockdown_check = FALSE
+ if(data.parry_data[PARRY_KNOCKDOWN_ATTACKER] && parry_efficiency >= data.parry_efficiency_to_counterattack)
+ knockdown_check = TRUE
if(data.parry_sounds)
playsound(src, pick(data.parry_sounds), 75)
- visible_message("[src] parries [attack_text][length(effect_text)? ", [english_list(effect_text)] [attacker]" : ""]!")
+ visible_message("[src] parries [attack_text][length(effect_text)? ", [english_list(effect_text)] [attacker]" : ""][length(effect_text) && knockdown_check? " and" : ""][knockdown_check? " knocking them to the ground" : ""]!")
/// Run counterattack if any
/mob/living/proc/run_parry_countereffects(atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, list/return_list = list(), parry_efficiency)
@@ -289,15 +292,15 @@
if(data.parry_data[PARRY_DISARM_ATTACKER])
L.drop_all_held_items()
effect_text += "disarming"
- if(data.parry_data[PARRY_KNOCKDOWN_ATTACKER])
- L.DefaultCombatKnockdown(data.parry_data[PARRY_KNOCKDOWN_ATTACKER])
- effect_text += "knocking them to the ground"
if(data.parry_data[PARRY_STAGGER_ATTACKER])
L.Stagger(data.parry_data[PARRY_STAGGER_ATTACKER])
effect_text += "staggering"
if(data.parry_data[PARRY_DAZE_ATTACKER])
L.Daze(data.parry_data[PARRY_DAZE_ATTACKER])
effect_text += "dazing"
+ if(data.parry_data[PARRY_KNOCKDOWN_ATTACKER])
+ L.DefaultCombatKnockdown(data.parry_data[PARRY_KNOCKDOWN_ATTACKER])
+ // effect_text += "knocking them to the ground" - snowflaked above
return effect_text
/// Gets the datum/block_parry_data we're going to use to parry.
diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm
index 5be9dbf195..19adb7b787 100644
--- a/code/modules/mob/living/living_defense.dm
+++ b/code/modules/mob/living/living_defense.dm
@@ -86,7 +86,7 @@
totaldamage = block_calculate_resultant_damage(totaldamage, returnlist)
var/armor = run_armor_check(def_zone, P.flag, null, null, P.armour_penetration, null)
if(!P.nodamage)
- apply_damage(totaldamage, P.damage_type, def_zone, armor, wound_bonus=P.wound_bonus, bare_wound_bonus=P.bare_wound_bonus, sharpness=P.sharpness)
+ apply_damage(totaldamage, P.damage_type, def_zone, armor, wound_bonus = P.wound_bonus, bare_wound_bonus = P.bare_wound_bonus, sharpness = P.sharpness)
if(P.dismemberment)
check_projectile_dismemberment(P, def_zone)
var/missing = 100 - final_percent
@@ -136,12 +136,13 @@
dtype = I.damtype
if(!blocked)
- visible_message("[src] has been hit by [I].", \
- "You have been hit by [I].")
- var/armor = run_armor_check(impacting_zone, "melee", "Your armor has protected your [parse_zone(impacting_zone)].", "Your armor has softened hit to your [parse_zone(impacting_zone)].",I.armour_penetration)
- apply_damage(total_damage, dtype, impacting_zone, armor, sharpness=I.sharpness)
- if(I.thrownby)
- log_combat(I.thrownby, src, "threw and hit", I)
+ if(!nosell_hit)
+ visible_message("[src] is hit by [I]!", \
+ "You're hit by [I]!")
+ if(!I.throwforce)
+ return
+ var/armor = run_armor_check(impacting_zone, "melee", "Your armor has protected your [parse_zone(impacting_zone)].", "Your armor has softened hit to your [parse_zone(impacting_zone)].",I.armour_penetration)
+ apply_damage(I.throwforce, dtype, impacting_zone, armor, sharpness=I.get_sharpness(), wound_bonus=(nosell_hit * CANT_WOUND))
else
return 1
else
diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm
index aab741edd6..3f11d88668 100644
--- a/code/modules/mob/living/silicon/ai/ai.dm
+++ b/code/modules/mob/living/silicon/ai/ai.dm
@@ -23,6 +23,7 @@
a_intent = INTENT_HARM //so we always get pushed instead of trying to swap
sight = SEE_TURFS | SEE_MOBS | SEE_OBJS
see_in_dark = 8
+ deathsound = 'sound/voice/scream/android_scream.ogg'
hud_type = /datum/hud/ai
med_hud = DATA_HUD_MEDICAL_BASIC
sec_hud = DATA_HUD_SECURITY_BASIC
diff --git a/code/modules/mob/living/silicon/ai/life.dm b/code/modules/mob/living/silicon/ai/life.dm
index 4f2332e99d..2cbd1f35ca 100644
--- a/code/modules/mob/living/silicon/ai/life.dm
+++ b/code/modules/mob/living/silicon/ai/life.dm
@@ -151,7 +151,7 @@
to_chat(src, "Receiving control information from APC.")
sleep(2)
apc_override = 1
- theAPC.ui_interact(src, state = GLOB.conscious_state)
+ theAPC.ui_interact(src)
apc_override = 0
aiRestorePowerRoutine = POWER_RESTORATION_APC_FOUND
sleep(50)
diff --git a/code/modules/mob/living/silicon/ai/robot_control.dm b/code/modules/mob/living/silicon/ai/robot_control.dm
index 0eaea103f2..bbfb7604ba 100644
--- a/code/modules/mob/living/silicon/ai/robot_control.dm
+++ b/code/modules/mob/living/silicon/ai/robot_control.dm
@@ -19,11 +19,13 @@
return ..()
return UI_CLOSE
-/datum/robot_control/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \
- datum/tgui/master_ui = null, datum/ui_state/state = GLOB.always_state)
- ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+/datum/robot_control/ui_state(mob/user)
+ return GLOB.always_state
+
+/datum/robot_control/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
- ui = new(user, src, ui_key, "RemoteRobotControl", "Remote Robot Control", 500, 500, master_ui, state)
+ ui = new(user, src, "RemoteRobotControl")
ui.open()
/datum/robot_control/ui_data(mob/user)
diff --git a/code/modules/mob/living/silicon/damage_procs.dm b/code/modules/mob/living/silicon/damage_procs.dm
index b89f249c80..7530630d74 100644
--- a/code/modules/mob/living/silicon/damage_procs.dm
+++ b/code/modules/mob/living/silicon/damage_procs.dm
@@ -1,5 +1,5 @@
-/mob/living/silicon/apply_damage(damage = 0,damagetype = BRUTE, def_zone = null, blocked = FALSE, forced = FALSE, wound_bonus = 0, bare_wound_bonus = 0, sharpness = FALSE)
+/mob/living/silicon/apply_damage(damage = 0,damagetype = BRUTE, def_zone = null, blocked = FALSE, forced = FALSE, wound_bonus = 0, bare_wound_bonus = 0, sharpness = SHARP_NONE)
var/hit_percent = (100-blocked)/100
if(!damage || (!forced && hit_percent <= 0))
return 0
diff --git a/code/modules/mob/living/silicon/robot/emote.dm b/code/modules/mob/living/silicon/robot/emote.dm
index 65e1047cdc..c532e44ff5 100644
--- a/code/modules/mob/living/silicon/robot/emote.dm
+++ b/code/modules/mob/living/silicon/robot/emote.dm
@@ -3,8 +3,14 @@
emote_type = EMOTE_AUDIBLE
/datum/emote/sound/silicon
- mob_type_allowed_typecache = list(/mob/living/silicon)
+ mob_type_allowed_typecache = list(/mob/living/silicon, /mob/living/carbon/human)
emote_type = EMOTE_AUDIBLE
+ var/unrestricted = TRUE
+
+/datum/emote/sound/silicon/run_emote(mob/user, params)
+ if(!unrestricted && !(issilicon(user) || isipcperson(user)))
+ return
+ return ..()
/datum/emote/silicon/boop
key = "boop"
diff --git a/code/modules/mob/living/silicon/robot/inventory.dm b/code/modules/mob/living/silicon/robot/inventory.dm
index a063d090db..9b964ef188 100644
--- a/code/modules/mob/living/silicon/robot/inventory.dm
+++ b/code/modules/mob/living/silicon/robot/inventory.dm
@@ -79,15 +79,15 @@
if(activated(O))
to_chat(src, "That module is already activated.")
return
- if(!held_items[1])
+ if(!held_items[1] && health >= -maxHealth*0.5)
held_items[1] = O
O.screen_loc = inv1.screen_loc
. = TRUE
- else if(!held_items[2])
+ else if(!held_items[2] && health >= 0)
held_items[2] = O
O.screen_loc = inv2.screen_loc
. = TRUE
- else if(!held_items[3])
+ else if(!held_items[3] && health >= maxHealth*0.5)
held_items[3] = O
O.screen_loc = inv3.screen_loc
. = TRUE
diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm
index 0565e0300c..46471d3df6 100644
--- a/code/modules/mob/living/silicon/robot/robot.dm
+++ b/code/modules/mob/living/silicon/robot/robot.dm
@@ -16,6 +16,8 @@
wires = new /datum/wires/robot(src)
AddElement(/datum/element/empprotection, EMP_PROTECT_WIRES)
+ RegisterSignal(src, COMSIG_PROCESS_BORGCHARGER_OCCUPANT, .proc/charge)
+
robot_modules_background = new()
robot_modules_background.icon_state = "block"
robot_modules_background.layer = HUD_LAYER //Objects that appear on screen are on layer ABOVE_HUD_LAYER, UI should be just below it.
@@ -287,50 +289,57 @@
return FALSE
return ISINRANGE(T1.x, T0.x - interaction_range, T0.x + interaction_range) && ISINRANGE(T1.y, T0.y - interaction_range, T0.y + interaction_range)
-/mob/living/silicon/robot/attackby(obj/item/W, mob/user, params)
- if(istype(W, /obj/item/weldingtool) && (user.a_intent != INTENT_HARM || user == src))
+/mob/living/silicon/robot/proc/attempt_welder_repair(obj/item/weldingtool/W, mob/user)
+ if (!getBruteLoss())
+ to_chat(user, "[src] is already in good condition!")
+ return
+ if (!W.tool_start_check(user, amount=0)) //The welder has 1u of fuel consumed by it's afterattack, so we don't need to worry about taking any away.
+ return
+ user.DelayNextAction(CLICK_CD_MELEE)
+ if(src == user)
+ to_chat(user, "You start fixing yourself...")
+ if(!W.use_tool(src, user, 50))
+ return
+ adjustBruteLoss(-10)
+ else
+ to_chat(user, "You start fixing [src]...")
+ if(!do_after(user, 30, target = src))
+ return
+ adjustBruteLoss(-30)
+ updatehealth()
+ add_fingerprint(user)
+ visible_message("[user] has fixed some of the dents on [src].")
+
+/mob/living/silicon/robot/proc/attempt_cable_repair(obj/item/stack/cable_coil/W, mob/user)
+ if (getFireLoss() > 0 || getToxLoss() > 0)
user.DelayNextAction(CLICK_CD_MELEE)
- if (!getBruteLoss())
- to_chat(user, "[src] is already in good condition!")
- return
- if (!W.tool_start_check(user, amount=0)) //The welder has 1u of fuel consumed by it's afterattack, so we don't need to worry about taking any away.
- return
if(src == user)
to_chat(user, "You start fixing yourself...")
- if(!W.use_tool(src, user, 50))
+ if(!W.use_tool(src, user, 50, 1, skill_gain_mult = TRIVIAL_USE_TOOL_MULT))
+ to_chat(user, "You need more cable to repair [src]!")
return
- adjustBruteLoss(-10)
+ adjustFireLoss(-10)
+ adjustToxLoss(-10)
else
to_chat(user, "You start fixing [src]...")
- if(!do_after(user, 30, target = src))
+ if(!W.use_tool(src, user, 30, 1))
+ to_chat(user, "You need more cable to repair [src]!")
return
- adjustBruteLoss(-30)
- updatehealth()
- add_fingerprint(user)
- visible_message("[user] has fixed some of the dents on [src].")
+ adjustFireLoss(-30)
+ adjustToxLoss(-30)
+ updatehealth()
+ user.visible_message("[user] has fixed some of the burnt wires on [src].", "You fix some of the burnt wires on [src].")
+ else
+ to_chat(user, "The wires seem fine, there's no need to fix them.")
+
+/mob/living/silicon/robot/attackby(obj/item/W, mob/user, params)
+ if(istype(W, /obj/item/weldingtool) && (user.a_intent != INTENT_HARM || user == src))
+ INVOKE_ASYNC(src, .proc/attempt_welder_repair, W, user)
return
else if(istype(W, /obj/item/stack/cable_coil) && wiresexposed)
- user.DelayNextAction(CLICK_CD_MELEE)
- if (getFireLoss() > 0 || getToxLoss() > 0)
- if(src == user)
- to_chat(user, "You start fixing yourself...")
- if(!W.use_tool(src, user, 50, 1, skill_gain_mult = TRIVIAL_USE_TOOL_MULT))
- to_chat(user, "You need more cable to repair [src]!")
- return
- adjustFireLoss(-10)
- adjustToxLoss(-10)
- else
- to_chat(user, "You start fixing [src]...")
- if(!W.use_tool(src, user, 30, 1))
- to_chat(user, "You need more cable to repair [src]!")
- return
- adjustFireLoss(-30)
- adjustToxLoss(-30)
- updatehealth()
- user.visible_message("[user] has fixed some of the burnt wires on [src].", "You fix some of the burnt wires on [src].")
- else
- to_chat(user, "The wires seem fine, there's no need to fix them.")
+ INVOKE_ASYNC(src, .proc/attempt_cable_repair, W, user)
+ return
else if(istype(W, /obj/item/crowbar)) // crowbar means open or close the cover
if(opened)
@@ -1097,6 +1106,15 @@
for(var/i in connected_ai.aicamera.stored)
aicamera.stored[i] = TRUE
+/mob/living/silicon/robot/proc/charge(datum/source, amount, repairs)
+ if(module)
+ var/coeff = amount * 0.005
+ module.respawn_consumable(src, coeff)
+ if(repairs)
+ heal_bodypart_damage(repairs, repairs - 1)
+ if(cell)
+ cell.charge = min(cell.charge + amount, cell.maxcharge)
+
/mob/living/silicon/robot/proc/rest_style()
set name = "Switch Rest Style"
set category = "Robot Commands"
diff --git a/code/modules/mob/living/silicon/silicon.dm b/code/modules/mob/living/silicon/silicon.dm
index 7a9610fb53..944b909463 100644
--- a/code/modules/mob/living/silicon/silicon.dm
+++ b/code/modules/mob/living/silicon/silicon.dm
@@ -13,6 +13,7 @@
mob_biotypes = MOB_ROBOTIC
rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE
speech_span = SPAN_ROBOT
+ deathsound = 'sound/voice/borg_deathsound.ogg'
flags_1 = PREVENT_CONTENTS_EXPLOSION_1 | HEAR_1
vore_flags = NO_VORE
@@ -430,3 +431,6 @@
/mob/living/silicon/handle_high_gravity(gravity)
return
+
+/mob/living/silicon/rust_heretic_act()
+ adjustBruteLoss(500)
diff --git a/code/modules/mob/living/simple_animal/astral.dm b/code/modules/mob/living/simple_animal/astral.dm
index f79a2b5b3e..ec2180ccb3 100644
--- a/code/modules/mob/living/simple_animal/astral.dm
+++ b/code/modules/mob/living/simple_animal/astral.dm
@@ -24,6 +24,7 @@
incorporeal_move = 1
alpha = 50
speak_emote = list("echos")
+ rad_flags = RAD_NO_CONTAMINATE
movement_type = FLYING
var/pseudo_death = FALSE
var/posses_safe = FALSE
diff --git a/code/modules/mob/living/simple_animal/bot/bot.dm b/code/modules/mob/living/simple_animal/bot/bot.dm
index fe6792b5c2..a1772d9281 100644
--- a/code/modules/mob/living/simple_animal/bot/bot.dm
+++ b/code/modules/mob/living/simple_animal/bot/bot.dm
@@ -1058,3 +1058,6 @@ Pass a positive integer as an argument to override a bot's default speed.
if(I)
I.icon_state = null
path.Cut(1, 2)
+
+/mob/living/silicon/rust_heretic_act()
+ adjustBruteLoss(500)
diff --git a/code/modules/mob/living/simple_animal/bot/cleanbot.dm b/code/modules/mob/living/simple_animal/bot/cleanbot.dm
index 5c83482bde..e7c5644e26 100644
--- a/code/modules/mob/living/simple_animal/bot/cleanbot.dm
+++ b/code/modules/mob/living/simple_animal/bot/cleanbot.dm
@@ -313,7 +313,7 @@
/obj/effect/decal/cleanable/dirt,
/obj/effect/decal/cleanable/insectguts,
/obj/effect/decal/cleanable/semen,
- /obj/effect/decal/cleanable/femcum,
+ /obj/effect/decal/cleanable/semen/femcum,
/obj/effect/decal/cleanable/generic,
/obj/effect/decal/cleanable/glass,,
/obj/effect/decal/cleanable/cobweb,
diff --git a/code/modules/mob/living/simple_animal/bot/mulebot.dm b/code/modules/mob/living/simple_animal/bot/mulebot.dm
index 5e96818766..d9bf8cc553 100644
--- a/code/modules/mob/living/simple_animal/bot/mulebot.dm
+++ b/code/modules/mob/living/simple_animal/bot/mulebot.dm
@@ -29,9 +29,6 @@
model = "MULE"
bot_core_type = /obj/machinery/bot_core/mulebot
- var/ui_x = 350
- var/ui_y = 425
-
var/id
path_image_color = "#7F5200"
@@ -170,11 +167,10 @@
return
ui_interact(user)
-/mob/living/simple_animal/bot/mulebot/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \
- datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state)
- ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+/mob/living/simple_animal/bot/mulebot/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
- ui = new(user, src, ui_key, "Mule", name, ui_x, ui_y, master_ui, state)
+ ui = new(user, src, "Mule", name)
ui.open()
/mob/living/simple_animal/bot/mulebot/ui_data(mob/user)
@@ -191,8 +187,7 @@
data["modeStatus"] = "average"
if(BOT_NO_ROUTE)
data["modeStatus"] = "bad"
- else
- data["load"] = load ? load.name : null
+ data["load"] = load ? load.name : null //IF YOU CHANGE THE NAME OF THIS, UPDATE MULEBOT/PARANORMAL/UI_DATA.
data["destination"] = destination ? destination : null
data["home"] = home_destination
data["destinations"] = GLOB.deliverybeacontags
@@ -206,18 +201,20 @@
return data
/mob/living/simple_animal/bot/mulebot/ui_act(action, params)
- var/silicon_access = hasSiliconAccessInArea(usr)
- if(..() || (locked && silicon_access))
+ if(..() || (locked && hasSiliconAccessInArea(usr)))
return
switch(action)
if("lock")
- if(silicon_access)
+ if(hasSiliconAccessInArea(usr))
locked = !locked
. = TRUE
if("power")
if(on)
turn_off()
- else if(cell && !open)
+ else if(open)
+ to_chat(usr, "[name]'s maintenance panel is open!")
+ return
+ else if(cell)
if(!turn_on())
to_chat(usr, "You can't switch on [src]!")
return
diff --git a/code/modules/mob/living/simple_animal/constructs.dm b/code/modules/mob/living/simple_animal/constructs.dm
index 354bc9ed0f..95bd8d8991 100644
--- a/code/modules/mob/living/simple_animal/constructs.dm
+++ b/code/modules/mob/living/simple_animal/constructs.dm
@@ -11,7 +11,6 @@
response_disarm_simple = "flail at"
response_harm_continuous = "punches"
response_harm_simple = "punch"
- threat = 1
speak_chance = 1
icon = 'icons/mob/mob.dmi'
speed = 0
@@ -122,7 +121,6 @@
desc = "A massive, armored construct built to spearhead attacks and soak up enemy fire."
icon_state = "behemoth"
icon_living = "behemoth"
- threat = 3
maxHealth = 150
health = 150
response_harm_continuous = "harmlessly punches"
@@ -187,7 +185,6 @@
desc = "A wicked, clawed shell constructed to assassinate enemies and sow chaos behind enemy lines."
icon_state = "floating"
icon_living = "floating"
- threat = 3
maxHealth = 65
health = 65
melee_damage_lower = 20
diff --git a/code/modules/mob/living/simple_animal/eldritch_demons.dm b/code/modules/mob/living/simple_animal/eldritch_demons.dm
new file mode 100644
index 0000000000..dbf62be16d
--- /dev/null
+++ b/code/modules/mob/living/simple_animal/eldritch_demons.dm
@@ -0,0 +1,386 @@
+/mob/living/simple_animal/hostile/eldritch
+ name = "Demon"
+ real_name = "Demon"
+ desc = ""
+ gender = NEUTER
+ mob_biotypes = NONE
+ speak_emote = list("screams")
+ response_help_continuous = "thinks better of touching"
+ response_help_simple = "think better of touching"
+ response_disarm_continuous = "flails at"
+ response_disarm_simple = "flail at"
+ response_harm_continuous = "reaps"
+ response_harm_simple = "tears"
+ speak_chance = 1
+ icon = 'icons/mob/eldritch_mobs.dmi'
+ speed = 0
+ a_intent = INTENT_HARM
+ stop_automated_movement = 1
+ AIStatus = AI_OFF
+ attack_sound = 'sound/weapons/punch1.ogg'
+ see_in_dark = 7
+ lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
+ damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 0, CLONE = 0, STAMINA = 0, OXY = 0)
+ atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0)
+ minbodytemp = 0
+ maxbodytemp = INFINITY
+ healable = 0
+ movement_type = GROUND
+ pressure_resistance = 100
+ del_on_death = TRUE
+ deathmessage = "implodes into itself"
+ faction = list("heretics")
+ simple_mob_flags = SILENCE_RANGED_MESSAGE
+ ///Innate spells that are supposed to be added when a beast is created
+ var/list/spells_to_add
+
+/mob/living/simple_animal/hostile/eldritch/Initialize()
+ . = ..()
+ add_spells()
+
+/**
+ * Add_spells
+ *
+ * Goes through spells_to_add and adds each spell to the mind.
+ */
+/mob/living/simple_animal/hostile/eldritch/proc/add_spells()
+ for(var/spell in spells_to_add)
+ AddSpell(new spell())
+
+/mob/living/simple_animal/hostile/eldritch/raw_prophet
+ name = "Raw Prophet"
+ real_name = "Raw Prophet"
+ desc = "Abomination made from severed limbs."
+ icon_state = "raw_prophet"
+ status_flags = CANPUSH
+ icon_living = "raw_prophet"
+ melee_damage_lower = 5
+ melee_damage_upper = 10
+ maxHealth = 50
+ health = 50
+ sight = SEE_MOBS|SEE_OBJS|SEE_TURFS
+ spells_to_add = list(/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/shift/ash/long,/obj/effect/proc_holder/spell/pointed/manse_link,/obj/effect/proc_holder/spell/targeted/telepathy/eldritch,/obj/effect/proc_holder/spell/pointed/trigger/blind/eldritch)
+
+ var/list/linked_mobs = list()
+
+/mob/living/simple_animal/hostile/eldritch/raw_prophet/Initialize()
+ . = ..()
+ link_mob(src)
+
+/mob/living/simple_animal/hostile/eldritch/raw_prophet/Login()
+ . = ..()
+ client.change_view(10)
+
+/mob/living/simple_animal/hostile/eldritch/raw_prophet/proc/link_mob(mob/living/mob_linked)
+ if(QDELETED(mob_linked) || mob_linked.stat == DEAD)
+ return FALSE
+ if(HAS_TRAIT(mob_linked, TRAIT_MINDSHIELD)) //mindshield implant, no dice
+ return FALSE
+ if(mob_linked.anti_magic_check(FALSE, FALSE, TRUE, 0))
+ return FALSE
+ if(linked_mobs[mob_linked])
+ return FALSE
+
+ to_chat(mob_linked, "You feel something new enter your sphere of mind, you hear whispers of people far away, screeches of horror and a humming of welcome to [src]'s Mansus Link.")
+ var/datum/action/innate/mansus_speech/action = new(src)
+ linked_mobs[mob_linked] = action
+ action.Grant(mob_linked)
+ RegisterSignal(mob_linked, list(COMSIG_MOB_DEATH, COMSIG_PARENT_QDELETING) , .proc/unlink_mob)
+ return TRUE
+
+/mob/living/simple_animal/hostile/eldritch/raw_prophet/proc/unlink_mob(mob/living/mob_linked)
+ if(!linked_mobs[mob_linked])
+ return
+ UnregisterSignal(mob_linked, list(COMSIG_MOB_DEATH, COMSIG_PARENT_QDELETING))
+ var/datum/action/innate/mansus_speech/action = linked_mobs[mob_linked]
+ action.Remove(mob_linked)
+ qdel(action)
+ to_chat(mob_linked, "Your mind shatters as the [src]'s Mansus Link leaves your mind.")
+ mob_linked.emote("Scream")
+ //micro stun
+ mob_linked.AdjustParalyzed(0.5 SECONDS)
+ linked_mobs -= mob_linked
+
+/mob/living/simple_animal/hostile/eldritch/raw_prophet/death(gibbed)
+ for(var/linked_mob in linked_mobs)
+ unlink_mob(linked_mob)
+ return ..()
+
+/mob/living/simple_animal/hostile/eldritch/armsy
+ name = "Terror of the Night"
+ real_name = "Armsy"
+ desc = "Abomination made from severed limbs."
+ icon_state = "armsy_start"
+ icon_living = "armsy_start"
+ maxHealth = 200
+ health = 200
+ obj_damage = 80
+ melee_damage_lower = 10
+ melee_damage_upper = 15
+ move_resist = MOVE_FORCE_OVERPOWERING+1
+ movement_type = GROUND
+ environment_smash = ENVIRONMENT_SMASH_RWALLS
+ sight = SEE_MOBS
+ spells_to_add = list(/obj/effect/proc_holder/spell/targeted/worm_contract)
+ ranged = TRUE
+ ///Previous segment in the chain
+ var/mob/living/simple_animal/hostile/eldritch/armsy/back
+ ///Next segment in the chain
+ var/mob/living/simple_animal/hostile/eldritch/armsy/front
+ ///Your old location
+ var/oldloc
+ ///Allow / disallow pulling
+ var/allow_pulling = FALSE
+ ///How many arms do we have to eat to expand?
+ var/stacks_to_grow = 2
+ ///Currently eaten arms
+ var/current_stacks = 0
+
+//I tried Initalize but it didnt work, like at all. This proc just wouldnt fire if it was Initalize instead of New
+/mob/living/simple_animal/hostile/eldritch/armsy/Initialize(mapload,spawn_more = TRUE,len = 6)
+ . = ..()
+ if(len < 3)
+ stack_trace("Eldritch Armsy created with invalid len ([len]). Reverting to 3.")
+ len = 3 //code breaks below 3, let's just not allow it.
+ oldloc = loc
+ RegisterSignal(src,COMSIG_MOVABLE_MOVED,.proc/update_chain_links)
+ if(!spawn_more)
+ return
+ allow_pulling = TRUE
+ ///next link
+ var/mob/living/simple_animal/hostile/eldritch/armsy/next
+ ///previous link
+ var/mob/living/simple_animal/hostile/eldritch/armsy/prev
+ ///current link
+ var/mob/living/simple_animal/hostile/eldritch/armsy/current
+ for(var/i in 0 to len)
+ prev = current
+ //i tried using switch, but byond is really fucky and it didnt work as intended. Im sorry
+ if(i == 0)
+ current = new type(drop_location(),FALSE)
+ current.icon_state = "armsy_mid"
+ current.icon_living = "armsy_mid"
+ current.front = src
+ current.AIStatus = AI_OFF
+ back = current
+ else if(i < len)
+ current = new type(drop_location(),FALSE)
+ prev.back = current
+ prev.icon_state = "armsy_mid"
+ prev.icon_living = "armsy_mid"
+ prev.front = next
+ prev.AIStatus = AI_OFF
+ else
+ prev.icon_state = "armsy_end"
+ prev.icon_living = "armsy_end"
+ prev.front = next
+ prev.AIStatus = AI_OFF
+ next = prev
+
+//we are literally a vessel of otherworldly destruction, we bring our own gravity unto this plane
+/mob/living/simple_animal/hostile/eldritch/armsy/has_gravity(turf/T)
+ return TRUE
+
+
+/mob/living/simple_animal/hostile/eldritch/armsy/can_be_pulled()
+ return FALSE
+
+///Updates chain links to force move onto a single tile
+/mob/living/simple_animal/hostile/eldritch/armsy/proc/contract_next_chain_into_single_tile()
+ if(back)
+ back.forceMove(loc)
+ back.contract_next_chain_into_single_tile()
+ return
+
+///Updates the next mob in the chain to move to our last location, fixed the worm if somehow broken.
+/mob/living/simple_animal/hostile/eldritch/armsy/proc/update_chain_links()
+ gib_trail()
+ if(back && back.loc != oldloc)
+ back.Move(oldloc)
+ // self fixing properties if somehow broken
+ if(front && loc != front.oldloc)
+ forceMove(front.oldloc)
+ oldloc = loc
+
+/mob/living/simple_animal/hostile/eldritch/armsy/proc/gib_trail()
+ if(front) // head makes gibs
+ return
+ var/chosen_decal = pick(typesof(/obj/effect/decal/cleanable/blood/tracks))
+ var/obj/effect/decal/cleanable/blood/gibs/decal = new chosen_decal(drop_location())
+ decal.setDir(dir)
+
+/mob/living/simple_animal/hostile/eldritch/armsy/Destroy()
+ if(front)
+ front.icon_state = "armsy_end"
+ front.icon_living = "armsy_end"
+ front.back = null
+ if(back)
+ QDEL_NULL(back) // chain destruction baby
+ return ..()
+
+/mob/living/simple_animal/hostile/eldritch/armsy/BiologicalLife(seconds, times_fired)
+ adjustBruteLoss(-2)
+
+/mob/living/simple_animal/hostile/eldritch/armsy/proc/heal()
+ if(health == maxHealth)
+ if(back)
+ back.heal()
+ return
+ else
+ current_stacks++
+ if(current_stacks >= stacks_to_grow)
+ var/mob/living/simple_animal/hostile/eldritch/armsy/prev = new type(drop_location(),spawn_more = FALSE)
+ icon_state = "armsy_mid"
+ icon_living = "armsy_mid"
+ back = prev
+ prev.icon_state = "armsy_end"
+ prev.icon_living = "armsy_end"
+ prev.front = src
+ prev.AIStatus = AI_OFF
+ current_stacks = 0
+
+ adjustBruteLoss(-maxHealth * 0.5, FALSE)
+ adjustFireLoss(-maxHealth * 0.5 ,FALSE)
+
+
+/mob/living/simple_animal/hostile/eldritch/armsy/Shoot(atom/targeted_atom)
+ target = targeted_atom
+ AttackingTarget()
+
+
+/mob/living/simple_animal/hostile/eldritch/armsy/AttackingTarget()
+ if(istype(target,/obj/item/bodypart/r_arm) || istype(target,/obj/item/bodypart/l_arm))
+ qdel(target)
+ heal()
+ return
+ if(target == back || target == front)
+ return
+ if(back)
+ back.target = target
+ back.AttackingTarget()
+ if(!Adjacent(target))
+ return
+ do_attack_animation(target)
+ //have fun
+ //if(istype(target,/turf/closed/wall))
+ //var/turf/closed/wall = target
+ //wall.ScrapeAway()
+
+
+ if(iscarbon(target))
+ var/mob/living/carbon/C = target
+ if(HAS_TRAIT(C, TRAIT_NODISMEMBER))
+ return
+ var/list/parts = list()
+ for(var/X in C.bodyparts)
+ var/obj/item/bodypart/bodypart = X
+ if(bodypart.body_part != HEAD && bodypart.body_part != CHEST)
+ if(bodypart.dismemberable)
+ parts += bodypart
+ if(length(parts) && prob(10))
+ var/obj/item/bodypart/bodypart = pick(parts)
+ bodypart.dismember()
+
+ return ..()
+
+/mob/living/simple_animal/hostile/eldritch/armsy/prime
+ name = "Lord of the Night"
+ real_name = "Master of Decay"
+ maxHealth = 400
+ health = 400
+ melee_damage_lower = 20
+ melee_damage_upper = 25
+
+/mob/living/simple_animal/hostile/eldritch/armsy/prime/Initialize(mapload,spawn_more = TRUE,len = 9)
+ . = ..()
+ var/matrix/matrix_transformation = matrix()
+ matrix_transformation.Scale(1.4,1.4)
+ transform = matrix_transformation
+
+/mob/living/simple_animal/hostile/eldritch/armsy/primeproc/heal()
+ if(health == maxHealth)
+ if(back)
+ back.heal()
+ return
+ else
+ current_stacks++
+ if(current_stacks >= stacks_to_grow)
+ var/mob/living/simple_animal/hostile/eldritch/armsy/prev = new type(drop_location(),spawn_more = FALSE)
+ icon_state = "armsy_mid"
+ icon_living = "armsy_mid"
+ back = prev
+ prev.icon_state = "armsy_end"
+ prev.icon_living = "armsy_end"
+ prev.front = src
+ prev.AIStatus = AI_OFF
+ current_stacks = 0
+ var/matrix/matrix_transformation = matrix()
+ matrix_transformation.Scale(1.4,1.4)
+ transform = matrix_transformation
+
+ adjustBruteLoss(-maxHealth * 0.5, FALSE)
+ adjustFireLoss(-maxHealth * 0.5 ,FALSE)
+
+
+/mob/living/simple_animal/hostile/eldritch/rust_spirit
+ name = "Rust Walker"
+ real_name = "Rusty"
+ desc = "Incomprehensible abomination actively seeping life out of it's surrounding."
+ icon_state = "rust_walker_s"
+ status_flags = CANPUSH
+ icon_living = "rust_walker_s"
+ maxHealth = 75
+ health = 75
+ melee_damage_lower = 15
+ melee_damage_upper = 20
+ sight = SEE_TURFS
+ spells_to_add = list(/obj/effect/proc_holder/spell/aoe_turf/rust_conversion/small,/obj/effect/proc_holder/spell/aimed/rust_wave/short)
+
+/mob/living/simple_animal/hostile/eldritch/rust_spirit/setDir(newdir, ismousemovement)
+ . = ..()
+ if(newdir == NORTH)
+ icon_state = "rust_walker_n"
+ else if(newdir == SOUTH)
+ icon_state = "rust_walker_s"
+ update_icon()
+
+/mob/living/simple_animal/hostile/eldritch/rust_spirit/Moved()
+ . = ..()
+ playsound(src, 'sound/effects/footstep/rustystep1.ogg', 100, TRUE)
+
+/mob/living/simple_animal/hostile/eldritch/rust_spirit/Life()
+ if(stat == DEAD)
+ return ..()
+ var/turf/T = get_turf(src)
+ if(istype(T,/turf/open/floor/plating/rust))
+ adjustBruteLoss(-3, FALSE)
+ adjustFireLoss(-3, FALSE)
+ return ..()
+
+/mob/living/simple_animal/hostile/eldritch/ash_spirit
+ name = "Ash Man"
+ real_name = "Ashy"
+ desc = "Incomprehensible abomination actively seeping life out of it's surrounding."
+ icon_state = "ash_walker"
+ status_flags = CANPUSH
+ icon_living = "ash_walker"
+ maxHealth = 75
+ health = 75
+ melee_damage_lower = 15
+ melee_damage_upper = 20
+ sight = SEE_TURFS
+ spells_to_add = list(/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/shift/ash,/obj/effect/proc_holder/spell/pointed/cleave/long,/obj/effect/proc_holder/spell/aoe_turf/fire_cascade)
+
+/mob/living/simple_animal/hostile/eldritch/stalker
+ name = "Flesh Stalker"
+ real_name = "Flesh Stalker"
+ desc = "Abomination made from severed limbs."
+ icon_state = "stalker"
+ status_flags = CANPUSH
+ icon_living = "stalker"
+ maxHealth = 150
+ health = 150
+ melee_damage_lower = 15
+ melee_damage_upper = 20
+ sight = SEE_MOBS
+ spells_to_add = list(/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/shift/ash,/obj/effect/proc_holder/spell/targeted/shapeshift/eldritch,/obj/effect/proc_holder/spell/targeted/emplosion/eldritch)
diff --git a/code/modules/mob/living/simple_animal/friendly/gondola.dm b/code/modules/mob/living/simple_animal/friendly/gondola.dm
index 0cfea3548b..e29cbb8062 100644
--- a/code/modules/mob/living/simple_animal/friendly/gondola.dm
+++ b/code/modules/mob/living/simple_animal/friendly/gondola.dm
@@ -58,7 +58,7 @@
eyes_overlay.pixel_y = -8
moustache_overlay.pixel_y = -8
- cut_overlays(TRUE)
+ cut_overlays()
add_overlay(body_overlay)
add_overlay(eyes_overlay)
add_overlay(moustache_overlay)
diff --git a/code/modules/mob/living/simple_animal/friendly/possum.dm b/code/modules/mob/living/simple_animal/friendly/possum.dm
new file mode 100644
index 0000000000..71fdbd1465
--- /dev/null
+++ b/code/modules/mob/living/simple_animal/friendly/possum.dm
@@ -0,0 +1,37 @@
+/mob/living/simple_animal/opossum
+ name = "opossum"
+ desc = "It's an opossum, a small scavenging marsupial."
+ icon_state = "possum"
+ icon_living = "possum"
+ icon_dead = "possum_dead"
+ speak = list("Hiss!","HISS!","Hissss?")
+ speak_emote = list("hisses")
+ emote_hear = list("hisses.")
+ emote_see = list("runs in a circle.", "shakes.")
+ speak_chance = 1
+ turns_per_move = 3
+ blood_volume = 250
+ see_in_dark = 5
+ maxHealth = 15
+ health = 15
+ butcher_results = list(/obj/item/reagent_containers/food/snacks/meat/slab = 1)
+ response_help_continuous = "pets"
+ response_help_simple = "pet"
+ response_disarm_continuous = "gently pushes aside"
+ response_disarm_simple = "gently push aside"
+ response_harm_continuous = "stamps on"
+ response_harm_simple = "stamp"
+ density = FALSE
+ ventcrawler = VENTCRAWLER_ALWAYS
+ pass_flags = PASSTABLE | PASSMOB
+ mob_size = MOB_SIZE_TINY
+ mob_biotypes = MOB_ORGANIC|MOB_BEAST
+ gold_core_spawnable = FRIENDLY_SPAWN
+
+/mob/living/simple_animal/opossum/poppy
+ name = "Poppy the Safety Possum"
+ desc = "Safety first!"
+ icon_state = "poppypossum"
+ icon_living = "poppypossum"
+ icon_dead = "poppypossum_dead"
+ butcher_results = list(/obj/item/clothing/head/hardhat = 1)
diff --git a/code/modules/mob/living/simple_animal/guardian/guardian.dm b/code/modules/mob/living/simple_animal/guardian/guardian.dm
index 73ebfa7c77..7abecc7c81 100644
--- a/code/modules/mob/living/simple_animal/guardian/guardian.dm
+++ b/code/modules/mob/living/simple_animal/guardian/guardian.dm
@@ -8,7 +8,6 @@ GLOBAL_LIST_EMPTY(parasites) //all currently existing/living guardians
name = "Guardian Spirit"
real_name = "Guardian Spirit"
desc = "A mysterious being that stands by its charge, ever vigilant."
- threat = 5
speak_emote = list("hisses")
gender = NEUTER
mob_biotypes = NONE
@@ -60,11 +59,13 @@ GLOBAL_LIST_EMPTY(parasites) //all currently existing/living guardians
var/magic_fluff_string = "You draw the Coder, symbolizing bugs and errors. This shouldn't happen! Submit a bug report!"
var/tech_fluff_string = "BOOT SEQUENCE COMPLETE. ERROR MODULE LOADED. THIS SHOULDN'T HAPPEN. Submit a bug report!"
var/carp_fluff_string = "CARP CARP CARP SOME SORT OF HORRIFIC BUG BLAME THE CODERS CARP CARP CARP"
+ /// sigh, fine.
+ var/datum/song/holoparasite/music_datum
/mob/living/simple_animal/hostile/guardian/Initialize(mapload, theme)
GLOB.parasites += src
updatetheme(theme)
-
+ music_datum = new(src, get_allowed_instrument_ids())
. = ..()
/mob/living/simple_animal/hostile/guardian/med_hud_set_health()
@@ -84,8 +85,16 @@ GLOBAL_LIST_EMPTY(parasites) //all currently existing/living guardians
/mob/living/simple_animal/hostile/guardian/Destroy()
GLOB.parasites -= src
+ QDEL_NULL(music_datum)
return ..()
+/mob/living/simple_animal/hostile/guardian/verb/music_interact()
+ set name = "Access Internal Synthesizer"
+ set desc = "Access your internal musical synthesizer"
+ set category = "IC"
+
+ music_datum.ui_interact(src)
+
/mob/living/simple_animal/hostile/guardian/proc/updatetheme(theme) //update the guardian's theme
if(!theme)
theme = pick("magic", "tech", "carp")
@@ -638,6 +647,12 @@ GLOBAL_LIST_EMPTY(parasites) //all currently existing/living guardians
/obj/item/guardiancreator/tech/choose/dextrous
possible_guardians = list("Assassin", "Chaos", "Charger", "Dextrous", "Explosive", "Lightning", "Protector", "Ranged", "Standard", "Support")
+/obj/item/guardiancreator/tech/choose/nukie // lacks support and protector as encouraging nukies to play turtle isnt fun and dextrous is epic
+ possible_guardians = list("Assassin", "Chaos", "Charger", "Dextrous", "Explosive", "Lightning", "Ranged", "Standard")
+
+/obj/item/guardiancreator/tech/choose/nukie/check_uplink_validity()
+ return !used
+
/obj/item/paper/guides/antag/guardian
name = "Holoparasite Guide"
icon_state = "paper_words"
@@ -677,7 +692,7 @@ GLOBAL_LIST_EMPTY(parasites) //all currently existing/living guardians
Charger: Moves extremely fast, does medium damage on attack, and can charge at targets, damaging the first target hit and forcing them to drop any items they are holding.
- Dexterous: Does low damage on attack, but is capable of holding items and storing a single item within it. It will drop items held in its hands when it recalls, but it will retain the stored item.
+ Dextrous: Does low damage on attack, but is capable of holding items and storing a single item within it. It will drop items held in its hands when it recalls, but it will retain the stored item.
Explosive: High damage resist and medium power attack that may explosively teleport targets. Can turn any object, including objects too large to pick up, into a bomb, dealing explosive damage to the next person to touch it. The object will return to normal after the trap is triggered or after a delay.
@@ -691,6 +706,29 @@ GLOBAL_LIST_EMPTY(parasites) //all currently existing/living guardians
"}
+/obj/item/paper/guides/antag/guardian/nukie
+ name = "Guardian Guide"
+ info = {"A list of Guardian Types
+
+
+ Assassin: Does medium damage and takes full damage, but can enter stealth, causing its next attack to do massive damage and ignore armor. However, it becomes briefly unable to recall after attacking from stealth.
+
+ Chaos: Ignites enemies on touch and causes them to hallucinate all nearby people as the guardian. Automatically extinguishes the user if they catch on fire.
+
+ Charger: Moves extremely fast, does medium damage on attack, and can charge at targets, damaging the first target hit and forcing them to drop any items they are holding.
+
+ Dextrous: Does low damage on attack, but is capable of holding items and storing a single item within it. It will drop items held in its hands when it recalls, but it will retain the stored item.
+
+ Explosive: High damage resist and medium power attack that may explosively teleport targets. Can turn any object, including objects too large to pick up, into a bomb, dealing explosive damage to the next person to touch it. The object will return to normal after the trap is triggered or after a delay.
+
+ Lightning: Attacks apply lightning chains to targets. Has a lightning chain to the user. Lightning chains shock everything near them, doing constant damage.
+
+ Ranged: Has two modes. Ranged; which fires a constant stream of weak, armor-ignoring projectiles. Scout; Cannot attack, but can move through walls and is quite hard to see. Can lay surveillance snares, which alert it when crossed, in either mode.
+
+ Standard: Devastating close combat attacks and high damage resist. Can smash through weak walls.
+
+"}
+
/obj/item/storage/box/syndie_kit/guardian
name = "holoparasite injector kit"
@@ -699,6 +737,13 @@ GLOBAL_LIST_EMPTY(parasites) //all currently existing/living guardians
new /obj/item/guardiancreator/tech/choose/traitor(src)
new /obj/item/paper/guides/antag/guardian(src)
+/obj/item/storage/box/syndie_kit/nukieguardian
+ name = "holoparasite injector kit"
+
+/obj/item/storage/box/syndie_kit/nukieguardian/PopulateContents()
+ new /obj/item/guardiancreator/tech/choose/nukie(src)
+ new /obj/item/paper/guides/antag/guardian/nukie(src)
+
/obj/item/guardiancreator/carp
name = "holocarp fishsticks"
desc = "Using the power of Carp'sie, you can catch a carp from byond the veil of Carpthulu, and bind it to your fleshy flesh form."
diff --git a/code/modules/mob/living/simple_animal/guardian/types/explosive.dm b/code/modules/mob/living/simple_animal/guardian/types/explosive.dm
index 4e6923615d..26e5d791f3 100644
--- a/code/modules/mob/living/simple_animal/guardian/types/explosive.dm
+++ b/code/modules/mob/living/simple_animal/guardian/types/explosive.dm
@@ -36,60 +36,21 @@
return
if(isobj(A) && Adjacent(A))
if(bomb_cooldown <= world.time && !stat)
- var/obj/guardian_bomb/B = new /obj/guardian_bomb(get_turf(A))
+ var/datum/component/killerqueen/K = A.AddComponent(/datum/component/killerqueen, EXPLODE_HEAVY, CALLBACK(src, .proc/on_explode), CALLBACK(src, .proc/on_failure), \
+ examine_message = "It glows with a strange light!")
+ QDEL_IN(K, 1 MINUTES)
to_chat(src, "Success! Bomb armed!")
bomb_cooldown = world.time + 200
- B.spawner = src
- B.disguise(A)
else
to_chat(src, "Your powers are on cooldown! You must wait 20 seconds between bombs.")
-/obj/guardian_bomb
- name = "bomb"
- desc = "You shouldn't be seeing this!"
- var/obj/stored_obj
- var/mob/living/simple_animal/hostile/guardian/spawner
+/mob/living/simple_animal/hostile/guardian/bomb/proc/on_explode(atom/bomb, atom/victim)
+ if((victim == src) || (victim == summoner) || (hasmatchingsummoner(victim)))
+ to_chat(victim, "[src] glows with a strange light, and you don't touch it.")
+ return FALSE
+ to_chat(src, "One of your explosive traps caught [victim]!")
+ to_chat(victim, "[bomb] was boobytrapped!")
+ return TRUE
-
-/obj/guardian_bomb/proc/disguise(obj/A)
- A.forceMove(src)
- stored_obj = A
- opacity = A.opacity
- anchored = A.anchored
- density = A.density
- appearance = A.appearance
- addtimer(CALLBACK(src, .proc/disable), 600)
-
-/obj/guardian_bomb/proc/disable()
- stored_obj.forceMove(get_turf(src))
- to_chat(spawner, "Failure! Your trap didn't catch anyone this time.")
- qdel(src)
-
-/obj/guardian_bomb/proc/detonate(mob/living/user)
- if(isliving(user))
- if(user != spawner && user != spawner.summoner && !spawner.hasmatchingsummoner(user))
- to_chat(user, "[src] was boobytrapped!")
- to_chat(spawner, "Success! Your trap caught [user]")
- var/turf/T = get_turf(src)
- stored_obj.forceMove(T)
- playsound(T,'sound/effects/explosion2.ogg', 200, 1)
- new /obj/effect/temp_visual/explosion(T)
- user.ex_act(EXPLODE_HEAVY)
- qdel(src)
- else
- to_chat(user, "[src] glows with a strange light, and you don't touch it.")
-
-/obj/guardian_bomb/Bump(atom/A)
- detonate(A)
- ..()
-
-/obj/guardian_bomb/attackby(mob/living/user)
- detonate(user)
-
-/obj/guardian_bomb/on_attack_hand(mob/living/user, act_intent = user.a_intent, unarmed_attack_flags)
- detonate(user)
-
-/obj/guardian_bomb/examine(mob/user)
- . = stored_obj.examine(user)
- if(get_dist(user,src)<=2)
- . += "It glows with a strange light!"
+/mob/living/simple_animal/hostile/guardian/bomb/proc/on_failure(atom/bomb)
+ to_chat(src, "Failure! Your trap didn't catch anyone this time.")
diff --git a/code/modules/mob/living/simple_animal/hostile/alien.dm b/code/modules/mob/living/simple_animal/hostile/alien.dm
index 7120ab6d0a..86467624b5 100644
--- a/code/modules/mob/living/simple_animal/hostile/alien.dm
+++ b/code/modules/mob/living/simple_animal/hostile/alien.dm
@@ -7,7 +7,6 @@
icon_dead = "alienh_dead"
icon_gib = "syndicate_gib"
gender = FEMALE
- threat = 1
response_help_continuous = "pokes"
response_help_simple = "poke"
response_disarm_continuous = "shoves"
@@ -69,7 +68,6 @@
icon_state = "aliens"
icon_living = "aliens"
icon_dead = "aliens_dead"
- threat = 3
health = 150
maxHealth = 150
melee_damage_lower = 15
@@ -87,7 +85,6 @@
icon_living = "alienq"
icon_dead = "alienq_dead"
pixel_x = -16
- threat = 8
health = 250
maxHealth = 250
melee_damage_lower = 15
@@ -167,7 +164,6 @@
name = "lusty xenomorph maid"
melee_damage_lower = 0
melee_damage_upper = 0
- threat = -1
a_intent = INTENT_HELP
friendly_verb_continuous = "caresses"
friendly_verb_simple = "caress"
diff --git a/code/modules/mob/living/simple_animal/hostile/bear.dm b/code/modules/mob/living/simple_animal/hostile/bear.dm
index 057d4bdb45..1be90a07f8 100644
--- a/code/modules/mob/living/simple_animal/hostile/bear.dm
+++ b/code/modules/mob/living/simple_animal/hostile/bear.dm
@@ -2,7 +2,6 @@
/mob/living/simple_animal/hostile/bear
name = "space bear"
desc = "You don't need to be faster than a space bear, you just need to outrun your crewmates."
- threat = 1
icon_state = "bear"
icon_living = "bear"
icon_dead = "bear_dead"
@@ -33,7 +32,7 @@
melee_damage_upper = 15
wound_bonus = -5
bare_wound_bonus = 10 // BEAR wound bonus am i right
- sharpness = TRUE
+ sharpness = SHARP_EDGED
attack_verb_continuous = "claws"
attack_verb_simple = "claw"
attack_sound = 'sound/weapons/bladeslice.ogg'
diff --git a/code/modules/mob/living/simple_animal/hostile/bees.dm b/code/modules/mob/living/simple_animal/hostile/bees.dm
index 5f3d4f11c1..0a3cde3ef8 100644
--- a/code/modules/mob/living/simple_animal/hostile/bees.dm
+++ b/code/modules/mob/living/simple_animal/hostile/bees.dm
@@ -16,7 +16,6 @@
icon_state = ""
icon_living = ""
icon = 'icons/mob/bees.dmi'
- threat = 0.3
gender = FEMALE
speak_emote = list("buzzes")
emote_hear = list("buzzes")
diff --git a/code/modules/mob/living/simple_animal/hostile/bosses/boss.dm b/code/modules/mob/living/simple_animal/hostile/bosses/boss.dm
index cde63adffd..b4d60af198 100644
--- a/code/modules/mob/living/simple_animal/hostile/bosses/boss.dm
+++ b/code/modules/mob/living/simple_animal/hostile/bosses/boss.dm
@@ -1,7 +1,6 @@
/mob/living/simple_animal/hostile/boss
name = "A Perfectly Generic Boss Placeholder"
desc = ""
- threat = 10
robust_searching = TRUE
stat_attack = UNCONSCIOUS
status_flags = NONE
diff --git a/code/modules/mob/living/simple_animal/hostile/carp.dm b/code/modules/mob/living/simple_animal/hostile/carp.dm
index f775d6600c..51a646f668 100644
--- a/code/modules/mob/living/simple_animal/hostile/carp.dm
+++ b/code/modules/mob/living/simple_animal/hostile/carp.dm
@@ -7,7 +7,6 @@
icon_living = "carp"
icon_dead = "carp_dead"
icon_gib = "carp_gib"
- threat = 0.1
mob_biotypes = MOB_ORGANIC|MOB_BEAST
speak_chance = 0
turns_per_move = 5
@@ -74,7 +73,6 @@
icon_living = "megacarp"
icon_dead = "megacarp_dead"
icon_gib = "megacarp_gib"
- threat = 3
regen_amount = 6
maxHealth = 30
@@ -98,7 +96,6 @@
name = "Cayenne"
desc = "A failed Syndicate experiment in weaponized space carp technology, it now serves as a lovable mascot."
gender = FEMALE
- threat = 5
regen_amount = 8
speak_emote = list("squeaks")
diff --git a/code/modules/mob/living/simple_animal/hostile/dark_wizard.dm b/code/modules/mob/living/simple_animal/hostile/dark_wizard.dm
index 48266e3e76..9b50587b3d 100644
--- a/code/modules/mob/living/simple_animal/hostile/dark_wizard.dm
+++ b/code/modules/mob/living/simple_animal/hostile/dark_wizard.dm
@@ -1,7 +1,6 @@
/mob/living/simple_animal/hostile/dark_wizard
name = "Dark Wizard"
desc = "Killing amateurs since the dawn of times."
- threat = 3
icon = 'icons/mob/simple_human.dmi'
icon_state = "dark_wizard"
icon_living = "dark_wizard"
diff --git a/code/modules/mob/living/simple_animal/hostile/faithless.dm b/code/modules/mob/living/simple_animal/hostile/faithless.dm
index 4f8d2fef6c..b44a2502ef 100644
--- a/code/modules/mob/living/simple_animal/hostile/faithless.dm
+++ b/code/modules/mob/living/simple_animal/hostile/faithless.dm
@@ -4,7 +4,6 @@
icon_state = "faithless"
icon_living = "faithless"
icon_dead = "faithless_dead"
- threat = 1
mob_biotypes = MOB_ORGANIC|MOB_HUMANOID
gender = MALE
speak_chance = 0
diff --git a/code/modules/mob/living/simple_animal/hostile/giant_spider.dm b/code/modules/mob/living/simple_animal/hostile/giant_spider.dm
index a253ecfd96..a8799f4e8c 100644
--- a/code/modules/mob/living/simple_animal/hostile/giant_spider.dm
+++ b/code/modules/mob/living/simple_animal/hostile/giant_spider.dm
@@ -17,7 +17,6 @@
//basic spider mob, these generally guard nests
/mob/living/simple_animal/hostile/poison/giant_spider
- threat = 1
name = "giant spider"
desc = "Furry and black, it makes you shudder to look at it. This one has deep red eyes."
icon_state = "guard"
diff --git a/code/modules/mob/living/simple_animal/hostile/gorilla/emotes.dm b/code/modules/mob/living/simple_animal/hostile/gorilla/emotes.dm
index 100db06174..8fb531f1fb 100644
--- a/code/modules/mob/living/simple_animal/hostile/gorilla/emotes.dm
+++ b/code/modules/mob/living/simple_animal/hostile/gorilla/emotes.dm
@@ -1,6 +1,5 @@
/datum/emote/sound/gorilla
mob_type_allowed_typecache = /mob/living/simple_animal/hostile/gorilla
- mob_type_blacklist_typecache = list()
/datum/emote/sound/gorilla/ooga
key = "ooga"
@@ -8,4 +7,3 @@
message = "oogas."
message_param = "oogas at %t."
sound = 'sound/creatures/gorilla.ogg'
-
diff --git a/code/modules/mob/living/simple_animal/hostile/gorilla/gorilla.dm b/code/modules/mob/living/simple_animal/hostile/gorilla/gorilla.dm
index f48eef083b..ec0b7acd07 100644
--- a/code/modules/mob/living/simple_animal/hostile/gorilla/gorilla.dm
+++ b/code/modules/mob/living/simple_animal/hostile/gorilla/gorilla.dm
@@ -9,7 +9,6 @@
icon_state = "crawling"
icon_living = "crawling"
icon_dead = "dead"
- threat = 0.5
mob_biotypes = MOB_ORGANIC|MOB_HUMANOID
speak_chance = 80
maxHealth = 220
diff --git a/code/modules/mob/living/simple_animal/hostile/headcrab.dm b/code/modules/mob/living/simple_animal/hostile/headcrab.dm
index 501534237a..ac53ff794b 100644
--- a/code/modules/mob/living/simple_animal/hostile/headcrab.dm
+++ b/code/modules/mob/living/simple_animal/hostile/headcrab.dm
@@ -6,7 +6,6 @@
icon_state = "headcrab"
icon_living = "headcrab"
icon_dead = "headcrab_dead"
- threat = 1
gender = NEUTER
health = 50
maxHealth = 50
diff --git a/code/modules/mob/living/simple_animal/hostile/hostile.dm b/code/modules/mob/living/simple_animal/hostile/hostile.dm
index 5ed3cd0cdd..186fe36a10 100644
--- a/code/modules/mob/living/simple_animal/hostile/hostile.dm
+++ b/code/modules/mob/living/simple_animal/hostile/hostile.dm
@@ -3,7 +3,6 @@
stop_automated_movement_when_pulled = 0
obj_damage = 40
environment_smash = ENVIRONMENT_SMASH_STRUCTURES //Bitflags. Set to ENVIRONMENT_SMASH_STRUCTURES to break closets,tables,racks, etc; ENVIRONMENT_SMASH_WALLS for walls; ENVIRONMENT_SMASH_RWALLS for rwalls
- var/threat = 0 // for dynamic
var/atom/target
var/ranged = FALSE
var/rapid = 0 //How many shots per volley.
@@ -600,6 +599,3 @@ mob/living/simple_animal/hostile/proc/DestroySurroundings() // for use with mega
. += M
else if (M.loc.type in hostile_machines)
. += M.loc
-
-/mob/living/simple_animal/hostile/proc/threat()
- return threat
diff --git a/code/modules/mob/living/simple_animal/hostile/jungle/leaper.dm b/code/modules/mob/living/simple_animal/hostile/jungle/leaper.dm
index 55046d6555..362432b2af 100644
--- a/code/modules/mob/living/simple_animal/hostile/jungle/leaper.dm
+++ b/code/modules/mob/living/simple_animal/hostile/jungle/leaper.dm
@@ -11,7 +11,6 @@
icon_living = "leaper"
icon_dead = "leaper_dead"
mob_biotypes = MOB_ORGANIC|MOB_BEAST
- threat = 2
maxHealth = 300
health = 300
ranged = TRUE
diff --git a/code/modules/mob/living/simple_animal/hostile/jungle/mega_arachnid.dm b/code/modules/mob/living/simple_animal/hostile/jungle/mega_arachnid.dm
index 99349168f0..31303bd7f1 100644
--- a/code/modules/mob/living/simple_animal/hostile/jungle/mega_arachnid.dm
+++ b/code/modules/mob/living/simple_animal/hostile/jungle/mega_arachnid.dm
@@ -8,7 +8,6 @@
icon_living = "arachnid"
icon_dead = "arachnid_dead"
mob_biotypes = MOB_ORGANIC|MOB_BUG
- threat = 2
melee_damage_lower = 30
melee_damage_upper = 30
maxHealth = 300
diff --git a/code/modules/mob/living/simple_animal/hostile/jungle/mook.dm b/code/modules/mob/living/simple_animal/hostile/jungle/mook.dm
index 6efa0cf468..2d964f5721 100644
--- a/code/modules/mob/living/simple_animal/hostile/jungle/mook.dm
+++ b/code/modules/mob/living/simple_animal/hostile/jungle/mook.dm
@@ -14,7 +14,6 @@
icon_living = "mook"
icon_dead = "mook_dead"
mob_biotypes = MOB_ORGANIC|MOB_HUMANOID
- threat = 0.5
pixel_x = -16
maxHealth = 45
health = 45
diff --git a/code/modules/mob/living/simple_animal/hostile/jungle/seedling.dm b/code/modules/mob/living/simple_animal/hostile/jungle/seedling.dm
index 0521afa9e9..7565a686bf 100644
--- a/code/modules/mob/living/simple_animal/hostile/jungle/seedling.dm
+++ b/code/modules/mob/living/simple_animal/hostile/jungle/seedling.dm
@@ -13,7 +13,6 @@
icon_state = "seedling"
icon_living = "seedling"
icon_dead = "seedling_dead"
- threat = 0.5
maxHealth = 100
health = 100
melee_damage_lower = 30
diff --git a/code/modules/mob/living/simple_animal/hostile/killertomato.dm b/code/modules/mob/living/simple_animal/hostile/killertomato.dm
index 9cb65d7c30..b86d5d87fc 100644
--- a/code/modules/mob/living/simple_animal/hostile/killertomato.dm
+++ b/code/modules/mob/living/simple_animal/hostile/killertomato.dm
@@ -5,7 +5,6 @@
icon_living = "tomato"
icon_dead = "tomato_dead"
gender = NEUTER
- threat = 0.3
speak_chance = 0
turns_per_move = 5
maxHealth = 30
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner.dm
index 5c1229c130..2a5f279386 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner.dm
@@ -23,7 +23,6 @@ Difficulty: Medium
/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner
name = "blood-drunk miner"
desc = "A miner destined to wander forever, engaged in an endless hunt."
- threat = 15
health = 900
maxHealth = 900
icon_state = "miner"
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm
index b191eaf3fb..519d6402e6 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm
@@ -26,7 +26,6 @@ Difficulty: Hard
/mob/living/simple_animal/hostile/megafauna/bubblegum
name = "bubblegum"
desc = "In what passes for a hierarchy among slaughter demons, this one is king."
- threat = 35
health = 2500
maxHealth = 2500
attack_verb_continuous = "rends"
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm
index 18abb019d3..883ad39261 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm
@@ -24,7 +24,6 @@ Difficulty: Very Hard
/mob/living/simple_animal/hostile/megafauna/colossus
name = "colossus"
desc = "A monstrous creature protected by heavy shielding."
- threat = 40
health = 2500
maxHealth = 2500
attack_verb_continuous = "judges"
@@ -603,7 +602,6 @@ Difficulty: Very Hard
icon_state = "lightgeist"
icon_living = "lightgeist"
icon_dead = "butterfly_dead"
- threat = -0.7
turns_per_move = 1
response_help_continuous = "waves away"
response_help_simple = "wave away"
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm
index a1ecde40b8..4dfd4561d6 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm
@@ -38,7 +38,6 @@ Difficulty: Medium
/mob/living/simple_animal/hostile/megafauna/dragon
name = "ash drake"
desc = "Guardians of the necropolis."
- threat = 30
health = 2500
maxHealth = 2500
spacewalk = TRUE
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 c00d7c3130..bd3a6e8232 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm
@@ -37,7 +37,6 @@ Difficulty: Normal
/mob/living/simple_animal/hostile/megafauna/hierophant
name = "hierophant"
desc = "A massive metal club that hangs in the air as though waiting. It'll make you dance to its beat."
- threat = 30
health = 2500
maxHealth = 2500
attack_verb_continuous = "clubs"
@@ -662,7 +661,7 @@ Difficulty: Normal
continue
to_chat(M.occupant, "Your [M.name] is struck by a [name]!")
playsound(M,'sound/weapons/sear.ogg', 50, 1, -4)
- M.take_damage(damage, BURN, 0, 0)
+ M.take_damage(damage, BURN, 0, 0, null, 50)
/obj/effect/hierophant
name = "hierophant beacon"
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/legion.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/legion.dm
index 78c02fb7a7..3de4c8b41b 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/legion.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/legion.dm
@@ -18,7 +18,6 @@ Difficulty: Medium
/mob/living/simple_animal/hostile/megafauna/legion
name = "Legion"
- threat = 30
health = 800
maxHealth = 800
spacewalk = TRUE
@@ -142,7 +141,7 @@ Difficulty: Medium
loot = list(/obj/item/staff/storm)
elimination = 0
else if(prob(20))
- loot = list(/obj/structure/closet/crate/necropolis/tendril)
+ loot = list(/obj/structure/closet/crate/necropolis/tendril/random) //This one spawns a chest that could be any of the three types
..()
/obj/item/gps/internal/legion
diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/basilisk.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/basilisk.dm
index 13738b20e1..61be1f6287 100644
--- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/basilisk.dm
+++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/basilisk.dm
@@ -8,7 +8,6 @@
icon_aggro = "Basilisk_alert"
icon_dead = "Basilisk_dead"
icon_gib = "syndicate_gib"
- threat = 4
mob_biotypes = MOB_ORGANIC|MOB_BEAST
move_to_delay = 20
projectiletype = /obj/item/projectile/temp/basilisk
diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/curse_blob.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/curse_blob.dm
index 11cc0fe67e..ed056c2ad9 100644
--- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/curse_blob.dm
+++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/curse_blob.dm
@@ -10,7 +10,6 @@
move_to_delay = 5
vision_range = 20
aggro_vision_range = 20
- threat = 1
maxHealth = 40 //easy to kill, but oh, will you be seeing a lot of them.
health = 40
melee_damage_lower = 10
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 87ec79abcf..e347056924 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
@@ -11,7 +11,6 @@
robust_searching = TRUE
ranged_ignores_vision = TRUE
ranged = TRUE
- threat = 5
obj_damage = 5
vision_range = 6
aggro_vision_range = 18
@@ -26,6 +25,7 @@
var/list/attack_action_types = list()
var/can_talk = FALSE
var/obj/loot_drop = null
+ var/crate_type = /obj/structure/closet/crate/necropolis/tendril
var/owner
//Gives player-controlled variants the ability to swap attacks
@@ -183,7 +183,7 @@ While using this makes the system rely on OnFire, it still gives options for tim
activator = null
-obj/structure/elite_tumor/proc/spawn_elite(var/mob/dead/observer/elitemind)
+/obj/structure/elite_tumor/proc/spawn_elite(var/mob/dead/observer/elitemind)
var/selectedspawn = pick(potentialspawns)
mychild = new selectedspawn(loc)
visible_message("[mychild] emerges from [src]!")
@@ -194,7 +194,7 @@ obj/structure/elite_tumor/proc/spawn_elite(var/mob/dead/observer/elitemind)
icon_state = "tumor_popped"
INVOKE_ASYNC(src, .proc/arena_checks)
-obj/structure/elite_tumor/proc/return_elite()
+/obj/structure/elite_tumor/proc/return_elite()
mychild.forceMove(loc)
visible_message("[mychild] emerges from [src]!")
playsound(loc,'sound/effects/phasein.ogg', 200, 0, 50, TRUE, TRUE)
@@ -272,11 +272,11 @@ obj/structure/elite_tumor/proc/return_elite()
visible_message("[mychild] suddenly reappears above [src]!")
playsound(loc,'sound/effects/phasein.ogg', 200, 0, 50, TRUE, TRUE)
-obj/structure/elite_tumor/proc/onEliteLoss()
+/obj/structure/elite_tumor/proc/onEliteLoss()
playsound(loc,'sound/effects/tendril_destroyed.ogg', 200, 0, 50, TRUE, TRUE)
visible_message("[src] begins to convulse violently before beginning to dissipate.")
visible_message("As [src] closes, something is forced up from down below.")
- var/obj/structure/closet/crate/necropolis/tendril/lootbox = new /obj/structure/closet/crate/necropolis/tendril(loc)
+ var/obj/structure/closet/crate/necropolis/tendril/lootbox = new mychild.crate_type(loc)
if(!boosted)
mychild = null
activator = null
@@ -291,7 +291,7 @@ obj/structure/elite_tumor/proc/onEliteLoss()
activator = null
qdel(src)
-obj/structure/elite_tumor/proc/onEliteWon()
+/obj/structure/elite_tumor/proc/onEliteWon()
activity = TUMOR_PASSIVE
activator = null
mychild.revive(full_heal = TRUE, admin_revive = TRUE)
diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/goliath_broodmother.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/goliath_broodmother.dm
index 80489e0e40..4e8f4f6160 100644
--- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/goliath_broodmother.dm
+++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/goliath_broodmother.dm
@@ -25,7 +25,6 @@
icon_aggro = "broodmother"
icon_dead = "egg_sac"
icon_gib = "syndicate_gib"
- threat = 10
maxHealth = 800
health = 800
melee_damage_lower = 30
@@ -41,6 +40,7 @@
mouse_opacity = MOUSE_OPACITY_ICON
deathmessage = "explodes into gore!"
loot_drop = /obj/item/crusher_trophy/broodmother_tongue
+ crate_type = /obj/structure/closet/crate/necropolis/tendril/weapon_armor
attack_action_types = list(/datum/action/innate/elite_attack/tentacle_patch,
/datum/action/innate/elite_attack/spawn_children,
diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/herald.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/herald.dm
index 78b24acfb0..032bc2d60d 100644
--- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/herald.dm
+++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/herald.dm
@@ -24,7 +24,6 @@
icon_aggro = "herald"
icon_dead = "herald_dying"
icon_gib = "syndicate_gib"
- threat = 10
maxHealth = 800
health = 800
melee_damage_lower = 20
@@ -39,7 +38,7 @@
deathsound = 'sound/magic/demon_dies.ogg'
deathmessage = "begins to shudder as it becomes transparent..."
loot_drop = /obj/item/clothing/neck/cloak/herald_cloak
-
+ crate_type = /obj/structure/closet/crate/necropolis/tendril/magic
can_talk = 1
attack_action_types = list(/datum/action/innate/elite_attack/herald_trishot,
diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/legionnaire.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/legionnaire.dm
index 31f925fb2b..6c13bb903e 100644
--- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/legionnaire.dm
+++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/legionnaire.dm
@@ -24,7 +24,6 @@
icon_aggro = "legionnaire"
icon_dead = "legionnaire_dead"
icon_gib = "syndicate_gib"
- threat = 10
maxHealth = 800
health = 800
melee_damage_lower = 30
@@ -39,7 +38,7 @@
deathsound = 'sound/magic/curse.ogg'
deathmessage = "'s arms reach out before it falls apart onto the floor, lifeless."
loot_drop = /obj/item/crusher_trophy/legionnaire_spine
-
+ crate_type = /obj/structure/closet/crate/necropolis/tendril/misc
attack_action_types = list(/datum/action/innate/elite_attack/legionnaire_charge,
/datum/action/innate/elite_attack/head_detach,
/datum/action/innate/elite_attack/bonfire_teleport,
diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/pandora.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/pandora.dm
index cdef0c50d3..b3300a86bb 100644
--- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/pandora.dm
+++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/pandora.dm
@@ -24,7 +24,6 @@
icon_aggro = "pandora"
icon_dead = "pandora_dead"
icon_gib = "syndicate_gib"
- threat = 10
maxHealth = 800
health = 800
melee_damage_lower = 15
@@ -39,7 +38,7 @@
deathsound = 'sound/magic/repulse.ogg'
deathmessage = "'s lights flicker, before its top part falls down."
loot_drop = /obj/item/clothing/accessory/pandora_hope
-
+ crate_type = /obj/structure/closet/crate/necropolis/tendril/magic
attack_action_types = list(/datum/action/innate/elite_attack/singular_shot,
/datum/action/innate/elite_attack/magic_box,
/datum/action/innate/elite_attack/pandora_teleport,
@@ -191,4 +190,4 @@
/obj/item/clothing/accessory/pandora_hope/on_uniform_dropped(obj/item/clothing/under/U, user)
var/mob/living/L = user
if(L && L.mind)
- SEND_SIGNAL(L, COMSIG_CLEAR_MOOD_EVENT, "hope_lavaland")
+ SEND_SIGNAL(L, COMSIG_CLEAR_MOOD_EVENT, "hope_lavaland")
\ No newline at end of file
diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/goldgrub.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/goldgrub.dm
index 4a3497055a..3845c6f406 100644
--- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/goldgrub.dm
+++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/goldgrub.dm
@@ -8,7 +8,6 @@
icon_aggro = "Goldgrub_alert"
icon_dead = "Goldgrub_dead"
icon_gib = "syndicate_gib"
- threat = 0.2
mob_biotypes = MOB_ORGANIC|MOB_BEAST
vision_range = 2
aggro_vision_range = 9
diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/goliath.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/goliath.dm
index 5f6c21bbbd..fa67fd8e3b 100644
--- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/goliath.dm
+++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/goliath.dm
@@ -10,7 +10,6 @@
icon_gib = "syndicate_gib"
mob_biotypes = MOB_ORGANIC|MOB_BEAST
mouse_opacity = MOUSE_OPACITY_OPAQUE
- threat = 2
move_to_delay = 10
ranged = 1
ranged_cooldown_time = 60
@@ -201,6 +200,8 @@
L.Stun(75)
L.adjustBruteLoss(rand(15,20)) // Less stun more harm
latched = TRUE
+ for(var/obj/mecha/M in loc)
+ M.take_damage(20, BRUTE, null, null, null, 25)
if(!latched)
retract()
else
diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/hivelord.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/hivelord.dm
index 16f892bbff..11ce4f9214 100644
--- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/hivelord.dm
+++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/hivelord.dm
@@ -9,7 +9,6 @@
icon_gib = "syndicate_gib"
mob_biotypes = MOB_ORGANIC
mouse_opacity = MOUSE_OPACITY_OPAQUE
- threat = 4
move_to_delay = 14
ranged = 1
vision_range = 4
@@ -241,7 +240,6 @@
icon_state = "legion"
icon_living = "legion"
icon_dead = "legion"
- threat = 5
health = 450
maxHealth = 450
melee_damage_lower = 20
diff --git a/code/modules/mob/living/simple_animal/hostile/netherworld.dm b/code/modules/mob/living/simple_animal/hostile/netherworld.dm
index e21e3e0693..92f331071a 100644
--- a/code/modules/mob/living/simple_animal/hostile/netherworld.dm
+++ b/code/modules/mob/living/simple_animal/hostile/netherworld.dm
@@ -10,7 +10,6 @@
obj_damage = 100
melee_damage_lower = 25
melee_damage_upper = 50
- threat = 2
attack_verb_continuous = "slashes"
attack_verb_simple = "slash"
attack_sound = 'sound/weapons/bladeslice.ogg'
diff --git a/code/modules/mob/living/simple_animal/hostile/pirate.dm b/code/modules/mob/living/simple_animal/hostile/pirate.dm
index 0544ddc676..74e37bea21 100644
--- a/code/modules/mob/living/simple_animal/hostile/pirate.dm
+++ b/code/modules/mob/living/simple_animal/hostile/pirate.dm
@@ -10,7 +10,6 @@
turns_per_move = 5
response_help_continuous = "pushes"
response_help_simple = "push"
- threat = 3
speed = 0
maxHealth = 115
health = 115
diff --git a/code/modules/mob/living/simple_animal/hostile/regalrat.dm b/code/modules/mob/living/simple_animal/hostile/regalrat.dm
index e21514b37e..77b2d4268a 100644
--- a/code/modules/mob/living/simple_animal/hostile/regalrat.dm
+++ b/code/modules/mob/living/simple_animal/hostile/regalrat.dm
@@ -184,6 +184,7 @@
/mob/living/simple_animal/hostile/rat/Initialize()
. = ..()
SSmobs.cheeserats += src
+ AddComponent(/datum/component/swarming)
/mob/living/simple_animal/hostile/rat/Destroy()
SSmobs.cheeserats -= src
diff --git a/code/modules/mob/living/simple_animal/hostile/russian.dm b/code/modules/mob/living/simple_animal/hostile/russian.dm
index f7a46658c4..e9879f38ec 100644
--- a/code/modules/mob/living/simple_animal/hostile/russian.dm
+++ b/code/modules/mob/living/simple_animal/hostile/russian.dm
@@ -10,7 +10,6 @@
speak_chance = 0
turns_per_move = 5
speed = 0
- threat = 1
maxHealth = 100
health = 100
harm_intent_damage = 5
diff --git a/code/modules/mob/living/simple_animal/hostile/sharks.dm b/code/modules/mob/living/simple_animal/hostile/sharks.dm
index 1263a23d26..af61149130 100644
--- a/code/modules/mob/living/simple_animal/hostile/sharks.dm
+++ b/code/modules/mob/living/simple_animal/hostile/sharks.dm
@@ -19,7 +19,6 @@
response_harm_continuous = "kicks"
response_harm_simple = "kick"
speed = 0
- threat = 1
maxHealth = 75
health = 75
harm_intent_damage = 18
diff --git a/code/modules/mob/living/simple_animal/hostile/skeleton.dm b/code/modules/mob/living/simple_animal/hostile/skeleton.dm
index 3091949552..ebacf1edef 100644
--- a/code/modules/mob/living/simple_animal/hostile/skeleton.dm
+++ b/code/modules/mob/living/simple_animal/hostile/skeleton.dm
@@ -11,7 +11,6 @@
speak_emote = list("rattles")
emote_see = list("rattles")
a_intent = INTENT_HARM
- threat = 0.5
maxHealth = 40
blood_volume = 0
health = 40
@@ -64,7 +63,6 @@
icon_state = "templar"
icon_living = "templar"
icon_dead = "templar_dead"
- threat = 1.5
maxHealth = 150
health = 150
weather_immunities = list("snow")
@@ -98,7 +96,6 @@
icon_state = "plasma_miner"
icon_living = "plasma_miner"
icon_dead = "plasma_miner"
- threat = 2
maxHealth = 150
health = 150
harm_intent_damage = 10
@@ -116,7 +113,6 @@
icon_state = "plasma_miner_tool"
icon_living = "plasma_miner_tool"
icon_dead = "plasma_miner_tool"
- threat = 3
maxHealth = 185
health = 185
harm_intent_damage = 15
diff --git a/code/modules/mob/living/simple_animal/hostile/statue.dm b/code/modules/mob/living/simple_animal/hostile/statue.dm
index 3490d757a1..600d60eb4d 100644
--- a/code/modules/mob/living/simple_animal/hostile/statue.dm
+++ b/code/modules/mob/living/simple_animal/hostile/statue.dm
@@ -10,7 +10,6 @@
gender = NEUTER
a_intent = INTENT_HARM
mob_biotypes = MOB_HUMANOID
- threat = 3
response_help_continuous = "touches"
response_help_simple = "touch"
response_disarm_continuous = "pushes"
diff --git a/code/modules/mob/living/simple_animal/hostile/stickman.dm b/code/modules/mob/living/simple_animal/hostile/stickman.dm
index 226af952b1..6eeeabc877 100644
--- a/code/modules/mob/living/simple_animal/hostile/stickman.dm
+++ b/code/modules/mob/living/simple_animal/hostile/stickman.dm
@@ -5,7 +5,6 @@
icon_living = "stickman"
icon_dead = "stickman_dead"
icon_gib = "syndicate_gib"
- threat = 0.5
mob_biotypes = MOB_HUMANOID
gender = MALE
speak_chance = 0
diff --git a/code/modules/mob/living/simple_animal/hostile/syndicate.dm b/code/modules/mob/living/simple_animal/hostile/syndicate.dm
index 4ea8a3c5dc..103868e1d3 100644
--- a/code/modules/mob/living/simple_animal/hostile/syndicate.dm
+++ b/code/modules/mob/living/simple_animal/hostile/syndicate.dm
@@ -25,7 +25,6 @@
mob_biotypes = MOB_ORGANIC|MOB_HUMANOID
speak_chance = 0
turns_per_move = 5
- threat = 1
speed = 0
stat_attack = UNCONSCIOUS
robust_searching = 1
@@ -79,7 +78,7 @@
melee_damage_upper = 15
wound_bonus = -10
bare_wound_bonus = 20
- sharpness = TRUE
+ sharpness = SHARP_EDGED
icon_state = "syndicate_knife"
icon_living = "syndicate_knife"
loot = list(/obj/effect/gibspawner/human)
diff --git a/code/modules/mob/living/simple_animal/hostile/tree.dm b/code/modules/mob/living/simple_animal/hostile/tree.dm
index 2b9f4eea35..46a5a8ec68 100644
--- a/code/modules/mob/living/simple_animal/hostile/tree.dm
+++ b/code/modules/mob/living/simple_animal/hostile/tree.dm
@@ -7,7 +7,6 @@
icon_dead = "pine_1"
icon_gib = "pine_1"
gender = NEUTER
- threat = 1
speak_chance = 0
turns_per_move = 5
response_help_continuous = "brushes"
diff --git a/code/modules/mob/living/simple_animal/hostile/wizard.dm b/code/modules/mob/living/simple_animal/hostile/wizard.dm
index 57fb6f829d..b3523fc42c 100644
--- a/code/modules/mob/living/simple_animal/hostile/wizard.dm
+++ b/code/modules/mob/living/simple_animal/hostile/wizard.dm
@@ -8,7 +8,6 @@
mob_biotypes = MOB_ORGANIC|MOB_HUMANOID
speak_chance = 0
turns_per_move = 3
- threat = 3
speed = 0
maxHealth = 100
health = 100
diff --git a/code/modules/mob/living/simple_animal/hostile/wumborian_fugu.dm b/code/modules/mob/living/simple_animal/hostile/wumborian_fugu.dm
index dc6154c05e..bce1a01c8a 100644
--- a/code/modules/mob/living/simple_animal/hostile/wumborian_fugu.dm
+++ b/code/modules/mob/living/simple_animal/hostile/wumborian_fugu.dm
@@ -11,7 +11,6 @@
mob_biotypes = MOB_ORGANIC|MOB_BEAST
mouse_opacity = MOUSE_OPACITY_ICON
move_to_delay = 5
- threat = 1
friendly_verb_continuous = "floats near"
friendly_verb_simple = "float near"
speak_emote = list("puffs")
diff --git a/code/modules/mob/living/simple_animal/hostile/zombie.dm b/code/modules/mob/living/simple_animal/hostile/zombie.dm
index 6e4cbac022..1217084ce3 100644
--- a/code/modules/mob/living/simple_animal/hostile/zombie.dm
+++ b/code/modules/mob/living/simple_animal/hostile/zombie.dm
@@ -7,7 +7,6 @@
mob_biotypes = MOB_ORGANIC|MOB_HUMANOID
speak_chance = 0
stat_attack = UNCONSCIOUS //braains
- threat = 1
maxHealth = 100
health = 100
harm_intent_damage = 5
diff --git a/code/modules/mob/living/simple_animal/simple_animal.dm b/code/modules/mob/living/simple_animal/simple_animal.dm
index 1102559aca..be0338a60e 100644
--- a/code/modules/mob/living/simple_animal/simple_animal.dm
+++ b/code/modules/mob/living/simple_animal/simple_animal.dm
@@ -145,7 +145,9 @@
//How much bare wounding power it has
var/bare_wound_bonus = 0
//If the attacks from this are sharp
- var/sharpness = FALSE
+ var/sharpness = SHARP_NONE
+ //Generic flags
+ var/simple_mob_flags = NONE
/mob/living/simple_animal/Initialize()
. = ..()
diff --git a/code/modules/mob/living/simple_animal/slime/emote.dm b/code/modules/mob/living/simple_animal/slime/emote.dm
index 070cd48a70..9440caf3fc 100644
--- a/code/modules/mob/living/simple_animal/slime/emote.dm
+++ b/code/modules/mob/living/simple_animal/slime/emote.dm
@@ -1,6 +1,5 @@
/datum/emote/slime
mob_type_allowed_typecache = /mob/living/simple_animal/slime
- mob_type_blacklist_typecache = list()
/datum/emote/slime/bounce
key = "bounce"
diff --git a/code/modules/mob/living/simple_animal/slime/slime.dm b/code/modules/mob/living/simple_animal/slime/slime.dm
index 1bdd988694..b74dd6fa20 100644
--- a/code/modules/mob/living/simple_animal/slime/slime.dm
+++ b/code/modules/mob/living/simple_animal/slime/slime.dm
@@ -106,6 +106,7 @@
set_colour(new_colour)
. = ..()
AddComponent(/datum/component/footstep, FOOTSTEP_MOB_SLIME, 7.5)
+ set_nutrition(rand(650, 800))
/mob/living/simple_animal/slime/Destroy()
for (var/A in actions)
diff --git a/code/modules/mob/living/status_procs.dm b/code/modules/mob/living/status_procs.dm
index 0029300936..87fd0cf609 100644
--- a/code/modules/mob/living/status_procs.dm
+++ b/code/modules/mob/living/status_procs.dm
@@ -500,6 +500,15 @@
S = apply_status_effect(STATUS_EFFECT_SLEEPING, amount, updating)
return S
+///////////////////////////////// OFF BALANCE/SHOVIES ////////////////////////
+/mob/living/proc/ShoveOffBalance(amount)
+ var/datum/status_effect/off_balance/B = has_status_effect(STATUS_EFFECT_OFF_BALANCE)
+ if(B)
+ B.duration = max(world.time + amount, B.duration)
+ else if(amount > 0)
+ B = apply_status_effect(STATUS_EFFECT_OFF_BALANCE, amount)
+ return B
+
///////////////////////////////// FROZEN /////////////////////////////////////
/mob/living/proc/IsFrozen()
diff --git a/code/modules/mob/living/ventcrawling.dm b/code/modules/mob/living/ventcrawling.dm
index 36a596f42e..6661d0ccea 100644
--- a/code/modules/mob/living/ventcrawling.dm
+++ b/code/modules/mob/living/ventcrawling.dm
@@ -19,8 +19,11 @@ GLOBAL_LIST_INIT(ventcrawl_machinery, typecacheof(list(
to_chat(src, "You can't vent crawl while you're restrained!")
return
if(has_buckled_mobs())
- to_chat(src, "You can't vent crawl with other creatures on you!")
- return
+ // attempt once
+ unbuckle_all_mobs()
+ if(has_buckled_mobs())
+ to_chat(src, "You can't vent crawl with other creatures on you!")
+ return
if(buckled)
to_chat(src, "You can't vent crawl while buckled!")
return
diff --git a/code/modules/mob/login.dm b/code/modules/mob/login.dm
index 31bfb5621f..0d4afd0915 100644
--- a/code/modules/mob/login.dm
+++ b/code/modules/mob/login.dm
@@ -50,6 +50,8 @@
var/datum/callback/CB = foo
CB.Invoke()
+ mind?.hide_ckey = client?.prefs?.hide_ckey
+
log_message("Client [key_name(src)] has taken ownership of mob [src]([src.type])", LOG_OWNERSHIP)
SEND_SIGNAL(src, COMSIG_MOB_CLIENT_LOGIN, client)
diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm
index d454f85d1c..358eff7a47 100644
--- a/code/modules/mob/mob.dm
+++ b/code/modules/mob/mob.dm
@@ -649,8 +649,6 @@ GLOBAL_VAR_INIT(exploit_warn_spam_prevention, 0)
continue
if(overrides.len && (A in overrides))
continue
- if(A.IsObscured())
- continue
statpanel(listed_turf.name, null, A)
if(mind)
add_spells_to_statpanel(mind.spell_list)
diff --git a/code/modules/mob/mob_defines.dm b/code/modules/mob/mob_defines.dm
index 195c50e958..03a3ab1001 100644
--- a/code/modules/mob/mob_defines.dm
+++ b/code/modules/mob/mob_defines.dm
@@ -161,4 +161,4 @@
/// The timer that will remove our indicator for early aborts (like when an user finishes their message)
var/typing_indicator_timerid
/// Current state of our typing indicator. Used for cut overlay, DO NOT RUNTIME ASSIGN OTHER THAN FROM SHOW/CLEAR. Used to absolutely ensure we do not get stuck overlays.
- var/typing_indicator_current
+ var/mutable_appearance/typing_indicator_current
diff --git a/code/modules/mob/mob_helpers.dm b/code/modules/mob/mob_helpers.dm
index 52b755d926..aa6635f73d 100644
--- a/code/modules/mob/mob_helpers.dm
+++ b/code/modules/mob/mob_helpers.dm
@@ -431,7 +431,7 @@ It's fairly easy to fix if dealing with single letters but not so much with comp
/mob/living/getImplant(type)
return locate(type) in implants
-/proc/offer_control(mob/M)
+/proc/offer_control(mob/M,ignore_category=null)
to_chat(M, "Control of your mob has been offered to dead players.")
if(usr)
log_admin("[key_name(usr)] has offered control of ([key_name(M)]) to ghosts.")
@@ -445,7 +445,7 @@ It's fairly easy to fix if dealing with single letters but not so much with comp
var/datum/antagonist/A = M.mind.has_antag_datum(/datum/antagonist/)
if(A)
poll_message = "[poll_message] Status:[A.name]."
- var/list/mob/candidates = pollCandidatesForMob(poll_message, ROLE_PAI, null, FALSE, 100, M)
+ var/list/mob/candidates = pollCandidatesForMob(poll_message, ROLE_PAI, null, FALSE, 100, M, ignore_category)
if(LAZYLEN(candidates))
var/mob/C = pick(candidates)
diff --git a/code/modules/mob/say.dm b/code/modules/mob/say.dm
index 3ceafe4a11..7d97270808 100644
--- a/code/modules/mob/say.dm
+++ b/code/modules/mob/say.dm
@@ -43,6 +43,11 @@
if(GLOB.say_disabled) //This is here to try to identify lag problems
to_chat(usr, "Speech is currently admin-disabled.")
return
+
+ if(length(message) > MAX_MESSAGE_LEN)
+ to_chat(usr, message)
+ to_chat(usr, "^^^----- The preceeding message has been DISCARDED for being over the maximum length of [MAX_MESSAGE_LEN]. It has NOT been sent! -----^^^")
+ return
message = trim(copytext_char(sanitize(message), 1, MAX_MESSAGE_LEN))
clear_typing_indicator() // clear it immediately!
diff --git a/code/modules/mob/say_vr.dm b/code/modules/mob/say_vr.dm
index 5ed24af726..5db3ccf216 100644
--- a/code/modules/mob/say_vr.dm
+++ b/code/modules/mob/say_vr.dm
@@ -37,7 +37,7 @@ proc/get_top_level_mob(var/mob/S)
to_chat(user, "You cannot send IC messages (muted).")
return FALSE
else if(!params)
- var/subtle_emote = stripped_multiline_input(user, "Choose an emote to display.", "Subtle", null, MAX_MESSAGE_LEN)
+ var/subtle_emote = stripped_multiline_input_or_reflect(user, "Choose an emote to display.", "Subtle", null, MAX_MESSAGE_LEN)
if(subtle_emote && !check_invalid(user, subtle_emote))
var/type = input("Is this a visible or hearable emote?") as null|anything in list("Visible", "Hearable")
switch(type)
@@ -98,7 +98,7 @@ proc/get_top_level_mob(var/mob/S)
to_chat(user, "You cannot send IC messages (muted).")
return FALSE
else if(!params)
- var/subtle_emote = stripped_multiline_input(user, "Choose an emote to display.", "Subtler" , null, MAX_MESSAGE_LEN)
+ var/subtle_emote = stripped_multiline_input_or_reflect(user, "Choose an emote to display.", "Subtler" , null, MAX_MESSAGE_LEN)
if(subtle_emote && !check_invalid(user, subtle_emote))
var/type = input("Is this a visible or hearable emote?") as null|anything in list("Visible", "Hearable")
switch(type)
diff --git a/code/modules/mob/typing_indicator.dm b/code/modules/mob/typing_indicator.dm
index f28cbe4385..6154828f4d 100644
--- a/code/modules/mob/typing_indicator.dm
+++ b/code/modules/mob/typing_indicator.dm
@@ -15,13 +15,23 @@ GLOBAL_LIST_EMPTY(typing_indicator_overlays)
/mob/proc/get_typing_indicator_icon_state()
return typing_indicator_state
+/// Generates the mutable appearance for typing indicator. Should prevent stuck overlays.
+/mob/proc/generate_typing_indicator()
+ var/state = get_typing_indicator_icon_state()
+ if(ispath(state))
+ var/atom/thing = new state(null)
+ var/mutable_appearance/generated = new(thing)
+ return generated
+ else
+ CRASH("Unsupported typing indicator state: [state]")
+
/**
* Displays typing indicator.
* @param timeout_override - Sets how long until this will disappear on its own without the user finishing their message or logging out. Defaults to src.typing_indicator_timeout
* @param state_override - Sets the state that we will fetch. Defaults to src.get_typing_indicator_icon_state()
* @param force - shows even if src.typing_indcator_enabled is FALSE.
*/
-/mob/proc/display_typing_indicator(timeout_override = TYPING_INDICATOR_TIMEOUT, state_override = get_typing_indicator_icon_state(), force = FALSE)
+/mob/proc/display_typing_indicator(timeout_override = TYPING_INDICATOR_TIMEOUT, state_override = generate_typing_indicator(), force = FALSE)
if((!typing_indicator_enabled && !force) || typing_indicator_current)
return
typing_indicator_current = state_override
diff --git a/code/modules/modular_computers/NTNet/NTNRC/conversation.dm b/code/modules/modular_computers/NTNet/NTNRC/conversation.dm
index eeb5212aeb..b5f3bae53d 100644
--- a/code/modules/modular_computers/NTNet/NTNRC/conversation.dm
+++ b/code/modules/modular_computers/NTNet/NTNRC/conversation.dm
@@ -73,4 +73,4 @@
add_status_message("[client.username] has changed channel title from [title] to [newtitle]")
title = newtitle
-#undef MAX_CHANNELS
\ No newline at end of file
+#undef MAX_CHANNELS
diff --git a/code/modules/modular_computers/computers/_modular_computer_shared.dm b/code/modules/modular_computers/computers/_modular_computer_shared.dm
new file mode 100644
index 0000000000..9dde9a0c49
--- /dev/null
+++ b/code/modules/modular_computers/computers/_modular_computer_shared.dm
@@ -0,0 +1,66 @@
+
+/obj/proc/is_modular_computer()
+ return
+
+/obj/proc/get_modular_computer_part(part_type)
+ return null
+
+/obj/item/modular_computer/is_modular_computer()
+ return TRUE
+
+/obj/item/modular_computer/get_modular_computer_part(part_type)
+ if(!part_type)
+ stack_trace("get_modular_computer_part() called without a valid part_type")
+ return null
+ return all_components[part_type]
+
+
+/obj/machinery/modular_computer/is_modular_computer()
+ return TRUE
+
+/obj/machinery/modular_computer/get_modular_computer_part(part_type)
+ if(!part_type)
+ stack_trace("get_modular_computer_part() called without a valid part_type")
+ return null
+ return cpu?.all_components[part_type]
+
+
+/obj/proc/get_modular_computer_parts_examine(mob/user)
+ . = list()
+ if(!is_modular_computer())
+ return
+
+ var/user_is_adjacent = Adjacent(user) //don't reveal full details unless they're close enough to see it on the screen anyway.
+
+ var/obj/item/computer_hardware/ai_slot/ai_slot = get_modular_computer_part(MC_AI)
+ if(ai_slot)
+ if(ai_slot.stored_card)
+ if(user_is_adjacent)
+ . += "It has a slot installed for an intelliCard which contains: [ai_slot.stored_card.name]"
+ else
+ . += "It has a slot installed for an intelliCard, which appears to be occupied."
+ . += "Alt-click to eject the intelliCard."
+ else
+ . += "It has a slot installed for an intelliCard."
+
+ var/obj/item/computer_hardware/card_slot/card_slot = get_modular_computer_part(MC_CARD)
+ var/obj/item/computer_hardware/card_slot/card_slot2 = get_modular_computer_part(MC_CARD2)
+ var/multiple_slots = istype(card_slot) && istype(card_slot2)
+ if(card_slot)
+ if(card_slot?.stored_card || card_slot2?.stored_card)
+ var/obj/item/card/id/first_ID = card_slot.stored_card
+ var/obj/item/card/id/second_ID = card_slot2.stored_card
+ var/multiple_cards = istype(first_ID) && istype(second_ID)
+ if(user_is_adjacent)
+ . += "It has [multiple_slots ? "two slots" : "a slot"] for identification cards installed[multiple_cards ? " which contain [first_ID] and [second_ID]" : ", one of which contains [first_ID ? first_ID : second_ID]"]."
+ else
+ . += "It has [multiple_slots ? "two slots" : "a slot"] for identification cards installed, [multiple_cards ? "both of which appear" : "and one of them appears"] to be occupied."
+ . += "Alt-click [src] to eject the identification card[multiple_cards ? "s":""]."
+ else
+ . += "It has [multiple_slots ? "two slots" : "a slot"] installed for identification cards."
+
+ var/obj/item/computer_hardware/printer/printer_slot = get_modular_computer_part(MC_PRINT)
+ if(printer_slot)
+ . += "It has a printer installed."
+ if(user_is_adjacent)
+ . += "The printer's paper levels are at: [printer_slot.stored_paper]/[printer_slot.max_paper].]"
diff --git a/code/modules/modular_computers/computers/item/computer.dm b/code/modules/modular_computers/computers/item/computer.dm
index d722197bd9..63cb1cc5fa 100644
--- a/code/modules/modular_computers/computers/item/computer.dm
+++ b/code/modules/modular_computers/computers/item/computer.dm
@@ -4,9 +4,16 @@
/obj/item/modular_computer
name = "modular microcomputer"
desc = "A small portable microcomputer."
+ icon = 'icons/obj/computer.dmi'
+ icon_state = "laptop-open"
+ var/light_on = FALSE
+ integrity_failure = 0.5
+ max_integrity = 100
+ armor = list("melee" = 0, "bullet" = 20, "laser" = 20, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 0, "acid" = 0)
var/enabled = 0 // Whether the computer is turned on.
var/screen_on = 1 // Whether the computer is active/opened/it's screen is on.
+ var/device_theme = "ntos" // Sets the theme for the main menu, hardware config, and file browser apps. Overridden by certain non-NT devices.
var/datum/computer_file/program/active_program = null // A currently active program running on the computer.
var/hardware_flag = 0 // A flag that describes this device type
var/last_power_usage = 0
@@ -21,8 +28,6 @@
// must have it's own DMI file. Icon states must be called exactly the same in all files, but may look differently
// If you create a program which is limited to Laptops and Consoles you don't have to add it's icon_state overlay for Tablets too, for example.
- icon = 'icons/obj/computer.dmi'
- icon_state = "laptop-open"
var/icon_state_unpowered = null // Icon state when the computer is turned off.
var/icon_state_powered = null // Icon state when the computer is turned on.
var/icon_state_menu = "menu" // Icon state overlay when the computer is turned on, but no program is loaded that would override the screen.
@@ -30,20 +35,16 @@
var/max_hardware_size = 0 // Maximal hardware w_class. Tablets/PDAs have 1, laptops 2, consoles 4.
var/steel_sheet_cost = 5 // Amount of steel sheets refunded when disassembling an empty frame of this computer.
- integrity_failure = 0.5
- max_integrity = 100
- armor = list("melee" = 0, "bullet" = 20, "laser" = 20, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 0, "acid" = 0)
-
- // Important hardware (must be installed for computer to work)
-
- // Optional hardware (improves functionality, but is not critical for computer to work)
-
- var/list/all_components = list() // List of "connection ports" in this computer and the components with which they are plugged
+ /// List of "connection ports" in this computer and the components with which they are plugged
+ var/list/all_components = list()
+ /// Lazy List of extra hardware slots that can be used modularly.
+ var/list/expansion_bays
+ /// Number of total expansion bays this computer has available.
+ var/max_bays = 0
var/list/idle_threads // Idle programs on background. They still receive process calls but can't be interacted with.
var/obj/physical = null // Object that represents our computer. It's used for Adjacent() and UI visibility checks.
var/has_light = FALSE //If the computer has a flashlight/LED light/what-have-you installed
- var/light_on = FALSE //If that light is enabled
var/comp_light_luminosity = 3 //The brightness of that light
var/comp_light_color //The color of that light
@@ -70,80 +71,15 @@
physical = null
return ..()
-
-/obj/item/modular_computer/proc/add_verb(var/path)
- switch(path)
- if(MC_CARD)
- verbs += /obj/item/modular_computer/proc/eject_id
- if(MC_SDD)
- verbs += /obj/item/modular_computer/proc/eject_disk
- if(MC_AI)
- verbs += /obj/item/modular_computer/proc/eject_card
-
-/obj/item/modular_computer/proc/remove_verb(path)
- switch(path)
- if(MC_CARD)
- verbs -= /obj/item/modular_computer/proc/eject_id
- if(MC_SDD)
- verbs -= /obj/item/modular_computer/proc/eject_disk
- if(MC_AI)
- verbs -= /obj/item/modular_computer/proc/eject_card
-
-// Eject ID card from computer, if it has ID slot with card inside.
-/obj/item/modular_computer/proc/eject_id()
- set name = "Eject ID"
- set category = "Object"
- set src in view(1)
-
- if(issilicon(usr))
- return
- var/obj/item/computer_hardware/card_slot/card_slot = all_components[MC_CARD]
- if(usr.canUseTopic(src))
- card_slot.try_eject(null, usr)
-
-// Eject ID card from computer, if it has ID slot with card inside.
-/obj/item/modular_computer/proc/eject_card()
- set name = "Eject Intellicard"
- set category = "Object"
-
- if(issilicon(usr))
- return
- var/obj/item/computer_hardware/ai_slot/ai_slot = all_components[MC_AI]
- if(usr.canUseTopic(src))
- ai_slot.try_eject(null, usr,1)
-
-
-// Eject ID card from computer, if it has ID slot with card inside.
-/obj/item/modular_computer/proc/eject_disk()
- set name = "Eject Data Disk"
- set category = "Object"
-
- if(issilicon(usr))
- return
-
- if(usr.canUseTopic(src))
- var/obj/item/computer_hardware/hard_drive/portable/portable_drive = all_components[MC_SDD]
- if(uninstall_component(portable_drive, usr))
- portable_drive.verb_pickup()
-
/obj/item/modular_computer/AltClick(mob/user)
- . = ..()
+ ..()
if(issilicon(user))
return
- if(user.canUseTopic(src))
+ if(user.canUseTopic(src, BE_CLOSE))
+ var/obj/item/computer_hardware/card_slot/card_slot2 = all_components[MC_CARD2]
var/obj/item/computer_hardware/card_slot/card_slot = all_components[MC_CARD]
- var/obj/item/computer_hardware/ai_slot/ai_slot = all_components[MC_AI]
- var/obj/item/computer_hardware/hard_drive/portable/portable_drive = all_components[MC_SDD]
- if(portable_drive)
- if(uninstall_component(portable_drive, user))
- portable_drive.verb_pickup()
- else
- if(card_slot && card_slot.try_eject(null, user))
- return
- if(ai_slot)
- ai_slot.try_eject(null, user)
- return TRUE
+ return (card_slot2?.try_eject(user) || card_slot?.try_eject(user)) //Try the secondary one first.
// Gets IDs/access levels from card slot. Would be useful when/if PDAs would become modular PCs.
/obj/item/modular_computer/GetAccess()
@@ -159,23 +95,29 @@
return ..()
/obj/item/modular_computer/RemoveID()
+ var/obj/item/computer_hardware/card_slot/card_slot2 = all_components[MC_CARD2]
var/obj/item/computer_hardware/card_slot/card_slot = all_components[MC_CARD]
- if(!card_slot)
- return
- return card_slot.RemoveID()
+ return (card_slot2?.try_eject() || card_slot?.try_eject()) //Try the secondary one first.
/obj/item/modular_computer/InsertID(obj/item/inserting_item)
var/obj/item/computer_hardware/card_slot/card_slot = all_components[MC_CARD]
- if(!card_slot)
+ var/obj/item/computer_hardware/card_slot/card_slot2 = all_components[MC_CARD2]
+ if(!(card_slot || card_slot2))
+ //to_chat(user, "There isn't anywhere you can fit a card into on this computer.")
return FALSE
+
var/obj/item/card/inserting_id = inserting_item.RemoveID()
if(!inserting_id)
return FALSE
- return card_slot.try_insert(inserting_id)
+
+ if((card_slot?.try_insert(inserting_id)) || (card_slot2?.try_insert(inserting_id)))
+ return TRUE
+ //to_chat(user, "This computer doesn't have an open card slot.")
+ return FALSE
/obj/item/modular_computer/MouseDrop(obj/over_object, src_location, over_location)
var/mob/M = usr
- if((!istype(over_object, /obj/screen)) && usr.canUseTopic(src))
+ if((!istype(over_object, /obj/screen)) && usr.canUseTopic(src, BE_CLOSE))
return attack_self(M)
return ..()
@@ -195,12 +137,22 @@
/obj/item/modular_computer/emag_act(mob/user)
. = ..()
- if(obj_flags & EMAGGED)
- to_chat(user, "\The [src] was already emagged.")
- return
- obj_flags |= EMAGGED
- to_chat(user, "You emag \the [src]. It's screen briefly shows a \"OVERRIDE ACCEPTED: New software downloads available.\" message.")
- return TRUE
+ if(!enabled)
+ to_chat(user, "You'd need to turn the [src] on first.")
+ return FALSE
+ obj_flags |= EMAGGED //Mostly for consistancy purposes; the programs will do their own emag handling
+ var/newemag = FALSE
+ var/obj/item/computer_hardware/hard_drive/drive = all_components[MC_HDD]
+ for(var/datum/computer_file/program/app in drive.stored_files)
+ if(!istype(app))
+ continue
+ if(app.run_emag())
+ newemag = TRUE
+ if(newemag)
+ to_chat(user, "You swipe \the [src]. A console window momentarily fills the screen, with white text rapidly scrolling past.")
+ return TRUE
+ to_chat(user, "You swipe \the [src]. A console window fills the screen, but it quickly closes itself after only a few lines are written to it.")
+ return FALSE
/obj/item/modular_computer/examine(mob/user)
. = ..()
@@ -209,13 +161,14 @@
else if(obj_integrity < max_integrity)
. += "It is damaged."
+ . += get_modular_computer_parts_examine(user)
+
/obj/item/modular_computer/update_icon_state()
if(!enabled)
icon_state = icon_state_unpowered
else
icon_state = icon_state_powered
-
/obj/item/modular_computer/update_overlays()
. = ..()
if(!display_overlays)
@@ -306,6 +259,8 @@
/obj/item/modular_computer/proc/get_header_data()
var/list/data = list()
+ data["PC_device_theme"] = device_theme
+
var/obj/item/computer_hardware/battery/battery_module = all_components[MC_CELL]
var/obj/item/computer_hardware/recharger/recharger = all_components[MC_CHARGE]
@@ -407,17 +362,17 @@
if(install_component(W, user))
return
- if(istype(W, /obj/item/wrench))
+ if(W.tool_behaviour == TOOL_WRENCH)
if(all_components.len)
to_chat(user, "Remove all components from \the [src] before disassembling it.")
return
new /obj/item/stack/sheet/metal( get_turf(src.loc), steel_sheet_cost )
- physical.visible_message("\The [src] has been disassembled by [user].")
+ physical.visible_message("\The [src] is disassembled by [user].")
relay_qdel()
qdel(src)
return
- if(istype(W, /obj/item/weldingtool))
+ if(W.tool_behaviour == TOOL_WELDER)
if(obj_integrity == max_integrity)
to_chat(user, "\The [src] does not require repairs.")
return
@@ -431,7 +386,7 @@
to_chat(user, "You repair \the [src].")
return
- if(istype(W, /obj/item/screwdriver))
+ if(W.tool_behaviour == TOOL_SCREWDRIVER)
if(!all_components.len)
to_chat(user, "This device doesn't have any components installed.")
return
@@ -440,7 +395,7 @@
var/obj/item/computer_hardware/H = all_components[h]
component_names.Add(H.name)
- var/choice = input(user, "Which component do you want to uninstall?", "Computer maintenance", null) as null|anything in component_names
+ var/choice = input(user, "Which component do you want to uninstall?", "Computer maintenance", null) as null|anything in sortList(component_names)
if(!choice)
return
diff --git a/code/modules/modular_computers/computers/item/computer_components.dm b/code/modules/modular_computers/computers/item/computer_components.dm
index 3c94a66384..8668b279cf 100644
--- a/code/modules/modular_computers/computers/item/computer_components.dm
+++ b/code/modules/modular_computers/computers/item/computer_components.dm
@@ -6,6 +6,14 @@
to_chat(user, "This component is too large for \the [src]!")
return FALSE
+ if(H.expansion_hw)
+ if(LAZYLEN(expansion_bays) >= max_bays)
+ to_chat(user, "All of the computer's expansion bays are filled.")
+ return FALSE
+ if(LAZYACCESS(expansion_bays, H.device_type))
+ to_chat(user, "The computer immediately ejects /the [H] and flashes an error: \"Hardware Address Conflict\".")
+ return FALSE
+
if(all_components[H.device_type])
to_chat(user, "This computer's hardware slot is already occupied by \the [all_components[H.device_type]].")
return FALSE
@@ -20,6 +28,8 @@
if(user && !user.transferItemToLoc(H, src))
return FALSE
+ if(H.expansion_hw)
+ LAZYSET(expansion_bays, H.device_type, H)
all_components[H.device_type] = H
to_chat(user, "You install \the [H] into \the [src].")
@@ -32,7 +42,9 @@
/obj/item/modular_computer/proc/uninstall_component(obj/item/computer_hardware/H, mob/living/user = null)
if(H.holder != src) // Not our component at all.
return FALSE
+ if(H.expansion_hw)
+ LAZYREMOVE(expansion_bays, H.device_type)
all_components.Remove(H.device_type)
to_chat(user, "You remove \the [H] from \the [src].")
@@ -43,6 +55,7 @@
if(enabled && !use_power())
shutdown_computer()
update_icon()
+ return TRUE
// Checks all hardware pieces to determine if name matches, if yes, returns the hardware piece, otherwise returns null
diff --git a/code/modules/modular_computers/computers/item/computer_damage.dm b/code/modules/modular_computers/computers/item/computer_damage.dm
index 6664b449bd..b510f8aded 100644
--- a/code/modules/modular_computers/computers/item/computer_damage.dm
+++ b/code/modules/modular_computers/computers/item/computer_damage.dm
@@ -18,13 +18,13 @@
/obj/item/modular_computer/proc/break_apart()
if(!(flags_1 & NODECONSTRUCT_1))
- physical.visible_message("\The [src] breaks apart!")
+ physical.visible_message("\The [src] breaks apart!")
var/turf/newloc = get_turf(src)
new /obj/item/stack/sheet/metal(newloc, round(steel_sheet_cost/2))
for(var/C in all_components)
var/obj/item/computer_hardware/H = all_components[C]
if(QDELETED(H))
- return
+ continue
uninstall_component(H)
H.forceMove(newloc)
if(prob(25))
diff --git a/code/modules/modular_computers/computers/item/computer_power.dm b/code/modules/modular_computers/computers/item/computer_power.dm
index d3c65f86ec..b5188f43d9 100644
--- a/code/modules/modular_computers/computers/item/computer_power.dm
+++ b/code/modules/modular_computers/computers/item/computer_power.dm
@@ -28,8 +28,7 @@
/obj/item/modular_computer/get_cell()
var/obj/item/computer_hardware/battery/battery_module = all_components[MC_CELL]
- if(battery_module && battery_module.battery)
- return battery_module.battery
+ return battery_module?.get_cell()
// Used in following function to reduce copypaste
/obj/item/modular_computer/proc/power_failure()
diff --git a/code/modules/modular_computers/computers/item/computer_ui.dm b/code/modules/modular_computers/computers/item/computer_ui.dm
index 6266daad2b..4a985b93c1 100644
--- a/code/modules/modular_computers/computers/item/computer_ui.dm
+++ b/code/modules/modular_computers/computers/item/computer_ui.dm
@@ -3,7 +3,7 @@
ui_interact(user)
// Operates TGUI
-/obj/item/modular_computer/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state)
+/obj/item/modular_computer/ui_interact(mob/user, datum/tgui/ui)
if(!enabled)
if(ui)
ui.close()
@@ -14,7 +14,7 @@
return 0
// Robots don't really need to see the screen, their wireless connection works as long as computer is on.
- if(!screen_on && !hasSiliconAccessInArea(user))
+ if(!screen_on && !issilicon(user))
if(ui)
ui.close()
return 0
@@ -33,19 +33,44 @@
to_chat(user, "\The [src] beeps three times, it's screen displaying a \"DISK ERROR\" warning.")
return // No HDD, No HDD files list or no stored files. Something is very broken.
- ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+ ui = SStgui.try_update_ui(user, src, ui)
if (!ui)
- var/datum/asset/assets = get_asset_datum(/datum/asset/simple/headers)
- assets.send(user)
- assets = get_asset_datum(/datum/asset/simple/arcade)
- assets.send(user)
- ui = new(user, src, ui_key, "NtosMain", "NtOS Main menu", 400, 500, master_ui, state)
+ ui = new(user, src, "NtosMain")
+ ui.set_autoupdate(TRUE)
ui.open()
- ui.set_autoupdate(state = 1)
+ ui.send_asset(get_asset_datum(/datum/asset/simple/headers))
/obj/item/modular_computer/ui_data(mob/user)
var/list/data = get_header_data()
+ data["device_theme"] = device_theme
+
+ data["login"] = list()
+ var/obj/item/computer_hardware/card_slot/cardholder = all_components[MC_CARD]
+ if(cardholder)
+ var/obj/item/card/id/stored_card = cardholder.GetID()
+ if(stored_card)
+ var/stored_name = stored_card.registered_name
+ var/stored_title = stored_card.assignment
+ if(!stored_name)
+ stored_name = "Unknown"
+ if(!stored_title)
+ stored_title = "Unknown"
+ data["login"] = list(
+ IDName = stored_name,
+ IDJob = stored_title,
+ )
+
+ data["removable_media"] = list()
+ if(all_components[MC_SDD])
+ data["removable_media"] += "removable storage disk"
+ var/obj/item/computer_hardware/ai_slot/intelliholder = all_components[MC_AI]
+ if(intelliholder?.stored_card)
+ data["removable_media"] += "intelliCard"
+ var/obj/item/computer_hardware/card_slot/secondarycardholder = all_components[MC_CARD2]
+ if(secondarycardholder?.stored_card)
+ data["removable_media"] += "secondary RFID card"
+
data["programs"] = list()
var/obj/item/computer_hardware/hard_drive/hard_drive = all_components[MC_HDD]
for(var/datum/computer_file/program/P in hard_drive.stored_files)
@@ -143,6 +168,7 @@
set_light(comp_light_luminosity, 1, comp_light_color)
else
set_light(0)
+ return TRUE
if("PC_light_color")
var/mob/user = usr
@@ -158,6 +184,36 @@
light_color = new_color
update_light()
return TRUE
+
+ if("PC_Eject_Disk")
+ var/param = params["name"]
+ var/mob/user = usr
+ switch(param)
+ if("removable storage disk")
+ var/obj/item/computer_hardware/hard_drive/portable/portable_drive = all_components[MC_SDD]
+ if(!portable_drive)
+ return
+ if(uninstall_component(portable_drive, usr))
+ user.put_in_hands(portable_drive)
+ playsound(src, 'sound/machines/card_slide.ogg', 50)
+ if("intelliCard")
+ var/obj/item/computer_hardware/ai_slot/intelliholder = all_components[MC_AI]
+ if(!intelliholder)
+ return
+ if(intelliholder.try_eject(user))
+ playsound(src, 'sound/machines/card_slide.ogg', 50)
+ if("ID")
+ var/obj/item/computer_hardware/card_slot/cardholder = all_components[MC_CARD]
+ if(!cardholder)
+ return
+ cardholder.try_eject(user)
+ if("secondary RFID card")
+ var/obj/item/computer_hardware/card_slot/cardholder = all_components[MC_CARD2]
+ if(!cardholder)
+ return
+ cardholder.try_eject(user)
+
+
else
return
diff --git a/code/modules/modular_computers/computers/item/laptop.dm b/code/modules/modular_computers/computers/item/laptop.dm
index 4781cbd8bc..ef83140a8f 100644
--- a/code/modules/modular_computers/computers/item/laptop.dm
+++ b/code/modules/modular_computers/computers/item/laptop.dm
@@ -7,10 +7,12 @@
icon_state_powered = "laptop"
icon_state_unpowered = "laptop-off"
icon_state_menu = "menu"
+ display_overlays = FALSE
hardware_flag = PROGRAM_LAPTOP
max_hardware_size = 2
w_class = WEIGHT_CLASS_NORMAL
+ max_bays = 4
// No running around with open laptops in hands.
item_flags = SLOWS_WHILE_IN_HAND
@@ -18,8 +20,8 @@
screen_on = 0 // Starts closed
var/start_open = TRUE // unless this var is set to 1
var/icon_state_closed = "laptop-closed"
- display_overlays = FALSE
var/w_class_open = WEIGHT_CLASS_BULKY
+ var/slowdown_open = TRUE
/obj/item/modular_computer/laptop/examine(mob/user)
. = ..()
@@ -38,6 +40,13 @@
else
. = ..()
+/obj/item/modular_computer/laptop/update_overlays()
+ if(screen_on)
+ return ..()
+ else
+ cut_overlays()
+ icon_state = icon_state_closed
+
/obj/item/modular_computer/laptop/attack_self(mob/user)
if(!screen_on)
try_toggle_open(user)
@@ -65,6 +74,7 @@
M.put_in_hand(src, H.held_index)
/obj/item/modular_computer/laptop/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
+ . = ..()
if(screen_on && isturf(loc))
return attack_self(user)
@@ -73,7 +83,7 @@
return
if(!isturf(loc) && !ismob(loc)) // No opening it in backpack.
return
- if(!user.canUseTopic(src))
+ if(!user.canUseTopic(src, BE_CLOSE))
return
toggle_open(user)
@@ -82,15 +92,17 @@
/obj/item/modular_computer/laptop/AltClick(mob/user)
if(screen_on) // Close it.
try_toggle_open(user)
- return TRUE
- return ..()
+ else
+ return ..()
/obj/item/modular_computer/laptop/proc/toggle_open(mob/living/user=null)
if(screen_on)
to_chat(user, "You close \the [src].")
+ slowdown = initial(slowdown)
w_class = initial(w_class)
else
to_chat(user, "You open \the [src].")
+ slowdown = slowdown_open
w_class = w_class_open
screen_on = !screen_on
diff --git a/code/modules/modular_computers/computers/item/laptop_presets.dm b/code/modules/modular_computers/computers/item/laptop_presets.dm
index e50392e3b3..6bc2919bea 100644
--- a/code/modules/modular_computers/computers/item/laptop_presets.dm
+++ b/code/modules/modular_computers/computers/item/laptop_presets.dm
@@ -20,4 +20,3 @@
/obj/item/modular_computer/laptop/preset/civilian/install_programs()
var/obj/item/computer_hardware/hard_drive/hard_drive = all_components[MC_HDD]
hard_drive.store_file(new/datum/computer_file/program/chatclient())
- hard_drive.store_file(new/datum/computer_file/program/nttransfer())
diff --git a/code/modules/modular_computers/computers/item/processor.dm b/code/modules/modular_computers/computers/item/processor.dm
index 81e2cb95e6..0d7b567877 100644
--- a/code/modules/modular_computers/computers/item/processor.dm
+++ b/code/modules/modular_computers/computers/item/processor.dm
@@ -1,4 +1,5 @@
// Held by /obj/machinery/modular_computer to reduce amount of copy-pasted code.
+//TODO: REFACTOR THIS SPAGHETTI CODE, MAKE IT A COMPUTER_HARDWARE COMPONENT OR REMOVE IT
/obj/item/modular_computer/processor
name = "processing unit"
desc = "You shouldn't see this. If you do, report it."
@@ -7,23 +8,27 @@
icon_state_unpowered = null
icon_state_menu = null
hardware_flag = 0
+ max_bays = 4
var/obj/machinery/modular_computer/machinery_computer = null
/obj/item/modular_computer/processor/Destroy()
- . = ..()
if(machinery_computer && (machinery_computer.cpu == src))
machinery_computer.cpu = null
+ machinery_computer.UnregisterSignal(src, COMSIG_ATOM_UPDATED_ICON)
machinery_computer = null
-
-/obj/item/modular_computer/processor/Initialize(mapload)
. = ..()
- if(!loc || !istype(loc, /obj/machinery/modular_computer))
+
+/obj/item/modular_computer/processor/New(comp)
+ ..()
+ STOP_PROCESSING(SSobj, src) // Processed by its machine
+
+ if(!comp || !istype(comp, /obj/machinery/modular_computer))
CRASH("Inapropriate type passed to obj/item/modular_computer/processor/New()! Aborting.")
// Obtain reference to machinery computer
all_components = list()
idle_threads = list()
- machinery_computer = loc
+ machinery_computer = comp
machinery_computer.cpu = src
hardware_flag = machinery_computer.hardware_flag
max_hardware_size = machinery_computer.max_hardware_size
@@ -39,7 +44,7 @@
qdel(machinery_computer)
// This thing is not meant to be used on it's own, get topic data from our machinery owner.
-//obj/item/modular_computer/processor/canUseTopic(atom/movable/M, be_close=FALSE, no_dextery=FALSE, no_tk=FALSE)
+//obj/item/modular_computer/processor/canUseTopic(atom/movable/M, be_close=FALSE, no_dexterity=FALSE, no_tk=FALSE)
// if(!machinery_computer)
// return 0
@@ -52,20 +57,5 @@
machinery_computer.update_icon()
return
-/obj/item/modular_computer/processor/add_verb(path)
- switch(path)
- if(MC_CARD)
- machinery_computer.verbs += /obj/machinery/modular_computer/proc/eject_id
- if(MC_SDD)
- machinery_computer.verbs += /obj/machinery/modular_computer/proc/eject_disk
- if(MC_AI)
- machinery_computer.verbs += /obj/machinery/modular_computer/proc/eject_card
-
-/obj/item/modular_computer/processor/remove_verb(path)
- switch(path)
- if(MC_CARD)
- machinery_computer.verbs -= /obj/machinery/modular_computer/proc/eject_id
- if(MC_SDD)
- machinery_computer.verbs -= /obj/machinery/modular_computer/proc/eject_disk
- if(MC_AI)
- machinery_computer.verbs -= /obj/machinery/modular_computer/proc/eject_card
+/obj/item/modular_computer/processor/attack_ghost(mob/user)
+ ui_interact(user)
diff --git a/code/modules/modular_computers/computers/item/tablet.dm b/code/modules/modular_computers/computers/item/tablet.dm
index a371e97ec6..67e8118c7a 100644
--- a/code/modules/modular_computers/computers/item/tablet.dm
+++ b/code/modules/modular_computers/computers/item/tablet.dm
@@ -5,9 +5,11 @@
icon_state_unpowered = "tablet"
icon_state_powered = "tablet"
icon_state_menu = "menu"
+ // worn_icon_state = "tablet"
hardware_flag = PROGRAM_TABLET
max_hardware_size = 1
w_class = WEIGHT_CLASS_SMALL
+ max_bays = 3
steel_sheet_cost = 1
slot_flags = ITEM_SLOT_ID | ITEM_SLOT_BELT
has_light = TRUE //LED flashlight!
@@ -32,3 +34,17 @@
slot_flags = ITEM_SLOT_ID | ITEM_SLOT_BELT
comp_light_luminosity = 6.3
has_variants = FALSE
+
+/// Given to Nuke Ops members.
+/obj/item/modular_computer/tablet/nukeops
+ icon_state = "tablet-syndicate"
+ comp_light_luminosity = 6.3
+ has_variants = FALSE
+ device_theme = "syndicate"
+
+/obj/item/modular_computer/tablet/nukeops/emag_act(mob/user)
+ if(!enabled)
+ to_chat(user, "You'd need to turn the [src] on first.")
+ return FALSE
+ to_chat(user, "You swipe \the [src]. It's screen briefly shows a message reading \"MEMORY CODE INJECTION DETECTED AND SUCCESSFULLY QUARANTINED\".")
+ return FALSE
diff --git a/code/modules/modular_computers/computers/item/tablet_presets.dm b/code/modules/modular_computers/computers/item/tablet_presets.dm
index f516d3802f..8ac669d2bf 100644
--- a/code/modules/modular_computers/computers/item/tablet_presets.dm
+++ b/code/modules/modular_computers/computers/item/tablet_presets.dm
@@ -22,23 +22,48 @@
/obj/item/modular_computer/tablet/preset/cargo/Initialize()
. = ..()
+ var/obj/item/computer_hardware/hard_drive/small/hard_drive = new
install_component(new /obj/item/computer_hardware/processor_unit/small)
install_component(new /obj/item/computer_hardware/battery(src, /obj/item/stock_parts/cell/computer))
- install_component(new /obj/item/computer_hardware/hard_drive/small)
+ install_component(hard_drive)
+ install_component(new /obj/item/computer_hardware/card_slot)
install_component(new /obj/item/computer_hardware/network_card)
install_component(new /obj/item/computer_hardware/printer/mini)
+ hard_drive.store_file(new /datum/computer_file/program/bounty)
+ //hard_drive.store_file(new /datum/computer_file/program/shipping)
-/obj/item/modular_computer/tablet/syndicate_contract_uplink/preset/uplink/Initialize() // Given by the syndicate as part of the contract uplink bundle - loads in the Contractor Uplink.
+/obj/item/modular_computer/tablet/preset/advanced/atmos/Initialize() //This will be defunct and will be replaced when NtOS PDAs are done
+ . = ..()
+ install_component(new /obj/item/computer_hardware/sensorpackage)
+
+/obj/item/modular_computer/tablet/preset/advanced/command/Initialize()
+ . = ..()
+ install_component(new /obj/item/computer_hardware/sensorpackage)
+ install_component(new /obj/item/computer_hardware/card_slot/secondary)
+
+/// Given by the syndicate as part of the contract uplink bundle - loads in the Contractor Uplink.
+/obj/item/modular_computer/tablet/syndicate_contract_uplink/preset/uplink/Initialize()
. = ..()
var/obj/item/computer_hardware/hard_drive/small/syndicate/hard_drive = new
var/datum/computer_file/program/contract_uplink/uplink = new
+
active_program = uplink
uplink.program_state = PROGRAM_STATE_ACTIVE
uplink.computer = src
+
hard_drive.store_file(uplink)
+
install_component(new /obj/item/computer_hardware/processor_unit/small)
install_component(new /obj/item/computer_hardware/battery(src, /obj/item/stock_parts/cell/computer))
install_component(hard_drive)
install_component(new /obj/item/computer_hardware/network_card)
install_component(new /obj/item/computer_hardware/card_slot)
- install_component(new /obj/item/computer_hardware/printer/mini)
\ No newline at end of file
+ install_component(new /obj/item/computer_hardware/printer/mini)
+
+/// Given to Nuke Ops members.
+/obj/item/modular_computer/tablet/nukeops/Initialize()
+ . = ..()
+ install_component(new /obj/item/computer_hardware/processor_unit/small)
+ install_component(new /obj/item/computer_hardware/battery(src, /obj/item/stock_parts/cell/computer))
+ install_component(new /obj/item/computer_hardware/hard_drive/small/nukeops)
+ install_component(new /obj/item/computer_hardware/network_card)
diff --git a/code/modules/modular_computers/computers/machinery/console_presets.dm b/code/modules/modular_computers/computers/machinery/console_presets.dm
index 066f1fb98b..12b2f6d25a 100644
--- a/code/modules/modular_computers/computers/machinery/console_presets.dm
+++ b/code/modules/modular_computers/computers/machinery/console_presets.dm
@@ -1,6 +1,6 @@
/obj/machinery/modular_computer/console/preset
// Can be changed to give devices specific hardware
- var/_has_id_slot = FALSE
+ var/_has_second_id_slot = FALSE
var/_has_printer = FALSE
var/_has_battery = FALSE
var/_has_ai = FALSE
@@ -11,8 +11,9 @@
return
cpu.install_component(new /obj/item/computer_hardware/processor_unit)
- if(_has_id_slot)
- cpu.install_component(new /obj/item/computer_hardware/card_slot)
+ cpu.install_component(new /obj/item/computer_hardware/card_slot)
+ if(_has_second_id_slot)
+ cpu.install_component(new /obj/item/computer_hardware/card_slot/secondary)
if(_has_printer)
cpu.install_component(new /obj/item/computer_hardware/printer)
if(_has_battery)
@@ -46,16 +47,12 @@
desc = "A stationary computer. This one comes preloaded with research programs."
_has_ai = TRUE
-/obj/machinery/modular_computer/console/preset/research/examine(mob/user)
- . = ..()
- . += "Alt-click to eject the intelliCard."
-
/obj/machinery/modular_computer/console/preset/research/install_programs()
var/obj/item/computer_hardware/hard_drive/hard_drive = cpu.all_components[MC_HDD]
hard_drive.store_file(new/datum/computer_file/program/ntnetmonitor())
- hard_drive.store_file(new/datum/computer_file/program/nttransfer())
hard_drive.store_file(new/datum/computer_file/program/chatclient())
hard_drive.store_file(new/datum/computer_file/program/aidiag())
+ hard_drive.store_file(new/datum/computer_file/program/robocontrol())
// ===== COMMAND CONSOLE =====
@@ -63,18 +60,30 @@
console_department = "Command"
name = "command console"
desc = "A stationary computer. This one comes preloaded with command programs."
- _has_id_slot = TRUE
+ _has_second_id_slot = TRUE
_has_printer = TRUE
-/obj/machinery/modular_computer/console/preset/command/examine(mob/user)
- . = ..()
- . += "Alt-click [src] to eject the identification card."
-
/obj/machinery/modular_computer/console/preset/command/install_programs()
var/obj/item/computer_hardware/hard_drive/hard_drive = cpu.all_components[MC_HDD]
hard_drive.store_file(new/datum/computer_file/program/chatclient())
hard_drive.store_file(new/datum/computer_file/program/card_mod())
+
+// ===== IDENTIFICATION CONSOLE =====
+/obj/machinery/modular_computer/console/preset/id
+ console_department = "Identification"
+ name = "identification console"
+ desc = "A stationary computer. This one comes preloaded with identification modification programs."
+ _has_second_id_slot = TRUE
+ _has_printer = TRUE
+
+/obj/machinery/modular_computer/console/preset/id/install_programs()
+ var/obj/item/computer_hardware/hard_drive/hard_drive = cpu.all_components[MC_HDD]
+ hard_drive.store_file(new/datum/computer_file/program/chatclient())
+ hard_drive.store_file(new/datum/computer_file/program/card_mod())
+ hard_drive.store_file(new/datum/computer_file/program/job_management())
+ hard_drive.store_file(new/datum/computer_file/program/crew_manifest())
+
// ===== CIVILIAN CONSOLE =====
/obj/machinery/modular_computer/console/preset/civilian
console_department = "Civilian"
@@ -84,5 +93,4 @@
/obj/machinery/modular_computer/console/preset/civilian/install_programs()
var/obj/item/computer_hardware/hard_drive/hard_drive = cpu.all_components[MC_HDD]
hard_drive.store_file(new/datum/computer_file/program/chatclient())
- hard_drive.store_file(new/datum/computer_file/program/nttransfer())
hard_drive.store_file(new/datum/computer_file/program/arcade())
diff --git a/code/modules/modular_computers/computers/machinery/modular_computer.dm b/code/modules/modular_computers/computers/machinery/modular_computer.dm
index 7e384c3ddd..0e6f4d161a 100644
--- a/code/modules/modular_computers/computers/machinery/modular_computer.dm
+++ b/code/modules/modular_computers/computers/machinery/modular_computer.dm
@@ -36,6 +36,10 @@
QDEL_NULL(cpu)
return ..()
+/obj/machinery/modular_computer/examine(mob/user)
+ . = ..()
+ . += get_modular_computer_parts_examine(user)
+
/obj/machinery/modular_computer/attack_ghost(mob/dead/observer/user)
. = ..()
if(.)
@@ -45,61 +49,37 @@
/obj/machinery/modular_computer/emag_act(mob/user)
. = ..()
- if(cpu)
- . |= cpu.emag_act(user)
+ if(!cpu)
+ to_chat(user, "You'd need to turn the [src] on first.")
+ return FALSE
+ return (cpu.emag_act(user))
-/obj/machinery/modular_computer/update_icon_state()
- if(cpu?.enabled)
- icon_state = icon_state_powered
- else if(stat & NOPOWER || !(cpu?.use_power()))
- icon_state = icon_state_unpowered
+/obj/machinery/modular_computer/update_icon()
+ cut_overlays()
+ icon_state = icon_state_powered
-/obj/machinery/modular_computer/update_overlays()
- . = ..()
if(!cpu || !cpu.enabled)
if (!(stat & NOPOWER) && (cpu && cpu.use_power()))
- . += screen_icon_screensaver
+ add_overlay(screen_icon_screensaver)
+ else
+ icon_state = icon_state_unpowered
set_light(0)
else
set_light(light_strength)
if(cpu.active_program)
- . += cpu.active_program.program_icon_state ? cpu.active_program.program_icon_state : screen_icon_state_menu
+ add_overlay(cpu.active_program.program_icon_state ? cpu.active_program.program_icon_state : screen_icon_state_menu)
else
- . += screen_icon_state_menu
+ add_overlay(screen_icon_state_menu)
if(cpu && cpu.obj_integrity <= cpu.integrity_failure * cpu.max_integrity)
- . += "bsod"
- . += "broken"
-
-// Eject ID card from computer, if it has ID slot with card inside.
-/obj/machinery/modular_computer/proc/eject_id()
- set name = "Eject ID"
- set category = "Object"
-
- if(cpu)
- cpu.eject_id()
-
-// Eject ID card from computer, if it has ID slot with card inside.
-/obj/machinery/modular_computer/proc/eject_disk()
- set name = "Eject Data Disk"
- set category = "Object"
-
- if(cpu)
- cpu.eject_disk()
-
-/obj/machinery/modular_computer/proc/eject_card()
- set name = "Eject Intellicard"
- set category = "Object"
- set src in view(1)
-
- if(cpu)
- cpu.eject_card()
+ add_overlay("bsod")
+ add_overlay("broken")
/obj/machinery/modular_computer/AltClick(mob/user)
- . = ..()
if(cpu)
- return cpu.AltClick(user)
+ cpu.AltClick(user)
+//ATTACK HAND IGNORING PARENT RETURN VALUE
// On-click handling. Turns on the computer if it's off and opens the GUI.
/obj/machinery/modular_computer/interact(mob/user)
if(cpu)
@@ -130,10 +110,9 @@
stat &= ~NOPOWER
update_icon()
return
- ..()
- update_icon()
+ . = ..()
-/obj/machinery/modular_computer/attackby(var/obj/item/W as obj, mob/user)
+/obj/machinery/modular_computer/attackby(obj/item/W as obj, mob/user)
if(cpu && !(flags_1 & NODECONSTRUCT_1))
return cpu.attackby(W, user)
return ..()
@@ -144,6 +123,13 @@
/obj/machinery/modular_computer/ex_act(severity)
if(cpu)
cpu.ex_act(severity)
+ // switch(severity)
+ // if(EXPLODE_DEVASTATE)
+ // SSexplosions.highobj += cpu
+ // if(EXPLODE_HEAVY)
+ // SSexplosions.medobj += cpu
+ // if(EXPLODE_LIGHT)
+ // SSexplosions.lowobj += cpu
..()
// EMPs are similar to explosions, but don't cause physical damage to the casing. Instead they screw up the components
@@ -159,5 +145,4 @@
// "Brute" damage mostly damages the casing.
/obj/machinery/modular_computer/bullet_act(obj/item/projectile/Proj)
if(cpu)
- return cpu.bullet_act(Proj)
- return ..()
+ cpu.bullet_act(Proj)
diff --git a/code/modules/modular_computers/computers/machinery/modular_console.dm b/code/modules/modular_computers/computers/machinery/modular_console.dm
index 3d4ec22e89..5d596f98e4 100644
--- a/code/modules/modular_computers/computers/machinery/modular_console.dm
+++ b/code/modules/modular_computers/computers/machinery/modular_console.dm
@@ -52,4 +52,4 @@
network_card.identification_string = "Unknown Console"
if(cpu)
cpu.screen_on = 1
- update_icon()
\ No newline at end of file
+ update_icon()
diff --git a/code/modules/modular_computers/documentation.md b/code/modules/modular_computers/documentation.md
index 246da7c3d9..88d059da7a 100644
--- a/code/modules/modular_computers/documentation.md
+++ b/code/modules/modular_computers/documentation.md
@@ -1,5 +1,7 @@
# Modular computer programs
+How module computer programs work
+
Ok. so a quick rundown on how to make a program. This is kind of a shitty documentation, but oh well I was asked to.
## Base setup
diff --git a/code/modules/modular_computers/file_system/computer_file.dm b/code/modules/modular_computers/file_system/computer_file.dm
index 7776fc04d0..4e862c4ae3 100644
--- a/code/modules/modular_computers/file_system/computer_file.dm
+++ b/code/modules/modular_computers/file_system/computer_file.dm
@@ -3,8 +3,8 @@
var/filetype = "XXX" // File full names are [filename].[filetype] so like NewFile.XXX in this case
var/size = 1 // File size in GQ. Integers only!
var/obj/item/computer_hardware/hard_drive/holder // Holder that contains this file.
- var/unsendable = 0 // Whether the file may be sent to someone via NTNet transfer or other means.
- var/undeletable = 0 // Whether the file may be deleted. Setting to 1 prevents deletion/renaming/etc.
+ var/unsendable = FALSE // Whether the file may be sent to someone via NTNet transfer or other means.
+ var/undeletable = FALSE // Whether the file may be deleted. Setting to TRUE prevents deletion/renaming/etc.
var/uid // UID of this file
var/static/file_uid = 0
@@ -24,7 +24,7 @@
return ..()
// Returns independent copy of this file.
-/datum/computer_file/proc/clone(rename = 0)
+/datum/computer_file/proc/clone(rename = FALSE)
var/datum/computer_file/temp = new type
temp.unsendable = unsendable
temp.undeletable = undeletable
@@ -34,4 +34,4 @@
else
temp.filename = filename
temp.filetype = filetype
- return temp
\ No newline at end of file
+ return temp
diff --git a/code/modules/modular_computers/file_system/program.dm b/code/modules/modular_computers/file_system/program.dm
index 6b5390fc60..6d6a48d567 100644
--- a/code/modules/modular_computers/file_system/program.dm
+++ b/code/modules/modular_computers/file_system/program.dm
@@ -25,21 +25,14 @@
var/ntnet_status = 1
/// Bitflags (PROGRAM_CONSOLE, PROGRAM_LAPTOP, PROGRAM_TABLET combination) or PROGRAM_ALL
var/usage_flags = PROGRAM_ALL
- /// Optional string that describes what NTNet server/system this program connects to. Used in default logging.
- var/network_destination = null
/// Whether the program can be downloaded from NTNet. Set to 0 to disable.
var/available_on_ntnet = 1
/// Whether the program can be downloaded from SyndiNet (accessible via emagging the computer). Set to 1 to enable.
var/available_on_syndinet = 0
- /// ID of TGUI interface
+ /// Name of the tgui interface
var/tgui_id
- /// Default size of TGUI window, in pixels
- var/ui_x = 575
- var/ui_y = 700
/// Example: "something.gif" - a header image that will be rendered in computer's UI when this program is running at background. Images are taken from /icons/program_icons. Be careful not to use too large images!
var/ui_header = null
- ///Assets specific to programs
- var/list/special_assets = list()
/datum/computer_file/program/New(obj/item/modular_computer/comp = null)
..()
@@ -69,28 +62,36 @@
/datum/computer_file/program/proc/generate_network_log(text)
if(computer)
return computer.add_log(text)
- return FALSE
+ return 0
/datum/computer_file/program/proc/is_supported_by_hardware(hardware_flag = 0, loud = 0, mob/user = null)
if(!(hardware_flag & usage_flags))
if(loud && computer && user)
- to_chat(user, "\The [computer] flashes an \"Hardware Error - Incompatible software\" warning.")
- return FALSE
- return TRUE
+ to_chat(user, "\The [computer] flashes a \"Hardware Error - Incompatible software\" warning.")
+ return 0
+ return 1
/datum/computer_file/program/proc/get_signal(specific_action = 0)
if(computer)
return computer.get_ntnet_status(specific_action)
- return FALSE
+ return 0
// Called by Process() on device that runs us, once every tick.
/datum/computer_file/program/proc/process_tick()
- return TRUE
+ return 1
-// Check if the user can run program. Only humans can operate computer. Automatically called in run_program()
-// User has to wear their ID for ID Scan to work.
-// Can also be called manually, with optional parameter being access_to_check to scan the user's ID
-/datum/computer_file/program/proc/can_run(mob/user, loud = FALSE, access_to_check, transfer = FALSE)
+/**
+ *Check if the user can run program. Only humans can operate computer. Automatically called in run_program()
+ *ID must be inserted into a card slot to be read. If the program is not currently installed (as is the case when
+ *NT Software Hub is checking available software), a list can be given to be used instead.
+ *Arguments:
+ *user is a ref of the mob using the device.
+ *loud is a bool deciding if this proc should use to_chats
+ *access_to_check is an access level that will be checked against the ID
+ *transfer, if TRUE and access_to_check is null, will tell this proc to use the program's transfer_access in place of access_to_check
+ *access can contain a list of access numbers to check against. If access is not empty, it will be used istead of checking any inserted ID.
+*/
+/datum/computer_file/program/proc/can_run(mob/user, loud = FALSE, access_to_check, transfer = FALSE, var/list/access)
// Defaults to required_access
if(!access_to_check)
if(transfer && transfer_access)
@@ -109,28 +110,23 @@
if(issilicon(user))
return TRUE
- if(ishuman(user))
+ if(!length(access))
var/obj/item/card/id/D
var/obj/item/computer_hardware/card_slot/card_slot
- if(computer && card_slot)
+ if(computer)
card_slot = computer.all_components[MC_CARD]
- D = card_slot.GetID()
- var/mob/living/carbon/human/h = user
- var/obj/item/card/id/I = h.get_idcard(TRUE)
+ D = card_slot?.GetID()
- if(!I && !D)
+ if(!D)
if(loud)
to_chat(user, "\The [computer] flashes an \"RFID Error - Unable to scan ID\" warning.")
return FALSE
+ access = D.GetAccess()
- if(I)
- if(access_to_check in I.GetAccess())
- return TRUE
- else if(D)
- if(access_to_check in D.GetAccess())
- return TRUE
- if(loud)
- to_chat(user, "\The [computer] flashes an \"Access Denied\" warning.")
+ if(access_to_check in access)
+ return TRUE
+ if(loud)
+ to_chat(user, "\The [computer] flashes an \"Access Denied\" warning.")
return FALSE
// This attempts to retrieve header data for UIs. If implementing completely new device of different type than existing ones
@@ -143,32 +139,48 @@
// This is performed on program startup. May be overridden to add extra logic. Remember to include ..() call. Return 1 on success, 0 on failure.
// When implementing new program based device, use this to run the program.
/datum/computer_file/program/proc/run_program(mob/living/user)
- if(can_run(user, TRUE))
- if(requires_ntnet && network_destination)
- generate_network_log("Connection opened to [network_destination].")
+ if(can_run(user, 1))
+ if(requires_ntnet)
+ var/obj/item/card/id/ID
+ var/obj/item/computer_hardware/card_slot/card_holder = computer.all_components[MC_CARD]
+ if(card_holder)
+ ID = card_holder.GetID()
+ generate_network_log("Connection opened -- Program ID: [filename] User:[ID?"[ID.registered_name]":"None"]")
program_state = PROGRAM_STATE_ACTIVE
- return TRUE
+ return 1
+ return 0
+
+/**
+ *
+ *Called by the device when it is emagged.
+ *
+ *Emagging the device allows certain programs to unlock new functions. However, the program will
+ *need to be downloaded first, and then handle the unlock on their own in their run_emag() proc.
+ *The device will allow an emag to be run multiple times, so the user can re-emag to run the
+ *override again, should they download something new. The run_emag() proc should return TRUE if
+ *the emagging affected anything, and FALSE if no change was made (already emagged, or has no
+ *emag functions).
+**/
+/datum/computer_file/program/proc/run_emag()
return FALSE
// Use this proc to kill the program. Designed to be implemented by each program if it requires on-quit logic, such as the NTNRC client.
/datum/computer_file/program/proc/kill_program(forced = FALSE)
program_state = PROGRAM_STATE_KILLED
- if(network_destination)
- generate_network_log("Connection to [network_destination] closed.")
- return TRUE
+ if(requires_ntnet)
+ var/obj/item/card/id/ID
+ var/obj/item/computer_hardware/card_slot/card_holder = computer.all_components[MC_CARD]
+ if(card_holder)
+ ID = card_holder.GetID()
+ generate_network_log("Connection closed -- Program ID: [filename] User:[ID?"[ID.registered_name]":"None"]")
+ return 1
-
-/datum/computer_file/program/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state)
- ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+/datum/computer_file/program/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
if(!ui && tgui_id)
- var/datum/asset/assets = get_asset_datum(/datum/asset/simple/headers)
- assets.send(user)
- for(var/i in special_assets)
- assets = get_asset_datum(i)
- assets.send(user)
-
- ui = new(user, src, ui_key, tgui_id, filedesc, ui_x, ui_y, state = state)
+ ui = new(user, src, tgui_id, filedesc)
ui.open()
+ ui.send_asset(get_asset_datum(/datum/asset/simple/headers))
// CONVENTIONS, READ THIS WHEN CREATING NEW PROGRAM AND OVERRIDING THIS PROC:
// Topic calls are automagically forwarded from NanoModule this program contains.
@@ -177,17 +189,17 @@
// ALWAYS INCLUDE PARENT CALL ..() OR DIE IN FIRE.
/datum/computer_file/program/ui_act(action,list/params,datum/tgui/ui)
if(..())
- return TRUE
+ return 1
if(computer)
switch(action)
if("PC_exit")
computer.kill_program()
ui.close()
- return TRUE
+ return 1
if("PC_shutdown")
computer.shutdown_computer()
ui.close()
- return TRUE
+ return 1
if("PC_minimize")
var/mob/user = usr
if(!computer.active_program || !computer.all_components[MC_CPU])
diff --git a/code/modules/modular_computers/file_system/program_events.dm b/code/modules/modular_computers/file_system/program_events.dm
index 279d646cfd..1cb74a227b 100644
--- a/code/modules/modular_computers/file_system/program_events.dm
+++ b/code/modules/modular_computers/file_system/program_events.dm
@@ -2,7 +2,7 @@
// Always include a parent call when overriding an event.
// Called when the ID card is removed from computer. ID is removed AFTER this proc.
-/datum/computer_file/program/proc/event_idremoved(background, slot)
+/datum/computer_file/program/proc/event_idremoved(background)
return
// Called when the computer fails due to power loss. Override when program wants to specifically react to power loss.
@@ -13,6 +13,6 @@
/datum/computer_file/program/proc/event_networkfailure(background)
kill_program(forced = TRUE)
if(background)
- computer.visible_message("\The [computer]'s screen displays an \"Process [filename].[filetype] (PID [rand(100,999)]) terminated - Network Error\" error")
+ computer.visible_message("\The [computer]'s screen displays a \"Process [filename].[filetype] (PID [rand(100,999)]) terminated - Network Error\" error")
else
computer.visible_message("\The [computer]'s screen briefly freezes and then shows \"NETWORK ERROR - NTNet connection lost. Please retry. If problem persists contact your system administrator.\" error.")
diff --git a/code/modules/modular_computers/file_system/programs/airestorer.dm b/code/modules/modular_computers/file_system/programs/airestorer.dm
index 4e9cd85577..7ae6dd203a 100644
--- a/code/modules/modular_computers/file_system/programs/airestorer.dm
+++ b/code/modules/modular_computers/file_system/programs/airestorer.dm
@@ -1,16 +1,14 @@
/datum/computer_file/program/aidiag
filename = "aidiag"
- filedesc = "AI Integrity Restorer"
+ filedesc = "NT FRK"
program_icon_state = "generic"
- extended_desc = "This program is capable of reconstructing damaged AI systems. Requires direct AI connection via intellicard slot."
+ extended_desc = "Firmware Restoration Kit, capable of reconstructing damaged AI systems. Requires direct AI connection via intellicard slot."
size = 12
requires_ntnet = FALSE
usage_flags = PROGRAM_CONSOLE | PROGRAM_LAPTOP
transfer_access = ACCESS_HEADS
available_on_ntnet = TRUE
tgui_id = "NtosAiRestorer"
- ui_x = 370
- ui_y = 400
/// Variable dictating if we are in the process of restoring the AI in the inserted intellicard
var/restoring = FALSE
@@ -50,7 +48,7 @@
if(computer.all_components[MC_AI])
var/obj/item/computer_hardware/ai_slot/ai_slot = computer.all_components[MC_AI]
if(ai_slot && ai_slot.stored_card)
- ai_slot.try_eject(0,usr)
+ ai_slot.try_eject(usr)
return TRUE
/datum/computer_file/program/aidiag/process_tick()
@@ -73,14 +71,19 @@
ai_slot.locked = FALSE
restoring = FALSE
return
- ai_slot.locked =TRUE
- A.adjustOxyLoss(-5, 0)
- A.adjustFireLoss(-5, 0)
+ ai_slot.locked = TRUE
+ A.adjustOxyLoss(-5, 0)//, FALSE)
+ A.adjustFireLoss(-5, 0)//, FALSE)
A.adjustToxLoss(-5, 0)
A.adjustBruteLoss(-5, 0)
+
+ // Please don't forget to update health, otherwise the below if statements will probably always fail.
A.updatehealth()
+
if(A.health >= 0 && A.stat == DEAD)
A.revive(full_heal = FALSE, admin_revive = FALSE)
+ cardhold.update_icon()
+
// Finished restoring
if(A.health >= 100)
ai_slot.locked = FALSE
@@ -113,7 +116,7 @@
data["restoring"] = restoring
data["health"] = (AI.health + 100) / 2
data["isDead"] = AI.stat == DEAD
- data["laws"] = AI.laws.get_law_list(include_zeroth = 1)
+ data["laws"] = AI.laws.get_law_list(include_zeroth = TRUE, render_html = FALSE)
return data
diff --git a/code/modules/modular_computers/file_system/programs/alarm.dm b/code/modules/modular_computers/file_system/programs/alarm.dm
index 34daeff6ca..55dea600e3 100644
--- a/code/modules/modular_computers/file_system/programs/alarm.dm
+++ b/code/modules/modular_computers/file_system/programs/alarm.dm
@@ -1,16 +1,12 @@
/datum/computer_file/program/alarm_monitor
filename = "alarmmonitor"
- filedesc = "Alarm Monitor"
+ filedesc = "Canary"
ui_header = "alarm_green.gif"
program_icon_state = "alert-green"
- extended_desc = "This program provides visual interface for station's alarm system."
+ extended_desc = "This program provides visual interface for a station's alarm system."
requires_ntnet = 1
- network_destination = "alarm monitoring network"
size = 5
tgui_id = "NtosStationAlertConsole"
- ui_x = 315
- ui_y = 500
-
var/has_alert = 0
var/alarms = list("Fire" = list(), "Atmosphere" = list(), "Power" = list())
diff --git a/code/modules/modular_computers/file_system/programs/antagonist/contract_uplink.dm b/code/modules/modular_computers/file_system/programs/antagonist/contract_uplink.dm
index cf842f086f..aa361d4544 100644
--- a/code/modules/modular_computers/file_system/programs/antagonist/contract_uplink.dm
+++ b/code/modules/modular_computers/file_system/programs/antagonist/contract_uplink.dm
@@ -1,6 +1,6 @@
/datum/computer_file/program/contract_uplink
filename = "contractor uplink"
- filedesc = "Syndicate Contract Uplink"
+ filedesc = "Syndicate Contractor Uplink"
program_icon_state = "assign"
extended_desc = "A standard, Syndicate issued system for handling important contracts while on the field."
size = 10
@@ -9,27 +9,29 @@
unsendable = 1
undeletable = 1
tgui_id = "SyndContractor"
- ui_x = 500
- ui_y = 600
var/error = ""
var/info_screen = TRUE
var/assigned = FALSE
var/first_load = TRUE
-/datum/computer_file/program/contract_uplink/run_program(var/mob/living/user)
+/datum/computer_file/program/contract_uplink/run_program(mob/living/user)
. = ..(user)
/datum/computer_file/program/contract_uplink/ui_act(action, params)
if(..())
return TRUE
+
var/mob/living/user = usr
var/obj/item/computer_hardware/hard_drive/small/syndicate/hard_drive = computer.all_components[MC_HDD]
+
switch(action)
if("PRG_contract-accept")
var/contract_id = text2num(params["contract_id"])
+
// Set as the active contract
hard_drive.traitor_data.contractor_hub.assigned_contracts[contract_id].status = CONTRACT_STATUS_ACTIVE
hard_drive.traitor_data.contractor_hub.current_contract = hard_drive.traitor_data.contractor_hub.assigned_contracts[contract_id]
+
program_icon_state = "single_contract"
return TRUE
if("PRG_login")
@@ -38,26 +40,30 @@
// Bake their data right into the hard drive, or we don't allow non-antags gaining access to an unused
// contract system.
// We also create their contracts at this point.
- if(traitor_data)
+ if (traitor_data)
// Only play greet sound, and handle contractor hub when assigning for the first time.
- if(!traitor_data.contractor_hub)
+ if (!traitor_data.contractor_hub)
user.playsound_local(user, 'sound/effects/contractstartup.ogg', 100, FALSE)
traitor_data.contractor_hub = new
traitor_data.contractor_hub.create_hub_items()
+
// Stops any topic exploits such as logging in multiple times on a single system.
- if(!assigned)
+ if (!assigned)
traitor_data.contractor_hub.create_contracts(traitor_data.owner)
+
hard_drive.traitor_data = traitor_data
+
program_icon_state = "contracts"
assigned = TRUE
else
error = "UNAUTHORIZED USER"
return TRUE
if("PRG_call_extraction")
- if(hard_drive.traitor_data.contractor_hub.current_contract.status != CONTRACT_STATUS_EXTRACTING)
- if(hard_drive.traitor_data.contractor_hub.current_contract.handle_extraction(user))
+ if (hard_drive.traitor_data.contractor_hub.current_contract.status != CONTRACT_STATUS_EXTRACTING)
+ if (hard_drive.traitor_data.contractor_hub.current_contract.handle_extraction(user))
user.playsound_local(user, 'sound/effects/confirmdropoff.ogg', 100, TRUE)
hard_drive.traitor_data.contractor_hub.current_contract.status = CONTRACT_STATUS_EXTRACTING
+
program_icon_state = "extracted"
else
user.playsound_local(user, 'sound/machines/uplinkerror.ogg', 50)
@@ -65,15 +71,19 @@
else
user.playsound_local(user, 'sound/machines/uplinkerror.ogg', 50)
error = "Already extracting... Place the target into the pod. If the pod was destroyed, this contract is no longer possible."
+
return TRUE
if("PRG_contract_abort")
var/contract_id = hard_drive.traitor_data.contractor_hub.current_contract.id
+
hard_drive.traitor_data.contractor_hub.current_contract = null
hard_drive.traitor_data.contractor_hub.assigned_contracts[contract_id].status = CONTRACT_STATUS_ABORTED
+
program_icon_state = "contracts"
+
return TRUE
if("PRG_redeem_TC")
- if(hard_drive.traitor_data.contractor_hub.contract_TC_to_redeem)
+ if (hard_drive.traitor_data.contractor_hub.contract_TC_to_redeem)
var/obj/item/stack/telecrystal/crystals = new /obj/item/stack/telecrystal(get_turf(user),
hard_drive.traitor_data.contractor_hub.contract_TC_to_redeem)
if(ishuman(user))
@@ -82,13 +92,14 @@
to_chat(H, "Your payment materializes into your hands!")
else
to_chat(user, "Your payment materializes onto the floor.")
+
hard_drive.traitor_data.contractor_hub.contract_TC_payed_out += hard_drive.traitor_data.contractor_hub.contract_TC_to_redeem
hard_drive.traitor_data.contractor_hub.contract_TC_to_redeem = 0
return TRUE
else
user.playsound_local(user, 'sound/machines/uplinkerror.ogg', 50)
return TRUE
- if("PRG_clear_error")
+ if ("PRG_clear_error")
error = ""
return TRUE
if("PRG_set_first_load_finished")
@@ -97,10 +108,11 @@
if("PRG_toggle_info")
info_screen = !info_screen
return TRUE
- if("buy_hub")
- if(hard_drive.traitor_data.owner.current == user)
+ if ("buy_hub")
+ if (hard_drive.traitor_data.owner.current == user)
var/item = params["item"]
- for(var/datum/contractor_item/hub_item in hard_drive.traitor_data.contractor_hub.hub_items)
+
+ for (var/datum/contractor_item/hub_item in hard_drive.traitor_data.contractor_hub.hub_items)
if (hub_item.name == item)
hub_item.handle_purchase(hard_drive.traitor_data.contractor_hub, user)
else
@@ -112,13 +124,15 @@
var/screen_to_be = null
data["first_load"] = first_load
- if(hard_drive && hard_drive.traitor_data != null)
+
+ if (hard_drive && hard_drive.traitor_data != null)
var/datum/antagonist/traitor/traitor_data = hard_drive.traitor_data
data += get_header_data()
- if(traitor_data.contractor_hub.current_contract)
+
+ if (traitor_data.contractor_hub.current_contract)
data["ongoing_contract"] = TRUE
screen_to_be = "single_contract"
- if(traitor_data.contractor_hub.current_contract.status == CONTRACT_STATUS_EXTRACTING)
+ if (traitor_data.contractor_hub.current_contract.status == CONTRACT_STATUS_EXTRACTING)
data["extraction_enroute"] = TRUE
screen_to_be = "extracted"
else
@@ -126,15 +140,19 @@
else
data["ongoing_contract"] = FALSE
data["extraction_enroute"] = FALSE
+
data["logged_in"] = TRUE
data["station_name"] = GLOB.station_name
data["redeemable_tc"] = traitor_data.contractor_hub.contract_TC_to_redeem
data["earned_tc"] = traitor_data.contractor_hub.contract_TC_payed_out
data["contracts_completed"] = traitor_data.contractor_hub.contracts_completed
data["contract_rep"] = traitor_data.contractor_hub.contract_rep
+
data["info_screen"] = info_screen
+
data["error"] = error
- for(var/datum/contractor_item/hub_item in traitor_data.contractor_hub.hub_items)
+
+ for (var/datum/contractor_item/hub_item in traitor_data.contractor_hub.hub_items)
data["contractor_hub_items"] += list(list(
"name" = hub_item.name,
"desc" = hub_item.desc,
@@ -142,7 +160,8 @@
"limited" = hub_item.limited,
"item_icon" = hub_item.item_icon
))
- for(var/datum/syndicate_contract/contract in traitor_data.contractor_hub.assigned_contracts)
+
+ for (var/datum/syndicate_contract/contract in traitor_data.contractor_hub.assigned_contracts)
data["contracts"] += list(list(
"target" = contract.contract.target,
"target_rank" = contract.target_rank,
@@ -155,23 +174,28 @@
))
var/direction
- if(traitor_data.contractor_hub.current_contract)
+ if (traitor_data.contractor_hub.current_contract)
var/turf/curr = get_turf(user)
var/turf/dropoff_turf
data["current_location"] = "[get_area_name(curr, TRUE)]"
- for(var/turf/content in traitor_data.contractor_hub.current_contract.contract.dropoff.contents)
- if(isturf(content))
+
+ for (var/turf/content in traitor_data.contractor_hub.current_contract.contract.dropoff.contents)
+ if (isturf(content))
dropoff_turf = content
break
+
if(curr.z == dropoff_turf.z) //Direction calculations for same z-level only
direction = uppertext(dir2text(get_dir(curr, dropoff_turf))) //Direction text (East, etc). Not as precise, but still helpful.
if(get_area(user) == traitor_data.contractor_hub.current_contract.contract.dropoff)
direction = "LOCATION CONFIRMED"
else
direction = "???"
+
data["dropoff_direction"] = direction
+
else
data["logged_in"] = FALSE
+
program_icon_state = screen_to_be
update_computer_icon()
return data
diff --git a/code/modules/modular_computers/file_system/programs/antagonist/dos.dm b/code/modules/modular_computers/file_system/programs/antagonist/dos.dm
index 9dedc3810f..803dadc0a0 100644
--- a/code/modules/modular_computers/file_system/programs/antagonist/dos.dm
+++ b/code/modules/modular_computers/file_system/programs/antagonist/dos.dm
@@ -8,8 +8,6 @@
available_on_ntnet = FALSE
available_on_syndinet = TRUE
tgui_id = "NtosNetDos"
- ui_x = 400
- ui_y = 250
var/obj/machinery/ntnet_relay/target = null
var/dos_speed = 0
diff --git a/code/modules/modular_computers/file_system/programs/antagonist/revelation.dm b/code/modules/modular_computers/file_system/programs/antagonist/revelation.dm
index a312815008..4f1c488b9e 100644
--- a/code/modules/modular_computers/file_system/programs/antagonist/revelation.dm
+++ b/code/modules/modular_computers/file_system/programs/antagonist/revelation.dm
@@ -8,12 +8,9 @@
available_on_ntnet = FALSE
available_on_syndinet = TRUE
tgui_id = "NtosRevelation"
- ui_x = 400
- ui_y = 250
-
var/armed = 0
-/datum/computer_file/program/revelation/run_program(var/mob/living/user)
+/datum/computer_file/program/revelation/run_program(mob/living/user)
. = ..(user)
if(armed)
activate()
diff --git a/code/modules/modular_computers/file_system/programs/arcade.dm b/code/modules/modular_computers/file_system/programs/arcade.dm
index 87debafd6b..87a3f1ec94 100644
--- a/code/modules/modular_computers/file_system/programs/arcade.dm
+++ b/code/modules/modular_computers/file_system/programs/arcade.dm
@@ -1,14 +1,11 @@
/datum/computer_file/program/arcade
- filename = "arcade"
- filedesc = "Nanotrasen Micro Arcade"
+ filename = "dsarcade"
+ filedesc = "Donksoft Micro Arcade"
program_icon_state = "arcade"
extended_desc = "This port of the classic game 'Outbomb Cuban Pete', redesigned to run on tablets, with thrilling graphics and chilling storytelling."
requires_ntnet = FALSE
- network_destination = "arcade network"
size = 6
tgui_id = "NtosArcade"
- ui_x = 450
- ui_y = 350
///Returns TRUE if the game is being played.
var/game_active = TRUE
@@ -27,6 +24,7 @@
/datum/computer_file/program/arcade/proc/game_check(mob/user)
sleep(5)
+ // user?.mind?.adjust_experience(/datum/skill/gaming, 1)
if(boss_hp <= 0)
heads_up = "You have crushed [boss_name]! Rejoice!"
playsound(computer.loc, 'sound/arcade/win.ogg', 50, TRUE, extrarange = -3, falloff = 10)
@@ -35,6 +33,7 @@
if(istype(computer))
computer.update_icon()
ticket_count += 1
+ // user?.mind?.adjust_experience(/datum/skill/gaming, 50)
sleep(10)
else if(player_hp <= 0 || player_mp <= 0)
heads_up = "You have been defeated... how will the station survive?"
@@ -43,6 +42,7 @@
program_icon_state = "arcade_off"
if(istype(computer))
computer.update_icon()
+ // user?.mind?.adjust_experience(/datum/skill/gaming, 10)
sleep(10)
/datum/computer_file/program/arcade/proc/enemy_check(mob/user)
@@ -73,14 +73,13 @@
pause_state = FALSE
game_check()
-/datum/computer_file/program/arcade/ui_interact(mob/user, ui_key, datum/tgui/ui, force_open, datum/tgui/master_ui, datum/ui_state/state)
- . = ..()
- var/datum/asset/assets = get_asset_datum(/datum/asset/simple/arcade)
- assets.send(user)
+/datum/computer_file/program/arcade/ui_assets(mob/user)
+ return list(
+ get_asset_datum(/datum/asset/simple/arcade),
+ )
/datum/computer_file/program/arcade/ui_data(mob/user)
var/list/data = get_header_data()
-
data["Hitpoints"] = boss_hp
data["PlayerHitpoints"] = player_hp
data["PlayerMP"] = player_mp
@@ -98,11 +97,13 @@
if(computer)
printer = computer.all_components[MC_PRINT]
+ // var/gamerSkillLevel = usr.mind?.get_skill_level(/datum/skill/gaming)
+ // var/gamerSkill = usr.mind?.get_skill_modifier(/datum/skill/gaming, SKILL_RANDS_MODIFIER)
switch(action)
if("Attack")
var/attackamt = 0 //Spam prevention.
if(pause_state == FALSE)
- attackamt = rand(2,6)
+ attackamt = rand(2,6)// + rand(0, gamerSkill)
pause_state = TRUE
heads_up = "You attack for [attackamt] damage."
playsound(computer.loc, 'sound/arcade/hit.ogg', 50, TRUE, extrarange = -3, falloff = 10)
@@ -115,8 +116,10 @@
var/healamt = 0 //More Spam Prevention.
var/healcost = 0
if(pause_state == FALSE)
- healamt = rand(6,8)
+ healamt = rand(6,8)// + rand(0, gamerSkill)
var/maxPointCost = 3
+ // if(gamerSkillLevel >= SKILL_LEVEL_JOURNEYMAN)
+ // maxPointCost = 2
healcost = rand(1, maxPointCost)
pause_state = TRUE
heads_up = "You heal for [healamt] damage."
@@ -130,7 +133,7 @@
if("Recharge_Power")
var/rechargeamt = 0 //As above.
if(pause_state == FALSE)
- rechargeamt = rand(4, 7)
+ rechargeamt = rand(4,7)// + rand(0, gamerSkill)
pause_state = TRUE
heads_up = "You regain [rechargeamt] magic power."
playsound(computer.loc, 'sound/arcade/mana.ogg', 50, TRUE, extrarange = -3, falloff = 10)
diff --git a/code/modules/modular_computers/file_system/programs/atmosscan.dm b/code/modules/modular_computers/file_system/programs/atmosscan.dm
index fe3833facd..c4b9951838 100644
--- a/code/modules/modular_computers/file_system/programs/atmosscan.dm
+++ b/code/modules/modular_computers/file_system/programs/atmosscan.dm
@@ -1,31 +1,41 @@
/datum/computer_file/program/atmosscan
filename = "atmosscan"
- filedesc = "Atmospheric Scanner"
+ filedesc = "AtmoZphere"
program_icon_state = "air"
extended_desc = "A small built-in sensor reads out the atmospheric conditions around the device."
- network_destination = "atmos scan"
size = 4
tgui_id = "NtosAtmos"
- ui_x = 300
- ui_y = 350
+
+/datum/computer_file/program/atmosscan/run_program(mob/living/user)
+ . = ..()
+ if (!.)
+ return
+ if(!computer?.get_modular_computer_part(MC_SENSORS)) //Giving a clue to users why the program is spitting out zeros.
+ to_chat(user, "\The [computer] flashes an error: \"hardware\\sensorpackage\\startup.bin -- file not found\".")
+
/datum/computer_file/program/atmosscan/ui_data(mob/user)
var/list/data = get_header_data()
var/list/airlist = list()
var/turf/T = get_turf(ui_host())
- if(T)
+ var/obj/item/computer_hardware/sensorpackage/sensors = computer?.get_modular_computer_part(MC_SENSORS)
+ if(T && sensors?.check_functionality())
var/datum/gas_mixture/environment = T.return_air()
- var/list/env_gases = environment.gases
+ var/list/env_gases = environment.get_gases()
var/pressure = environment.return_pressure()
var/total_moles = environment.total_moles()
data["AirPressure"] = round(pressure,0.1)
- data["AirTemp"] = round(environment.temperature-T0C)
+ data["AirTemp"] = round(environment.return_temperature()-T0C)
if (total_moles)
for(var/id in env_gases)
- var/gas_level = env_gases[id][MOLES]/total_moles
+ var/gas_level = environment.get_moles(id)/total_moles
if(gas_level > 0)
- airlist += list(list("name" = "[env_gases[id][GAS_META][META_GAS_NAME]]", "percentage" = round(gas_level*100, 0.01)))
+ airlist += list(list("name" = "[GLOB.meta_gas_names[id]]", "percentage" = round(gas_level*100, 0.01)))
data["AirData"] = airlist
+ else
+ data["AirPressure"] = 0
+ data["AirTemp"] = 0
+ data["AirData"] = list(list())
return data
/datum/computer_file/program/atmosscan/ui_act(action, list/params)
diff --git a/code/modules/modular_computers/file_system/programs/borg_monitor.dm b/code/modules/modular_computers/file_system/programs/borg_monitor.dm
index c493926c65..13caab27ef 100644
--- a/code/modules/modular_computers/file_system/programs/borg_monitor.dm
+++ b/code/modules/modular_computers/file_system/programs/borg_monitor.dm
@@ -1,29 +1,24 @@
/datum/computer_file/program/borg_monitor
- filename = "cyborgmonitor"
- filedesc = "Cyborg Remote Monitoring"
+ filename = "siliconnect"
+ filedesc = "SiliConnect"
ui_header = "borg_mon.gif"
program_icon_state = "generic"
extended_desc = "This program allows for remote monitoring of station cyborgs."
requires_ntnet = TRUE
transfer_access = ACCESS_ROBOTICS
- network_destination = "cyborg remote monitoring"
size = 5
tgui_id = "NtosCyborgRemoteMonitor"
- ui_x = 600
- ui_y = 800
/datum/computer_file/program/borg_monitor/ui_data(mob/user)
var/list/data = get_header_data()
data["card"] = FALSE
- if(computer.GetID())
+ if(checkID())
data["card"] = TRUE
data["cyborgs"] = list()
for(var/mob/living/silicon/robot/R in GLOB.silicon_mobs)
- if((get_turf(computer)).z != (get_turf(R)).z)
- continue
- if(R.scrambledcodes)
+ if(!evaluate_borg(R))
continue
var/list/upgrade
@@ -56,14 +51,52 @@
var/mob/living/silicon/robot/R = locate(params["ref"]) in GLOB.silicon_mobs
if(!istype(R))
return
- var/obj/item/card/id/ID = computer.GetID()
+ var/ID = checkID()
if(!ID)
return
var/message = stripped_input(usr, message = "Enter message to be sent to remote cyborg.", title = "Send Message")
if(!message)
return
- to_chat(R, "
Message from [ID.registered_name] -- \"[message]\" ")
+ to_chat(R, "
- [GLOB.data_core ? GLOB.data_core.get_manifest_html(0) : ""]
+ [GLOB.data_core ? GLOB.data_core.get_manifest() : ""]
"}
- if(!printer.print_text(contents,text("crew manifest ([])", station_time_timestamp())))
+ if(!printer.print_text(contents,text("crew manifest ([])", STATION_TIME_TIMESTAMP("hh:mm:ss", world.time))))
to_chat(usr, "Hardware error: Printer was unable to print the file. It may be out of paper.")
return
else
diff --git a/code/modules/modular_computers/file_system/programs/jobmanagement.dm b/code/modules/modular_computers/file_system/programs/jobmanagement.dm
index 7b847c123e..b88b793b66 100644
--- a/code/modules/modular_computers/file_system/programs/jobmanagement.dm
+++ b/code/modules/modular_computers/file_system/programs/jobmanagement.dm
@@ -1,14 +1,12 @@
/datum/computer_file/program/job_management
- filename = "job_manage"
- filedesc = "Job Manager"
+ filename = "plexagoncore"
+ filedesc = "Plexagon HR Core"
program_icon_state = "id"
extended_desc = "Program for viewing and changing job slot avalibility."
transfer_access = ACCESS_HEADS
- requires_ntnet = 0
+ requires_ntnet = TRUE
size = 4
tgui_id = "NtosJobManager"
- ui_x = 400
- ui_y = 620
var/change_position_cooldown = 30
//Jobs you cannot open new positions for
diff --git a/code/modules/modular_computers/file_system/programs/ntdownloader.dm b/code/modules/modular_computers/file_system/programs/ntdownloader.dm
index 352f13f305..8fbcfd0b01 100644
--- a/code/modules/modular_computers/file_system/programs/ntdownloader.dm
+++ b/code/modules/modular_computers/file_system/programs/ntdownloader.dm
@@ -1,18 +1,16 @@
/datum/computer_file/program/ntnetdownload
- filename = "ntndownloader"
- filedesc = "Software Download Tool"
+ filename = "ntsoftwarehub"
+ filedesc = "NT Software Hub"
program_icon_state = "generic"
extended_desc = "This program allows downloads of software from official NT repositories"
- unsendable = 1
- undeletable = 1
+ unsendable = TRUE
+ undeletable = TRUE
size = 4
- requires_ntnet = 1
+ requires_ntnet = TRUE
requires_ntnet_feature = NTNET_SOFTWAREDOWNLOAD
- available_on_ntnet = 0
+ available_on_ntnet = FALSE
ui_header = "downloader_finished.gif"
tgui_id = "NtosNetDownloader"
- ui_x = 480
- ui_y = 735
var/datum/computer_file/program/downloaded_file = null
var/hacked_download = 0
@@ -20,6 +18,21 @@
var/download_netspeed = 0
var/downloaderror = ""
var/obj/item/modular_computer/my_computer = null
+ var/emagged = FALSE
+ var/list/main_repo
+ var/list/antag_repo
+
+/datum/computer_file/program/ntnetdownload/run_program()
+ . = ..()
+ main_repo = SSnetworks.station_network.available_station_software
+ antag_repo = SSnetworks.station_network.available_antag_software
+
+/datum/computer_file/program/ntnetdownload/run_emag()
+ if(emagged)
+ return FALSE
+ emagged = TRUE
+ return TRUE
+
/datum/computer_file/program/ntnetdownload/proc/begin_file_download(filename)
if(downloaded_file)
@@ -30,8 +43,8 @@
if(!PRG || !istype(PRG))
return 0
- // Attempting to download antag only program, but without having emagged computer. No.
- if(PRG.available_on_syndinet && !(computer.obj_flags & EMAGGED))
+ // Attempting to download antag only program, but without having emagged/syndicate computer. No.
+ if(PRG.available_on_syndinet && !emagged)
return 0
var/obj/item/computer_hardware/hard_drive/hard_drive = computer.all_components[MC_HDD]
@@ -41,10 +54,10 @@
ui_header = "downloader_running.gif"
- if(PRG in SSnetworks.station_network.available_station_software)
+ if(PRG in main_repo)
generate_network_log("Began downloading file [PRG.filename].[PRG.filetype] from NTNet Software Repository.")
hacked_download = 0
- else if(PRG in SSnetworks.station_network.available_antag_software)
+ else if(PRG in antag_repo)
generate_network_log("Began downloading file **ENCRYPTED**.[PRG.filetype] from unspecified server.")
hacked_download = 1
else
@@ -112,6 +125,8 @@
if(!istype(my_computer))
return
+ var/obj/item/computer_hardware/card_slot/card_slot = computer.all_components[MC_CARD]
+ var/list/access = card_slot?.GetAccess()
var/list/data = get_header_data()
@@ -130,10 +145,10 @@
data["disk_size"] = hard_drive.max_capacity
data["disk_used"] = hard_drive.used_capacity
var/list/all_entries[0]
- for(var/A in SSnetworks.station_network.available_station_software)
+ for(var/A in main_repo)
var/datum/computer_file/program/P = A
// Only those programs our user can run will show in the list
- if(!P.can_run(user,transfer = 1) || hard_drive.find_file_by_name(P.filename))
+ if(!P.can_run(user,transfer = 1, access = access) || hard_drive.find_file_by_name(P.filename))
continue
all_entries.Add(list(list(
"filename" = P.filename,
@@ -143,9 +158,9 @@
"size" = P.size,
)))
data["hackedavailable"] = FALSE
- if(computer.obj_flags & EMAGGED) // If we are running on emagged computer we have access to some "bonus" software
+ if(emagged) // If we are running on emagged computer we have access to some "bonus" software
var/list/hacked_programs[0]
- for(var/S in SSnetworks.station_network.available_antag_software)
+ for(var/S in antag_repo)
var/datum/computer_file/program/P = S
if(hard_drive.find_file_by_name(P.filename))
continue
@@ -172,3 +187,24 @@
/datum/computer_file/program/ntnetdownload/kill_program(forced)
abort_file_download()
return ..(forced)
+
+////////////////////////
+//Syndicate Downloader//
+////////////////////////
+
+/// This app only lists programs normally found in the emagged section of the normal downloader app
+
+/datum/computer_file/program/ntnetdownload/syndicate
+ filename = "syndownloader"
+ filedesc = "Software Download Tool"
+ program_icon_state = "generic"
+ extended_desc = "This program allows downloads of software from shared Syndicate repositories"
+ requires_ntnet = 0
+ ui_header = "downloader_finished.gif"
+ tgui_id = "NtosNetDownloader"
+ emagged = TRUE
+
+/datum/computer_file/program/ntnetdownload/syndicate/run_program()
+ . = ..()
+ main_repo = SSnetworks.station_network.available_antag_software
+ antag_repo = null
diff --git a/code/modules/modular_computers/file_system/programs/ntmonitor.dm b/code/modules/modular_computers/file_system/programs/ntmonitor.dm
index 7d6d89f32c..bbbde14780 100644
--- a/code/modules/modular_computers/file_system/programs/ntmonitor.dm
+++ b/code/modules/modular_computers/file_system/programs/ntmonitor.dm
@@ -1,6 +1,6 @@
/datum/computer_file/program/ntnetmonitor
- filename = "ntmonitor"
- filedesc = "NTNet Diagnostics and Monitoring"
+ filename = "wirecarp"
+ filedesc = "WireCarp" //wireshark.
program_icon_state = "comm_monitor"
extended_desc = "This program monitors stationwide NTNet network, provides access to logging systems, and allows for configuration changes"
size = 12
diff --git a/code/modules/modular_computers/file_system/programs/ntnrc_client.dm b/code/modules/modular_computers/file_system/programs/ntnrc_client.dm
index 4ae5bd326b..f03ff3f8fd 100644
--- a/code/modules/modular_computers/file_system/programs/ntnrc_client.dm
+++ b/code/modules/modular_computers/file_system/programs/ntnrc_client.dm
@@ -6,13 +6,9 @@
size = 8
requires_ntnet = 1
requires_ntnet_feature = NTNET_COMMUNICATION
- network_destination = "NTNRC server"
ui_header = "ntnrc_idle.gif"
available_on_ntnet = 1
tgui_id = "NtosNetChat"
- ui_x = 900
- ui_y = 675
-
var/last_message // Used to generate the toolbar icon
var/username
var/active_channel
diff --git a/code/modules/modular_computers/file_system/programs/nttransfer.dm b/code/modules/modular_computers/file_system/programs/nttransfer.dm
deleted file mode 100644
index 698e557941..0000000000
--- a/code/modules/modular_computers/file_system/programs/nttransfer.dm
+++ /dev/null
@@ -1,183 +0,0 @@
-/datum/computer_file/program/nttransfer
- filename = "nttransfer"
- filedesc = "P2P Transfer Client"
- extended_desc = "This program allows for simple file transfer via direct peer to peer connection."
- program_icon_state = "comm_logs"
- size = 7
- requires_ntnet = 1
- requires_ntnet_feature = NTNET_PEERTOPEER
- network_destination = "other device via P2P tunnel"
- available_on_ntnet = 1
- tgui_id = "ntos_net_transfer"
-
- var/error = "" // Error screen
- var/server_password = "" // Optional password to download the file.
- var/datum/computer_file/provided_file = null // File which is provided to clients.
- var/datum/computer_file/downloaded_file = null // File which is being downloaded
- var/list/connected_clients = list() // List of connected clients.
- var/datum/computer_file/program/nttransfer/remote // Client var, specifies who are we downloading from.
- var/download_completion = 0 // Download progress in GQ
- var/download_netspeed = 0 // Our connectivity speed in GQ/s
- var/actual_netspeed = 0 // Displayed in the UI, this is the actual transfer speed.
- var/unique_token // UID of this program
- var/upload_menu = 0 // Whether we show the program list and upload menu
- var/static/nttransfer_uid = 0
-
-/datum/computer_file/program/nttransfer/New()
- unique_token = nttransfer_uid++
- ..()
-
-/datum/computer_file/program/nttransfer/process_tick()
- // Server mode
- update_netspeed()
- if(provided_file)
- for(var/datum/computer_file/program/nttransfer/C in connected_clients)
- // Transfer speed is limited by device which uses slower connectivity.
- // We can have multiple clients downloading at same time, but let's assume we use some sort of multicast transfer
- // so they can all run on same speed.
- C.actual_netspeed = min(C.download_netspeed, download_netspeed)
- C.download_completion += C.actual_netspeed
- if(C.download_completion >= provided_file.size)
- C.finish_download()
- else if(downloaded_file) // Client mode
- if(!remote)
- crash_download("Connection to remote server lost")
-
-/datum/computer_file/program/nttransfer/kill_program(forced = FALSE)
- if(downloaded_file) // Client mode, clean up variables for next use
- finalize_download()
-
- if(provided_file) // Server mode, disconnect all clients
- for(var/datum/computer_file/program/nttransfer/P in connected_clients)
- P.crash_download("Connection terminated by remote server")
- downloaded_file = null
- ..(forced)
-
-/datum/computer_file/program/nttransfer/proc/update_netspeed()
- download_netspeed = 0
- switch(ntnet_status)
- if(1)
- download_netspeed = NTNETSPEED_LOWSIGNAL
- if(2)
- download_netspeed = NTNETSPEED_HIGHSIGNAL
- if(3)
- download_netspeed = NTNETSPEED_ETHERNET
-
-// Finishes download and attempts to store the file on HDD
-/datum/computer_file/program/nttransfer/proc/finish_download()
- var/obj/item/computer_hardware/hard_drive/hard_drive = computer.all_components[MC_HDD]
- if(!computer || !hard_drive || !hard_drive.store_file(downloaded_file))
- error = "I/O Error: Unable to save file. Check your hard drive and try again."
- finalize_download()
-
-// Crashes the download and displays specific error message
-/datum/computer_file/program/nttransfer/proc/crash_download(var/message)
- error = message ? message : "An unknown error has occurred during download"
- finalize_download()
-
-// Cleans up variables for next use
-/datum/computer_file/program/nttransfer/proc/finalize_download()
- if(remote)
- remote.connected_clients.Remove(src)
- downloaded_file = null
- remote = null
- download_completion = 0
-
-/datum/computer_file/program/nttransfer/ui_act(action, params)
- if(..())
- return 1
- switch(action)
- if("PRG_downloadfile")
- for(var/datum/computer_file/program/nttransfer/P in SSnetworks.station_network.fileservers)
- if("[P.unique_token]" == params["id"])
- remote = P
- break
- if(!remote || !remote.provided_file)
- return
- if(remote.server_password)
- var/pass = reject_bad_text(input(usr, "Code 401 Unauthorized. Please enter password:", "Password required"))
- if(pass != remote.server_password)
- error = "Incorrect Password"
- return
- downloaded_file = remote.provided_file.clone()
- remote.connected_clients.Add(src)
- return 1
- if("PRG_reset")
- error = ""
- upload_menu = 0
- finalize_download()
- if(src in SSnetworks.station_network.fileservers)
- SSnetworks.station_network.fileservers.Remove(src)
- for(var/datum/computer_file/program/nttransfer/T in connected_clients)
- T.crash_download("Remote server has forcibly closed the connection")
- provided_file = null
- return 1
- if("PRG_setpassword")
- var/pass = reject_bad_text(input(usr, "Enter new server password. Leave blank to cancel, input 'none' to disable password.", "Server security", "none"))
- if(!pass)
- return
- if(pass == "none")
- server_password = ""
- return
- server_password = pass
- return 1
- if("PRG_uploadfile")
- var/obj/item/computer_hardware/hard_drive/hard_drive = computer.all_components[MC_HDD]
- for(var/datum/computer_file/F in hard_drive.stored_files)
- if("[F.uid]" == params["id"])
- if(F.unsendable)
- error = "I/O Error: File locked."
- return
- if(istype(F, /datum/computer_file/program))
- var/datum/computer_file/program/P = F
- if(!P.can_run(usr,transfer = 1))
- error = "Access Error: Insufficient rights to upload file."
- provided_file = F
- SSnetworks.station_network.fileservers.Add(src)
- return
- error = "I/O Error: Unable to locate file on hard drive."
- return 1
- if("PRG_uploadmenu")
- upload_menu = 1
-
-
-/datum/computer_file/program/nttransfer/ui_data(mob/user)
-
- var/list/data = get_header_data()
-
- if(error)
- data["error"] = error
- else if(downloaded_file)
- data["downloading"] = 1
- data["download_size"] = downloaded_file.size
- data["download_progress"] = download_completion
- data["download_netspeed"] = actual_netspeed
- data["download_name"] = "[downloaded_file.filename].[downloaded_file.filetype]"
- else if (provided_file)
- data["uploading"] = 1
- data["upload_uid"] = unique_token
- data["upload_clients"] = connected_clients.len
- data["upload_haspassword"] = server_password ? 1 : 0
- data["upload_filename"] = "[provided_file.filename].[provided_file.filetype]"
- else if (upload_menu)
- var/list/all_files[0]
- var/obj/item/computer_hardware/hard_drive/hard_drive = computer.all_components[MC_HDD]
- for(var/datum/computer_file/F in hard_drive.stored_files)
- all_files.Add(list(list(
- "uid" = F.uid,
- "filename" = "[F.filename].[F.filetype]",
- "size" = F.size
- )))
- data["upload_filelist"] = all_files
- else
- var/list/all_servers[0]
- for(var/datum/computer_file/program/nttransfer/P in SSnetworks.station_network.fileservers)
- all_servers.Add(list(list(
- "uid" = P.unique_token,
- "filename" = "[P.provided_file.filename].[P.provided_file.filetype]",
- "size" = P.provided_file.size,
- "haspassword" = P.server_password ? 1 : 0
- )))
- data["servers"] = all_servers
-
- return data
\ No newline at end of file
diff --git a/code/modules/modular_computers/file_system/programs/powermonitor.dm b/code/modules/modular_computers/file_system/programs/powermonitor.dm
index d2d57b1447..e87a731a40 100644
--- a/code/modules/modular_computers/file_system/programs/powermonitor.dm
+++ b/code/modules/modular_computers/file_system/programs/powermonitor.dm
@@ -1,19 +1,16 @@
//normal computer version is located in code\modules\power\monitor.dm, /obj/machinery/computer/monitor
/datum/computer_file/program/power_monitor
- filename = "powermonitor"
- filedesc = "Power Monitor"
+ filename = "ampcheck"
+ filedesc = "AmpCheck"
program_icon_state = "power_monitor"
extended_desc = "This program connects to sensors around the station to provide information about electrical systems"
ui_header = "power_norm.gif"
transfer_access = ACCESS_ENGINE
usage_flags = PROGRAM_CONSOLE
requires_ntnet = 0
- network_destination = "power monitoring system"
size = 9
tgui_id = "NtosPowerMonitor"
- ui_x = 550
- ui_y = 700
var/has_alert = 0
var/obj/structure/cable/attached_wire
diff --git a/code/modules/modular_computers/file_system/programs/radar.dm b/code/modules/modular_computers/file_system/programs/radar.dm
index 7a6e80267c..216365d6ea 100644
--- a/code/modules/modular_computers/file_system/programs/radar.dm
+++ b/code/modules/modular_computers/file_system/programs/radar.dm
@@ -2,31 +2,48 @@
filename = "genericfinder"
filedesc = "debug_finder"
ui_header = "borg_mon.gif" //DEBUG -- new icon before PR
- program_icon_state = "generic"
- extended_desc = "generic"
+ program_icon_state = "radarntos"
requires_ntnet = TRUE
transfer_access = null
available_on_ntnet = FALSE
- network_destination = "tracking program"
+ usage_flags = PROGRAM_LAPTOP | PROGRAM_TABLET
size = 5
tgui_id = "NtosRadar"
- ui_x = 800
- ui_y = 600
- special_assets = list(
- /datum/asset/simple/radar_assets,
- )
///List of trackable entities. Updated by the scan() proc.
var/list/objects
///Ref of the last trackable object selected by the user in the tgui window. Updated in the ui_act() proc.
var/atom/selected
///Used to store when the next scan is available. Updated by the scan() proc.
var/next_scan = 0
+ ///Used to keep track of the last value program_icon_state was set to, to prevent constant unnecessary update_icon() calls
+ var/last_icon_state = ""
+ ///Used by the tgui interface, themed NT or Syndicate.
+ var/arrowstyle = "ntosradarpointer.png"
+ ///Used by the tgui interface, themed for NT or Syndicate colors.
+ var/pointercolor = "green"
+
+/datum/computer_file/program/radar/run_program(mob/living/user)
+ . = ..()
+ if(.)
+ START_PROCESSING(SSfastprocess, src)
+ return
+ return FALSE
/datum/computer_file/program/radar/kill_program(forced = FALSE)
objects = list()
selected = null
+ STOP_PROCESSING(SSfastprocess, src)
return ..()
+/datum/computer_file/program/radar/Destroy()
+ STOP_PROCESSING(SSfastprocess, src)
+ return ..()
+
+/datum/computer_file/program/radar/ui_assets(mob/user)
+ return list(
+ get_asset_datum(/datum/asset/simple/radar_assets),
+ )
+
/datum/computer_file/program/radar/ui_data(mob/user)
var/list/data = get_header_data()
data["selected"] = selected
@@ -65,7 +82,37 @@
*
*/
/datum/computer_file/program/radar/proc/track()
- return
+ var/atom/movable/signal = find_atom()
+ if(!trackable(signal))
+ return
+
+ var/turf/here_turf = (get_turf(computer))
+ var/turf/target_turf = (get_turf(signal))
+ var/userot = FALSE
+ var/rot = 0
+ var/pointer="crosshairs"
+ var/locx = (target_turf.x - here_turf.x) + 24
+ var/locy = (here_turf.y - target_turf.y) + 24
+
+ if(get_dist_euclidian(here_turf, target_turf) > 24)
+ userot = TRUE
+ rot = round(Get_Angle(here_turf, target_turf))
+ else
+ if(target_turf.z > here_turf.z)
+ pointer="caret-up"
+ else if(target_turf.z < here_turf.z)
+ pointer="caret-down"
+
+ var/list/trackinfo = list(
+ "locx" = locx,
+ "locy" = locy,
+ "userot" = userot,
+ "rot" = rot,
+ "arrowstyle" = arrowstyle,
+ "color" = pointercolor,
+ "pointer" = pointer,
+ )
+ return trackinfo
/**
*
@@ -77,10 +124,12 @@
**arg1 is the atom being evaluated.
*/
/datum/computer_file/program/radar/proc/trackable(atom/movable/signal)
- if(!signal)
+ if(!signal || !computer)
return FALSE
var/turf/here = get_turf(computer)
var/turf/there = get_turf(signal)
+ if(!here || !there)
+ return FALSE //I was still getting a runtime even after the above check while scanning, so fuck it
return (there.z == here.z) || (is_station_level(here.z) && is_station_level(there.z))
/**
@@ -98,52 +147,74 @@
/datum/computer_file/program/radar/proc/scan()
return
+/**
+ *
+ *Finds the atom in the appropriate list that the `selected` var indicates
+ *
+ *The `selected` var holds a REF, which is a string. A mob REF may be
+ *something like "mob_209". In order to find the actual atom, we need
+ *to search the appropriate list for the REF string. This is dependant
+ *on the program (Lifeline uses GLOB.human_list, while Fission360 uses
+ *GLOB.poi_list), but the result will be the same; evaluate the string and
+ *return an atom reference.
+*/
+/datum/computer_file/program/radar/proc/find_atom()
+ return
+
+//We use SSfastprocess for the program icon state because it runs faster than process_tick() does.
+/datum/computer_file/program/radar/process()
+ if(computer.active_program != src)
+ STOP_PROCESSING(SSfastprocess, src) //We're not the active program, it's time to stop.
+ return
+ if(!selected)
+ return
+
+ var/atom/movable/signal = find_atom()
+ if(!trackable(signal))
+ program_icon_state = "[initial(program_icon_state)]lost"
+ if(last_icon_state != program_icon_state)
+ computer.update_icon()
+ last_icon_state = program_icon_state
+ return
+
+ var/here_turf = get_turf(computer)
+ var/target_turf = get_turf(signal)
+ var/trackdistance = get_dist_euclidian(here_turf, target_turf)
+ switch(trackdistance)
+ if(0)
+ program_icon_state = "[initial(program_icon_state)]direct"
+ if(1 to 12)
+ program_icon_state = "[initial(program_icon_state)]close"
+ if(13 to 24)
+ program_icon_state = "[initial(program_icon_state)]medium"
+ if(25 to INFINITY)
+ program_icon_state = "[initial(program_icon_state)]far"
+
+ if(last_icon_state != program_icon_state)
+ computer.update_icon()
+ last_icon_state = program_icon_state
+ computer.setDir(get_dir(here_turf, target_turf))
+
+//We can use process_tick to restart fast processing, since the computer will be running this constantly either way.
+/datum/computer_file/program/radar/process_tick()
+ if(computer.active_program == src)
+ START_PROCESSING(SSfastprocess, src)
+
///////////////////
//Suit Sensor App//
///////////////////
///A program that tracks crew members via suit sensors
/datum/computer_file/program/radar/lifeline
- filename = "Lifeline"
+ filename = "lifeline"
filedesc = "Lifeline"
- program_icon_state = "generic"
extended_desc = "This program allows for tracking of crew members via their suit sensors."
requires_ntnet = TRUE
transfer_access = ACCESS_MEDICAL
available_on_ntnet = TRUE
-/datum/computer_file/program/radar/lifeline/track()
- var/mob/living/carbon/human/humanoid = locate(selected) in GLOB.human_list
- if(!istype(humanoid) || !trackable(humanoid))
- return
-
- var/turf/here_turf = (get_turf(computer))
- var/turf/target_turf = (get_turf(humanoid))
- var/userot = FALSE
- var/rot = 0
- var/pointer="crosshairs"
- var/locx = (target_turf.x - here_turf.x)
- var/locy = (here_turf.y - target_turf.y)
- if(get_dist_euclidian(here_turf, target_turf) > 24) //If they're too far away, we need the angle for the arrow along the edge of the radar display
- userot = TRUE
- rot = round(Get_Angle(here_turf, target_turf))
- else
- locx = locx + 24
- locy = locy + 24
- if(target_turf.z > here_turf.z)
- pointer="caret-up"
- else if(target_turf.z < here_turf.z)
- pointer="caret-down"
- var/list/trackinfo = list(
- locx = locx,
- locy = locy,
- userot = userot,
- rot = rot,
- arrowstyle = "ntosradarpointer.png", //For the rotation arrow, it's stupid I know
- color = "green",
- pointer = pointer,
- )
- return trackinfo
+/datum/computer_file/program/radar/lifeline/find_atom()
+ return locate(selected) in GLOB.human_list
/datum/computer_file/program/radar/lifeline/scan()
if(world.time < next_scan)
@@ -180,50 +251,22 @@
//Nuke Disk Finder App//
////////////////////////
-///A program that tracks crew members via suit sensors
+///A program that tracks nukes and nuclear accessories
/datum/computer_file/program/radar/fission360
- filename = "Fission360"
+ filename = "fission360"
filedesc = "Fission360"
- program_icon_state = "generic"
+ program_icon_state = "radarsyndicate"
extended_desc = "This program allows for tracking of nuclear authorization disks and warheads."
requires_ntnet = FALSE
transfer_access = null
available_on_ntnet = FALSE
available_on_syndinet = TRUE
tgui_id = "NtosRadarSyndicate"
+ arrowstyle = "ntosradarpointerS.png"
+ pointercolor = "red"
-/datum/computer_file/program/radar/fission360/track()
- var/obj/nuke = locate(selected) in GLOB.poi_list
- if(!trackable(nuke))
- return
-
- var/turf/here_turf = (get_turf(computer))
- var/turf/target_turf = (get_turf(nuke))
- var/userot = FALSE
- var/rot = 0
- var/pointer="crosshairs"
- var/locx = (target_turf.x - here_turf.x)
- var/locy = (here_turf.y - target_turf.y)
- if(get_dist_euclidian(here_turf, target_turf) > 24) //If they're too far away, we need the angle for the arrow along the edge of the radar display
- userot = TRUE
- rot = round(Get_Angle(here_turf, target_turf))
- else
- locx = locx + 24
- locy = locy + 24
- if(target_turf.z > here_turf.z)
- pointer="caret-up"
- else if(target_turf.z < here_turf.z)
- pointer="caret-down"
- var/list/trackinfo = list(
- locx = locx,
- locy = locy,
- userot = userot,
- rot = rot,
- arrowstyle = "ntosradarpointerS.png",
- color = "red",
- pointer = pointer,
- )
- return trackinfo
+/datum/computer_file/program/radar/fission360/find_atom()
+ return locate(selected) in GLOB.poi_list
/datum/computer_file/program/radar/fission360/scan()
if(world.time < next_scan)
@@ -232,8 +275,6 @@
objects = list()
for(var/i in GLOB.nuke_list)
var/obj/machinery/nuclearbomb/nuke = i
- if(!trackable(nuke))
- continue
var/list/nukeinfo = list(
ref = REF(nuke),
@@ -241,9 +282,8 @@
)
objects += list(nukeinfo)
var/obj/item/disk/nuclear/disk = locate() in GLOB.poi_list
- if(trackable(disk))
- var/list/nukeinfo = list(
- ref = REF(disk),
- name = disk.name,
- )
- objects += list(nukeinfo)
+ var/list/nukeinfo = list(
+ ref = REF(disk),
+ name = "Nuke Auth. Disk",
+ )
+ objects += list(nukeinfo)
diff --git a/code/modules/modular_computers/file_system/programs/robocontrol.dm b/code/modules/modular_computers/file_system/programs/robocontrol.dm
index 910f923327..c0b82b9c95 100644
--- a/code/modules/modular_computers/file_system/programs/robocontrol.dm
+++ b/code/modules/modular_computers/file_system/programs/robocontrol.dm
@@ -1,16 +1,13 @@
/datum/computer_file/program/robocontrol
- filename = "robocontrol"
- filedesc = "Bot Remote Controller"
+ filename = "botkeeper"
+ filedesc = "Botkeeper"
program_icon_state = "robot"
extended_desc = "A remote controller used for giving basic commands to non-sentient robots."
transfer_access = ACCESS_ROBOTICS
requires_ntnet = TRUE
- network_destination = "robotics control network"
size = 12
tgui_id = "NtosRoboControl"
- ui_x = 550
- ui_y = 550
///Number of simple robots on-station.
var/botcount = 0
///Used to find the location of the user for the purposes of summoning robots.
@@ -80,7 +77,7 @@
return
if(id_card)
GLOB.data_core.manifest_modify(id_card.registered_name, id_card.assignment)
- card_slot.try_eject(TRUE, current_user)
+ card_slot.try_eject(current_user)
else
playsound(get_turf(ui_host()) , 'sound/machines/buzz-sigh.ogg', 25, FALSE)
return
diff --git a/code/modules/modular_computers/file_system/programs/sm_monitor.dm b/code/modules/modular_computers/file_system/programs/sm_monitor.dm
index 0a675a7abc..e4cf590930 100644
--- a/code/modules/modular_computers/file_system/programs/sm_monitor.dm
+++ b/code/modules/modular_computers/file_system/programs/sm_monitor.dm
@@ -1,16 +1,13 @@
/datum/computer_file/program/supermatter_monitor
- filename = "smmonitor"
- filedesc = "Supermatter Monitoring"
+ filename = "ntcims"
+ filedesc = "NT CIMS"
ui_header = "smmon_0.gif"
program_icon_state = "smmon_0"
- extended_desc = "This program connects to specially calibrated supermatter sensors to provide information on the status of supermatter-based engines."
+ extended_desc = "Crystal Integrity Monitoring System, connects to specially calibrated supermatter sensors to provide information on the status of supermatter-based engines."
requires_ntnet = TRUE
transfer_access = ACCESS_CONSTRUCTION
- network_destination = "supermatter monitoring system"
size = 5
tgui_id = "NtosSupermatterMonitor"
- ui_x = 600
- ui_y = 350
var/last_status = SUPERMATTER_INACTIVE
var/list/supermatters
var/obj/machinery/power/supermatter_crystal/active // Currently selected supermatter crystal.
diff --git a/code/modules/modular_computers/hardware/CPU.dm b/code/modules/modular_computers/hardware/CPU.dm
index d08d65ff8b..f13081e1f3 100644
--- a/code/modules/modular_computers/hardware/CPU.dm
+++ b/code/modules/modular_computers/hardware/CPU.dm
@@ -37,4 +37,4 @@
icon_state = "cpu_super"
w_class = WEIGHT_CLASS_TINY
power_usage = 75
- max_idle_programs = 2
\ No newline at end of file
+ max_idle_programs = 2
diff --git a/code/modules/modular_computers/hardware/_hardware.dm b/code/modules/modular_computers/hardware/_hardware.dm
index 37f3fc434e..81555340b2 100644
--- a/code/modules/modular_computers/hardware/_hardware.dm
+++ b/code/modules/modular_computers/hardware/_hardware.dm
@@ -10,9 +10,11 @@
// Computer that holds this hardware, if any.
var/power_usage = 0 // If the hardware uses extra power, change this.
- var/enabled = 1 // If the hardware is turned off set this to 0.
- var/critical = 0 // Prevent disabling for important component, like the CPU.
- var/can_install = 1 // Prevents direct installation of removable media.
+ var/enabled = TRUE // If the hardware is turned off set this to 0.
+ var/critical = FALSE // Prevent disabling for important component, like the CPU.
+ var/can_install = TRUE // Prevents direct installation of removable media.
+ var/expansion_hw = FALSE // Hardware that fits into expansion bays.
+ var/removable = TRUE // Whether the hardware is removable or not.
var/damage = 0 // Current damage level
var/max_damage = 100 // Maximal damage level.
var/damage_malfunction = 20 // "Malfunction" threshold. When damage exceeds this value the hardware piece will semi-randomly fail and do !!FUN!! things
@@ -20,7 +22,7 @@
var/malfunction_probability = 10// Chance of malfunction when the component is damaged
var/device_type
-/obj/item/computer_hardware/New(var/obj/L)
+/obj/item/computer_hardware/New(obj/L)
..()
pixel_x = rand(-8, 8)
pixel_y = rand(-8, 8)
@@ -32,30 +34,31 @@
/obj/item/computer_hardware/attackby(obj/item/I, mob/living/user)
- // Multitool. Runs diagnostics
- if(istype(I, /obj/item/multitool))
- to_chat(user, "***** DIAGNOSTICS REPORT *****")
- diagnostics(user)
- to_chat(user, "******************************")
- return 1
-
// Cable coil. Works as repair method, but will probably require multiple applications and more cable.
if(istype(I, /obj/item/stack/cable_coil))
+ var/obj/item/stack/S = I
if(obj_integrity == max_integrity)
to_chat(user, "\The [src] doesn't seem to require repairs.")
return 1
- if(I.use_tool(src, user, 0, 1))
+ if(S.use(1))
to_chat(user, "You patch up \the [src] with a bit of \the [I].")
obj_integrity = min(obj_integrity + 10, max_integrity)
return 1
if(try_insert(I, user))
- return 1
+ return TRUE
return ..()
+/obj/item/computer_hardware/multitool_act(mob/living/user, obj/item/I)
+ ..()
+ to_chat(user, "***** DIAGNOSTICS REPORT *****")
+ diagnostics(user)
+ to_chat(user, "******************************")
+ return TRUE
+
// Called on multitool click, prints diagnostic information to the user.
-/obj/item/computer_hardware/proc/diagnostics(var/mob/user)
+/obj/item/computer_hardware/proc/diagnostics(mob/user)
to_chat(user, "Hardware Integrity Test... (Corruption: [damage]/[max_damage]) [damage > damage_failure ? "FAIL" : damage > damage_malfunction ? "WARN" : "PASS"]")
// Handles damage checks
@@ -72,7 +75,7 @@
return TRUE // Good to go.
-/obj/item/computer_hardware/examine(var/mob/user)
+/obj/item/computer_hardware/examine(mob/user)
. = ..()
if(damage > damage_failure)
. += "It seems to be severely damaged!"
diff --git a/code/modules/modular_computers/hardware/ai_slot.dm b/code/modules/modular_computers/hardware/ai_slot.dm
index 8428467a87..c874d786a0 100644
--- a/code/modules/modular_computers/hardware/ai_slot.dm
+++ b/code/modules/modular_computers/hardware/ai_slot.dm
@@ -5,22 +5,21 @@
icon_state = "card_mini"
w_class = WEIGHT_CLASS_SMALL
device_type = MC_AI
+ expansion_hw = TRUE
var/obj/item/aicard/stored_card = null
var/locked = FALSE
+/obj/item/computer_hardware/ai_slot/handle_atom_del(atom/A)
+ if(A == stored_card)
+ try_eject(0, null, TRUE)
+ . = ..()
/obj/item/computer_hardware/ai_slot/examine(mob/user)
. = ..()
if(stored_card)
. += "There appears to be an intelliCard loaded. There appears to be a pinhole protecting a manual eject button. A screwdriver could probably press it."
-/obj/item/computer_hardware/ai_slot/on_install(obj/item/modular_computer/M, mob/living/user = null)
- M.add_verb(device_type)
-
-/obj/item/computer_hardware/ai_slot/on_remove(obj/item/modular_computer/M, mob/living/user = null)
- M.remove_verb(device_type)
-
/obj/item/computer_hardware/ai_slot/try_insert(obj/item/I, mob/living/user = null)
if(!holder)
return FALSE
@@ -40,14 +39,7 @@
return TRUE
-/obj/item/computer_hardware/ai_slot/try_eject(slot=0,mob/living/user = null,forced = 0)
- if (get_dist(src,user) > 1)
- if (iscarbon(user))
- var/mob/living/carbon/H = user
- if (!(H.dna && H.dna.check_mutation(TK) && tkMaxRangeCheck(src,H)))
- return FALSE
- else
- return FALSE
+/obj/item/computer_hardware/ai_slot/try_eject(mob/living/user = null,forced = FALSE)
if(!stored_card)
to_chat(user, "There is no card in \the [src].")
return FALSE
@@ -57,19 +49,21 @@
return FALSE
if(stored_card)
- stored_card.forceMove(get_turf(src))
+ to_chat(user, "You remove [stored_card] from [src].")
locked = FALSE
- stored_card.verb_pickup()
+ if(user)
+ user.put_in_hands(stored_card)
+ else
+ stored_card.forceMove(drop_location())
stored_card = null
- to_chat(user, "You remove the card from \the [src].")
return TRUE
return FALSE
/obj/item/computer_hardware/ai_slot/attackby(obj/item/I, mob/living/user)
if(..())
return
- if(istype(I, /obj/item/screwdriver))
+ if(I.tool_behaviour == TOOL_SCREWDRIVER)
to_chat(user, "You press down on the manual eject button with \the [I].")
try_eject(,user,1)
- return
\ No newline at end of file
+ return
diff --git a/code/modules/modular_computers/hardware/battery_module.dm b/code/modules/modular_computers/hardware/battery_module.dm
index e03427cc9c..0668248315 100644
--- a/code/modules/modular_computers/hardware/battery_module.dm
+++ b/code/modules/modular_computers/hardware/battery_module.dm
@@ -7,6 +7,9 @@
var/obj/item/stock_parts/cell/battery = null
device_type = MC_CELL
+/obj/item/computer_hardware/battery/get_cell()
+ return battery
+
/obj/item/computer_hardware/battery/New(loc, battery_type = null)
if(battery_type)
battery = new battery_type(src)
@@ -16,6 +19,11 @@
. = ..()
QDEL_NULL(battery)
+/obj/item/computer_hardware/battery/handle_atom_del(atom/A)
+ if(A == battery)
+ try_eject(forced = TRUE)
+ . = ..()
+
/obj/item/computer_hardware/battery/try_insert(obj/item/I, mob/living/user = null)
if(!holder)
return FALSE
@@ -40,12 +48,15 @@
return TRUE
-/obj/item/computer_hardware/battery/try_eject(slot=0, mob/living/user = null, forced = 0)
+/obj/item/computer_hardware/battery/try_eject(mob/living/user = null, forced = FALSE)
if(!battery)
to_chat(user, "There is no power cell connected to \the [src].")
return FALSE
else
- battery.forceMove(get_turf(src))
+ if(user)
+ user.put_in_hands(battery)
+ else
+ battery.forceMove(drop_location())
to_chat(user, "You detach \the [battery] from \the [src].")
battery = null
diff --git a/code/modules/modular_computers/hardware/card_slot.dm b/code/modules/modular_computers/hardware/card_slot.dm
index e4bc45dbc5..c243bf7db1 100644
--- a/code/modules/modular_computers/hardware/card_slot.dm
+++ b/code/modules/modular_computers/hardware/card_slot.dm
@@ -7,44 +7,36 @@
device_type = MC_CARD
var/obj/item/card/id/stored_card = null
- var/obj/item/card/id/stored_card2 = null
+
+/obj/item/computer_hardware/card_slot/handle_atom_del(atom/A)
+ if(A == stored_card)
+ try_eject(null, TRUE)
+ . = ..()
/obj/item/computer_hardware/card_slot/Destroy()
try_eject()
return ..()
/obj/item/computer_hardware/card_slot/GetAccess()
- if(stored_card && stored_card2) // Best of both worlds
- return (stored_card.GetAccess() | stored_card2.GetAccess())
- else if(stored_card)
- return stored_card.GetAccess()
- else if(stored_card2)
- return stored_card2.GetAccess()
- return ..()
+ var/list/total_access
+ if(stored_card)
+ total_access = stored_card.GetAccess()
+ var/obj/item/computer_hardware/card_slot/card_slot2 = holder?.all_components[MC_CARD2] //Best of both worlds
+ if(card_slot2?.stored_card)
+ total_access |= card_slot2.stored_card.GetAccess()
+ return total_access
/obj/item/computer_hardware/card_slot/GetID()
if(stored_card)
return stored_card
- else if(stored_card2)
- return stored_card2
return ..()
/obj/item/computer_hardware/card_slot/RemoveID()
if(stored_card)
. = stored_card
- if(!try_eject(1))
+ if(!try_eject())
return null
return
- if(stored_card2)
- . = stored_card2
- if(!try_eject(2))
- return null
-
-/obj/item/computer_hardware/card_slot/on_install(obj/item/modular_computer/M, mob/living/user = null)
- M.add_verb(device_type)
-
-/obj/item/computer_hardware/card_slot/on_remove(obj/item/modular_computer/M, mob/living/user = null)
- M.remove_verb(device_type)
/obj/item/computer_hardware/card_slot/try_insert(obj/item/I, mob/living/user = null)
if(!holder)
@@ -53,8 +45,7 @@
if(!istype(I, /obj/item/card/id))
return FALSE
- if(stored_card && stored_card2)
- to_chat(user, "You try to insert \the [I] into \the [src], but its slots are occupied.")
+ if(stored_card)
return FALSE
if(user)
if(!user.transferItemToLoc(I, src))
@@ -62,68 +53,68 @@
else
I.forceMove(src)
- if(!stored_card)
- stored_card = I
- else
- stored_card2 = I
- to_chat(user, "You insert \the [I] into \the [src].")
- playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, 0)
+ stored_card = I
+ to_chat(user, "You insert \the [I] into \the [expansion_hw ? "secondary":"primary"] [src].")
+ playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, FALSE)
+ if(ishuman(user))
+ var/mob/living/carbon/human/H = user
+ H.sec_hud_set_ID()
return TRUE
-/obj/item/computer_hardware/card_slot/try_eject(slot=0, mob/living/user = null, forced = 0)
- if (get_dist(src,user) > 1)
- if (iscarbon(user))
- var/mob/living/carbon/H = user
- if (!(H.dna && H.dna.check_mutation(TK) && tkMaxRangeCheck(src,H)))
- return FALSE
- else
- return FALSE
- if(!stored_card && !stored_card2)
+/obj/item/computer_hardware/card_slot/try_eject(mob/living/user = null, forced = FALSE)
+ if(!stored_card)
to_chat(user, "There are no cards in \the [src].")
return FALSE
- var/ejected = 0
- if(stored_card && (!slot || slot == 1))
- if(user)
- user.put_in_hands(stored_card)
- else
- stored_card.forceMove(get_turf(src))
- stored_card = null
- ejected++
+ if(user)
+ user.put_in_hands(stored_card)
+ else
+ stored_card.forceMove(drop_location())
+ stored_card = null
- if(stored_card2 && (!slot || slot == 2))
- if(user)
- user.put_in_hands(stored_card2)
- else
- stored_card2.forceMove(get_turf(src))
- stored_card2 = null
- ejected++
+ if(holder)
+ if(holder.active_program)
+ holder.active_program.event_idremoved(0)
- if(ejected)
- if(holder)
- if(holder.active_program)
- holder.active_program.event_idremoved(0, slot)
-
- for(var/I in holder.idle_threads)
- var/datum/computer_file/program/P = I
- P.event_idremoved(1, slot)
-
- to_chat(user, "You remove the card[ejected>1 ? "s" : ""] from \the [src].")
- playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, 0)
- return TRUE
- return FALSE
+ for(var/p in holder.idle_threads)
+ var/datum/computer_file/program/computer_program = p
+ computer_program.event_idremoved(1)
+ if(ishuman(user))
+ var/mob/living/carbon/human/human_user = user
+ human_user.sec_hud_set_ID()
+ to_chat(user, "You remove the card from \the [src].")
+ playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, FALSE)
+ return TRUE
/obj/item/computer_hardware/card_slot/attackby(obj/item/I, mob/living/user)
if(..())
return
- if(istype(I, /obj/item/screwdriver))
- to_chat(user, "You press down on the manual eject button with \the [I].")
- try_eject(0,user)
- return
+ if(I.tool_behaviour == TOOL_SCREWDRIVER)
+ if(stored_card)
+ to_chat(user, "You press down on the manual eject button with \the [I].")
+ try_eject(user)
+ return
+ swap_slot()
+ to_chat(user, "You adjust the connecter to fit into [expansion_hw ? "an expansion bay" : "the primary ID bay"].")
+
+/**
+ *Swaps the card_slot hardware between using the dedicated card slot bay on a computer, and using an expansion bay.
+*/
+/obj/item/computer_hardware/card_slot/proc/swap_slot()
+ expansion_hw = !expansion_hw
+ if(expansion_hw)
+ device_type = MC_CARD2
+ else
+ device_type = MC_CARD
/obj/item/computer_hardware/card_slot/examine(mob/user)
. = ..()
- if(stored_card || stored_card2)
+ . += "The connector is set to fit into [expansion_hw ? "an expansion bay" : "a computer's primary ID bay"], but can be adjusted with a screwdriver."
+ if(stored_card)
. += "There appears to be something loaded in the card slots."
+
+/obj/item/computer_hardware/card_slot/secondary
+ device_type = MC_CARD2
+ expansion_hw = TRUE
diff --git a/code/modules/modular_computers/hardware/hard_drive.dm b/code/modules/modular_computers/hardware/hard_drive.dm
index e27eaa53ae..e5c133de20 100644
--- a/code/modules/modular_computers/hardware/hard_drive.dm
+++ b/code/modules/modular_computers/hardware/hard_drive.dm
@@ -22,14 +22,14 @@
. = ..()
. += "It has [max_capacity] GQ of storage capacity."
-/obj/item/computer_hardware/hard_drive/diagnostics(var/mob/user)
+/obj/item/computer_hardware/hard_drive/diagnostics(mob/user)
..()
// 999 is a byond limit that is in place. It's unlikely someone will reach that many files anyway, since you would sooner run out of space.
to_chat(user, "NT-NFS File Table Status: [stored_files.len]/999")
to_chat(user, "Storage capacity: [used_capacity]/[max_capacity]GQ")
// Use this proc to add file to the drive. Returns 1 on success and 0 on failure. Contains necessary sanity checks.
-/obj/item/computer_hardware/hard_drive/proc/store_file(var/datum/computer_file/F)
+/obj/item/computer_hardware/hard_drive/proc/store_file(datum/computer_file/F)
if(!F || !istype(F))
return 0
@@ -52,7 +52,7 @@
return 1
// Use this proc to remove file from the drive. Returns 1 on success and 0 on failure. Contains necessary sanity checks.
-/obj/item/computer_hardware/hard_drive/proc/remove_file(var/datum/computer_file/F)
+/obj/item/computer_hardware/hard_drive/proc/remove_file(datum/computer_file/F)
if(!F || !istype(F))
return 0
@@ -78,7 +78,7 @@
used_capacity = total_size
// Checks whether file can be stored on the hard drive. We can only store unique files, so this checks whether we wouldn't get a duplicity by adding a file.
-/obj/item/computer_hardware/hard_drive/proc/can_store_file(var/datum/computer_file/F)
+/obj/item/computer_hardware/hard_drive/proc/can_store_file(datum/computer_file/F)
if(!F || !istype(F))
return 0
@@ -101,7 +101,7 @@
// Tries to find the file by filename. Returns null on failure
-/obj/item/computer_hardware/hard_drive/proc/find_file_by_name(var/filename)
+/obj/item/computer_hardware/hard_drive/proc/find_file_by_name(filename)
if(!check_functionality())
return null
@@ -157,18 +157,30 @@
max_capacity = 64
icon_state = "ssd_mini"
w_class = WEIGHT_CLASS_TINY
- custom_price = PRICE_ABOVE_NORMAL
+ custom_price = 150
-/obj/item/computer_hardware/hard_drive/small/syndicate // Syndicate variant - very slight better
+// Syndicate variant - very slight better
+/obj/item/computer_hardware/hard_drive/small/syndicate
desc = "An efficient SSD for portable devices developed by a rival organisation."
power_usage = 8
max_capacity = 70
var/datum/antagonist/traitor/traitor_data // Syndicate hard drive has the user's data baked directly into it on creation
+/// For tablets given to nuke ops
+/obj/item/computer_hardware/hard_drive/small/nukeops
+ power_usage = 8
+ max_capacity = 70
+
+/obj/item/computer_hardware/hard_drive/small/nukeops/install_default_programs()
+ store_file(new/datum/computer_file/program/computerconfig(src))
+ store_file(new/datum/computer_file/program/ntnetdownload/syndicate(src)) // Syndicate version; automatic access to syndicate apps and no NT apps
+ store_file(new/datum/computer_file/program/filemanager(src))
+ store_file(new/datum/computer_file/program/radar/fission360(src)) //I am legitimately afraid if I don't do this, Ops players will think they just don't get a pinpointer anymore.
+
/obj/item/computer_hardware/hard_drive/micro
name = "micro solid state drive"
desc = "A highly efficient SSD chip for portable devices."
power_usage = 2
max_capacity = 32
icon_state = "ssd_micro"
- w_class = WEIGHT_CLASS_TINY
\ No newline at end of file
+ w_class = WEIGHT_CLASS_TINY
diff --git a/code/modules/modular_computers/hardware/network_card.dm b/code/modules/modular_computers/hardware/network_card.dm
index fe1b1879cb..04bf494fe4 100644
--- a/code/modules/modular_computers/hardware/network_card.dm
+++ b/code/modules/modular_computers/hardware/network_card.dm
@@ -11,7 +11,7 @@
device_type = MC_NET
var/static/ntnet_card_uid = 1
-/obj/item/computer_hardware/network_card/diagnostics(var/mob/user)
+/obj/item/computer_hardware/network_card/diagnostics(mob/user)
..()
to_chat(user, "NIX Unique ID: [identification_id]")
to_chat(user, "NIX User Tag: [identification_string]")
@@ -22,7 +22,7 @@
if(ethernet)
to_chat(user, "OpenEth (Physical Connection) - Physical network connection port")
-/obj/item/computer_hardware/network_card/New(var/l)
+/obj/item/computer_hardware/network_card/New(l)
..()
identification_id = ntnet_card_uid++
@@ -31,7 +31,7 @@
return "[identification_string] (NID [identification_id])"
// 0 - No signal, 1 - Low signal, 2 - High signal. 3 - Wired Connection
-/obj/item/computer_hardware/network_card/proc/get_signal(var/specific_action = 0)
+/obj/item/computer_hardware/network_card/proc/get_signal(specific_action = 0)
if(!holder) // Hardware is not installed in anything. No signal. How did this even get called?
return 0
diff --git a/code/modules/modular_computers/hardware/portable_disk.dm b/code/modules/modular_computers/hardware/portable_disk.dm
index b5a957be04..89b0382e86 100644
--- a/code/modules/modular_computers/hardware/portable_disk.dm
+++ b/code/modules/modular_computers/hardware/portable_disk.dm
@@ -8,12 +8,8 @@
max_capacity = 16
device_type = MC_SDD
-/obj/item/computer_hardware/hard_drive/portable/on_install(obj/item/modular_computer/M, mob/living/user = null)
- M.add_verb(device_type)
-
-/obj/item/computer_hardware/hard_drive/portable/on_remove(obj/item/modular_computer/M, mob/living/user = null)
- ..()
- M.remove_verb(device_type)
+/obj/item/computer_hardware/hard_drive/portable/on_remove(obj/item/modular_computer/MC, mob/user)
+ return //this is a floppy disk, let's not shut the computer down when it gets pulled out.
/obj/item/computer_hardware/hard_drive/portable/install_default_programs()
return // Empty by default
diff --git a/code/modules/modular_computers/hardware/printer.dm b/code/modules/modular_computers/hardware/printer.dm
index 36c666e5d6..3bd5946435 100644
--- a/code/modules/modular_computers/hardware/printer.dm
+++ b/code/modules/modular_computers/hardware/printer.dm
@@ -5,19 +5,20 @@
icon_state = "printer"
w_class = WEIGHT_CLASS_NORMAL
device_type = MC_PRINT
+ expansion_hw = TRUE
var/stored_paper = 20
var/max_paper = 30
/obj/item/computer_hardware/printer/diagnostics(mob/living/user)
..()
- to_chat(user, "Paper level: [stored_paper]/[max_paper].")
+ to_chat(user, "Paper level: [stored_paper]/[max_paper].")
/obj/item/computer_hardware/printer/examine(mob/user)
. = ..()
. += "Paper level: [stored_paper]/[max_paper]."
-/obj/item/computer_hardware/printer/proc/print_text(var/text_to_print, var/paper_title = "")
+/obj/item/computer_hardware/printer/proc/print_text(text_to_print, paper_title = "")
if(!stored_paper)
return FALSE
if(!check_functionality())
@@ -27,11 +28,12 @@
// Damaged printer causes the resulting paper to be somewhat harder to read.
if(damage > damage_malfunction)
- P.setText(stars(text_to_print, 100-malfunction_probability))
+ P.info = stars(text_to_print, 100-malfunction_probability)
else
- P.setText(text_to_print)
+ P.info = text_to_print
if(paper_title)
P.name = paper_title
+ P.update_icon()
stored_paper--
P = null
return TRUE
diff --git a/code/modules/modular_computers/hardware/sensor_package.dm b/code/modules/modular_computers/hardware/sensor_package.dm
new file mode 100644
index 0000000000..c0363bc809
--- /dev/null
+++ b/code/modules/modular_computers/hardware/sensor_package.dm
@@ -0,0 +1,8 @@
+//This item doesn't do much on its own, but is required by apps such as AtmoZphere.
+/obj/item/computer_hardware/sensorpackage
+ name = "sensor package"
+ desc = "An integrated sensor package allowing a computer to take readings from the environment. Required by certain programs."
+ icon_state = "servo"
+ w_class = WEIGHT_CLASS_TINY
+ device_type = MC_SENSORS
+ expansion_hw = TRUE
diff --git a/code/modules/modular_computers/laptop_vendor.dm b/code/modules/modular_computers/laptop_vendor.dm
index ee9def4191..83bb057d66 100644
--- a/code/modules/modular_computers/laptop_vendor.dm
+++ b/code/modules/modular_computers/laptop_vendor.dm
@@ -27,9 +27,6 @@
var/dev_printer = 0 // 0: None, 1: Standard
var/dev_card = 0 // 0: None, 1: Standard
- ui_x = 500
- ui_y = 400
-
// Removes all traces of old order and allows you to begin configuration from scratch.
/obj/machinery/lapvend/proc/reset_order()
state = 0
@@ -55,6 +52,7 @@
var/obj/item/computer_hardware/battery/battery_module = null
if(fabricate)
fabricated_laptop = new /obj/item/modular_computer/laptop/buildable(src)
+ fabricated_laptop.install_component(new /obj/item/computer_hardware/card_slot)
fabricated_laptop.install_component(new /obj/item/computer_hardware/battery)
battery_module = fabricated_laptop.all_components[MC_CELL]
total_price = 99
@@ -110,7 +108,7 @@
if(dev_card)
total_price += 199
if(fabricate)
- fabricated_laptop.install_component(new /obj/item/computer_hardware/card_slot)
+ fabricated_laptop.install_component(new /obj/item/computer_hardware/card_slot/secondary)
return total_price
else if(devtype == 2) // Tablet, more expensive, not everyone could probably afford this.
@@ -119,6 +117,7 @@
fabricated_tablet = new(src)
fabricated_tablet.install_component(new /obj/item/computer_hardware/battery)
fabricated_tablet.install_component(new /obj/item/computer_hardware/processor_unit/small)
+ fabricated_tablet.install_component(new/obj/item/computer_hardware/card_slot)
battery_module = fabricated_tablet.all_components[MC_CELL]
total_price = 199
switch(dev_battery)
@@ -157,11 +156,11 @@
if(dev_printer)
total_price += 99
if(fabricate)
- fabricated_tablet.install_component(new/obj/item/computer_hardware/printer)
+ fabricated_tablet.install_component(new/obj/item/computer_hardware/printer/mini)
if(dev_card)
total_price += 199
if(fabricate)
- fabricated_tablet.install_component(new/obj/item/computer_hardware/card_slot)
+ fabricated_tablet.install_component(new/obj/item/computer_hardware/card_slot/secondary)
return total_price
return FALSE
@@ -224,15 +223,15 @@
return TRUE
return FALSE
-/obj/machinery/lapvend/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state)
+/obj/machinery/lapvend/ui_interact(mob/user, datum/tgui/ui)
if(stat & (BROKEN | NOPOWER | MAINT))
if(ui)
ui.close()
return FALSE
- ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+ ui = SStgui.try_update_ui(user, src, ui)
if (!ui)
- ui = new(user, src, ui_key, "ComputerFabricator", "Personal Computer Vendor", ui_x, ui_y, state = state)
+ ui = new(user, src, "ComputerFabricator")
ui.open()
/obj/machinery/lapvend/attackby(obj/item/I, mob/user)
@@ -241,7 +240,7 @@
if(!user.temporarilyRemoveItemFromInventory(c))
return
credits += c.value
- visible_message("[user] inserts [c.value] credits into [src].")
+ visible_message("[user] inserts [c.value] cr into [src].")
qdel(c)
return
else if(istype(I, /obj/item/holochip))
@@ -257,10 +256,10 @@
var/datum/bank_account/account = ID.registered_account
var/target_credits = total_price - credits
if(!account.adjust_money(-target_credits))
- say("Insufficient money on card to purchase!")
+ say("Insufficient credits on card to purchase!")
return
credits += target_credits
- say("[target_credits] cr has been desposited from your account.")
+ say("[target_credits] cr have been withdrawn from your account.")
return
return ..()
@@ -308,4 +307,4 @@
state = 3
addtimer(CALLBACK(src, .proc/reset_order), 100)
return TRUE
- return FALSE
\ No newline at end of file
+ return FALSE
diff --git a/code/modules/movespeed/modifiers/mobs.dm b/code/modules/movespeed/modifiers/mobs.dm
index a2176ca95e..d17767bb1f 100644
--- a/code/modules/movespeed/modifiers/mobs.dm
+++ b/code/modules/movespeed/modifiers/mobs.dm
@@ -78,9 +78,6 @@
blacklisted_movetypes = FLOATING
variable = TRUE
-/datum/movespeed_modifier/shove
- multiplicative_slowdown = SHOVE_SLOWDOWN_STRENGTH
-
/datum/movespeed_modifier/human_carry
variable = TRUE
diff --git a/code/modules/newscaster/ghostread.dm b/code/modules/newscaster/ghostread.dm
index 77cb1a03c8..ff51f5268c 100644
--- a/code/modules/newscaster/ghostread.dm
+++ b/code/modules/newscaster/ghostread.dm
@@ -3,7 +3,7 @@
set desc = "Open a list of available news channels"
set category = "Ghost"
- var/datum/browser/B = new(src, "ghost_news_list", "Chanenl List", 450, 600)
+ var/datum/browser/B = new(src, "ghost_news_list", "Channel List", 450, 600)
B.set_content(render_news_channel_list())
B.open()
diff --git a/code/modules/newscaster/newscaster_machine.dm b/code/modules/newscaster/newscaster_machine.dm
index cb2d49fc64..7e515bf0f5 100644
--- a/code/modules/newscaster/newscaster_machine.dm
+++ b/code/modules/newscaster/newscaster_machine.dm
@@ -95,6 +95,10 @@ GLOBAL_LIST_EMPTY(allCasters)
. = ..()
update_icon()
+/obj/machinery/newscaster/attack_ghost(mob/dead/observer/user)
+ if(istype(user))
+ user.read_news()
+
/obj/machinery/newscaster/ui_interact(mob/user)
. = ..()
if(ishuman(user) || issilicon(user))
@@ -336,7 +340,6 @@ GLOBAL_LIST_EMPTY(allCasters)
dat+="Return"
var/datum/browser/popup = new(human_or_robot_user, "newscaster_main", "Newscaster Unit #[unit_no]", 400, 600)
popup.set_content(dat)
- popup.set_title_image(human_or_robot_user.browse_rsc_icon(icon, icon_state))
popup.open()
/obj/machinery/newscaster/Topic(href, href_list)
diff --git a/code/modules/ninja/energy_katana.dm b/code/modules/ninja/energy_katana.dm
index e6d53d914f..00cfa94893 100644
--- a/code/modules/ninja/energy_katana.dm
+++ b/code/modules/ninja/energy_katana.dm
@@ -14,7 +14,7 @@
attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut")
block_chance = 50
slot_flags = ITEM_SLOT_BELT
- sharpness = IS_SHARP
+ sharpness = SHARP_EDGED
max_integrity = 200
resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF
var/datum/effect_system/spark_spread/spark_system
diff --git a/code/modules/ninja/suit/n_suit_verbs/ninja_stars.dm b/code/modules/ninja/suit/n_suit_verbs/ninja_stars.dm
index 508722ecf2..57faad9493 100644
--- a/code/modules/ninja/suit/n_suit_verbs/ninja_stars.dm
+++ b/code/modules/ninja/suit/n_suit_verbs/ninja_stars.dm
@@ -14,5 +14,5 @@
/obj/item/throwing_star/ninja
name = "ninja throwing star"
- throwforce = 30
+ throwforce = 20
embedding = list("pain_mult" = 6, "embed_chance" = 100, "fall_chance" = 0, "embed_chance_turf_mod" = 15)
diff --git a/code/modules/paperwork/carbonpaper.dm b/code/modules/paperwork/carbonpaper.dm
new file mode 100644
index 0000000000..dc8f172069
--- /dev/null
+++ b/code/modules/paperwork/carbonpaper.dm
@@ -0,0 +1,45 @@
+/obj/item/paper/carbon
+ name = "sheet of carbon"
+ icon_state = "paper_stack"
+ item_state = "paper"
+ // inhand_icon_state = "paper"
+ show_written_words = FALSE
+ var/copied = FALSE
+ var/iscopy = FALSE
+
+/obj/item/paper/carbon/update_icon_state()
+ if(iscopy)
+ icon_state = "cpaper"
+ else if(copied)
+ icon_state = "paper"
+ else
+ icon_state = "paper_stack"
+ if(info)
+ icon_state = "[icon_state]_words"
+
+/obj/item/paper/carbon/proc/removecopy(mob/living/user)
+ if(!copied)
+ var/obj/item/paper/carbon/C = src
+ var/copycontents = C.info
+ var/obj/item/paper/carbon/Copy = new /obj/item/paper/carbon(user.loc)
+
+ if(info)
+ copycontents = replacetext(copycontents, ""
+ Copy.name = "Copy - [C.name]"
+ to_chat(user, "You tear off the carbon-copy!")
+ C.copied = TRUE
+ Copy.iscopy = TRUE
+ Copy.update_icon_state()
+ C.update_icon_state()
+ user.put_in_hands(Copy)
+ else
+ to_chat(user, "There are no more carbon copies attached to this paper!")
+
+/obj/item/paper/carbon/on_attack_hand(mob/living/user)
+ if(loc == user && user.is_holding(src))
+ removecopy(user)
+ return
+ return ..()
diff --git a/code/modules/paperwork/clipboard.dm b/code/modules/paperwork/clipboard.dm
index 1a93661c76..5b576a2438 100644
--- a/code/modules/paperwork/clipboard.dm
+++ b/code/modules/paperwork/clipboard.dm
@@ -3,6 +3,8 @@
icon = 'icons/obj/bureaucracy.dmi'
icon_state = "clipboard"
item_state = "clipboard"
+ // inhand_icon_state = "clipboard"
+ // worn_icon_state = "clipboard"
throwforce = 0
w_class = WEIGHT_CLASS_SMALL
throw_speed = 3
@@ -34,7 +36,6 @@
. += "clipboard_pen"
. += "clipboard_over"
-
/obj/item/clipboard/attackby(obj/item/W, mob/user, params)
if(istype(W, /obj/item/paper))
if(!user.transferItemToLoc(W, src))
@@ -92,14 +93,14 @@
to_chat(usr, "You slot [W] into [src].")
if(href_list["write"])
- var/obj/item/P = locate(href_list["write"])
- if(istype(P) && P.loc == src)
+ var/obj/item/P = locate(href_list["write"]) in src
+ if(istype(P))
if(usr.get_active_held_item())
P.attackby(usr.get_active_held_item(), usr)
if(href_list["remove"])
- var/obj/item/P = locate(href_list["remove"])
- if(istype(P) && P.loc == src)
+ var/obj/item/P = locate(href_list["remove"]) in src
+ if(istype(P))
P.forceMove(usr.loc)
usr.put_in_hands(P)
if(P == toppaper)
@@ -111,13 +112,13 @@
toppaper = null
if(href_list["read"])
- var/obj/item/paper/P = locate(href_list["read"])
- if(istype(P) && P.loc == src)
+ var/obj/item/paper/P = locate(href_list["read"]) in src
+ if(istype(P))
usr.examinate(P)
if(href_list["top"])
- var/obj/item/P = locate(href_list["top"])
- if(istype(P) && P.loc == src)
+ var/obj/item/P = locate(href_list["top"]) in src
+ if(istype(P))
toppaper = P
to_chat(usr, "You move [P.name] to the top.")
diff --git a/code/modules/paperwork/filingcabinet.dm b/code/modules/paperwork/filingcabinet.dm
index b8f2c95762..390cd0cf83 100644
--- a/code/modules/paperwork/filingcabinet.dm
+++ b/code/modules/paperwork/filingcabinet.dm
@@ -35,7 +35,7 @@
. = ..()
if(mapload)
for(var/obj/item/I in loc)
- if(istype(I, /obj/item/paper) || istype(I, /obj/item/folder) || istype(I, /obj/item/photo))
+ if(I.w_class < WEIGHT_CLASS_NORMAL) //there probably shouldn't be anything placed ontop of filing cabinets in a map that isn't meant to go in them
I.forceMove(src)
/obj/structure/filingcabinet/deconstruct(disassembled = TRUE)
@@ -46,7 +46,12 @@
qdel(src)
/obj/structure/filingcabinet/attackby(obj/item/P, mob/user, params)
- if(istype(P, /obj/item/paper) || istype(P, /obj/item/folder) || istype(P, /obj/item/photo) || istype(P, /obj/item/documents))
+ if(P.tool_behaviour == TOOL_WRENCH && user.a_intent != INTENT_HELP)
+ to_chat(user, "You begin to [anchored ? "unwrench" : "wrench"] [src].")
+ if(P.use_tool(src, user, 20, volume=50))
+ to_chat(user, "You successfully [anchored ? "unwrench" : "wrench"] [src].")
+ anchored = !anchored
+ else if(P.w_class < WEIGHT_CLASS_NORMAL)
if(!user.transferItemToLoc(P, src))
return
to_chat(user, "You put [P] in [src].")
@@ -54,11 +59,6 @@
sleep(5)
icon_state = initial(icon_state)
updateUsrDialog()
- else if(istype(P, /obj/item/wrench))
- to_chat(user, "You begin to [anchored ? "unwrench" : "wrench"] [src].")
- if(P.use_tool(src, user, 20, volume=50))
- to_chat(user, "You successfully [anchored ? "unwrench" : "wrench"] [src].")
- anchored = !anchored
else if(user.a_intent != INTENT_HARM)
to_chat(user, "You can't put [P] in [src]!")
else
@@ -67,9 +67,6 @@
/obj/structure/filingcabinet/ui_interact(mob/user)
. = ..()
- if(isobserver(user))
- return
-
if(contents.len <= 0)
to_chat(user, "[src] is empty.")
return
@@ -100,16 +97,17 @@
to_chat(user, "You find nothing in [src].")
/obj/structure/filingcabinet/Topic(href, href_list)
+ if(!usr.canUseTopic(src, BE_CLOSE, ismonkey(usr)))
+ return
if(href_list["retrieve"])
usr << browse("", "window=filingcabinet") // Close the menu
- var/obj/item/P = locate(href_list["retrieve"])//contents[retrieveindex]
- if(istype(P) && P.loc == src && in_range(src, usr))
+ var/obj/item/P = locate(href_list["retrieve"]) in src //contents[retrieveindex]
+ if(istype(P) && in_range(src, usr))
usr.put_in_hands(P)
updateUsrDialog()
icon_state = "[initial(icon_state)]-open"
- sleep(5)
- icon_state = initial(icon_state)
+ addtimer(VARSET_CALLBACK(src, icon_state, initial(icon_state)), 5)
/*
@@ -170,6 +168,7 @@
virgin = 0 //tabbing here is correct- it's possible for people to try and use it
//before the records have been generated, so we do this inside the loop.
+//ATTACK HAND IGNORING PARENT RETURN VALUE
/obj/structure/filingcabinet/medical/on_attack_hand()
populate()
. = ..()
diff --git a/code/modules/paperwork/folders.dm b/code/modules/paperwork/folders.dm
index c32afab342..305099d115 100644
--- a/code/modules/paperwork/folders.dm
+++ b/code/modules/paperwork/folders.dm
@@ -33,7 +33,10 @@
if(contents.len)
. += "folder_paper"
+
/obj/item/folder/attackby(obj/item/W, mob/user, params)
+ if(burn_paper_product_attackby_check(W, user))
+ return
if(istype(W, /obj/item/paper) || istype(W, /obj/item/photo) || istype(W, /obj/item/documents))
if(!user.transferItemToLoc(W, src))
return
@@ -43,11 +46,14 @@
if(!user.is_literate())
to_chat(user, "You scribble illegibly on the cover of [src]!")
return
+
var/inputvalue = stripped_input(user, "What would you like to label the folder?", "Folder Labelling", "", MAX_NAME_LEN)
+
if(!inputvalue)
return
+
if(user.canUseTopic(src, BE_CLOSE))
- name = "folder - '[inputvalue]'"
+ name = "folder[(inputvalue ? " - '[inputvalue]'" : null)]"
/obj/item/folder/Destroy()
@@ -76,14 +82,14 @@
if(usr.contents.Find(src))
if(href_list["remove"])
- var/obj/item/I = locate(href_list["remove"])
- if(istype(I) && I.loc == src)
+ var/obj/item/I = locate(href_list["remove"]) in src
+ if(istype(I))
I.forceMove(usr.loc)
usr.put_in_hands(I)
if(href_list["read"])
- var/obj/item/I = locate(href_list["read"])
- if(istype(I) && I.loc == src)
+ var/obj/item/I = locate(href_list["read"]) in src
+ if(istype(I))
usr.examinate(I)
//Update everything
diff --git a/code/modules/paperwork/handlabeler.dm b/code/modules/paperwork/handlabeler.dm
index d054e5ff12..da9fdc4ca4 100644
--- a/code/modules/paperwork/handlabeler.dm
+++ b/code/modules/paperwork/handlabeler.dm
@@ -1,9 +1,10 @@
/obj/item/hand_labeler
name = "hand labeler"
- desc = "A combined label printer and applicator in a portable device, designed to be easy to operate and use."
+ desc = "A combined label printer, applicator, and remover, all in a single portable device. Designed to be easy to operate and use."
icon = 'icons/obj/bureaucracy.dmi'
icon_state = "labeler0"
item_state = "flight"
+ // inhand_icon_state = "flight"
var/label = null
var/labels_left = 30
var/mode = 0
@@ -55,9 +56,10 @@
to_chat(user, "You can't label creatures!") // use a collar
return
- user.visible_message("[user] labels [A] as [label].", \
- "You label [A] as [label].")
- A.name = "[A.name] ([label])"
+ user.visible_message("[user] labels [A] with \"[label]\".", \
+ "You label [A] with \"[label]\".")
+ A.AddComponent(/datum/component/label, label)
+ // playsound(A, 'sound/items/handling/component_pickup.ogg', 20, TRUE)
labels_left--
@@ -90,7 +92,9 @@
name = "cyborg-hand labeler"
/obj/item/hand_labeler/borg/afterattack(atom/A, mob/user, proximity)
- . = ..(A, user, proximity)
+ . = ..()
+ if(!proximity)
+ return
if(!iscyborg(user))
return
@@ -114,6 +118,7 @@
desc = "A roll of paper. Use it on a hand labeler to refill it."
icon_state = "labeler_refill"
item_state = "electropack"
+ // inhand_icon_state = "electropack"
lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi'
righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi'
w_class = WEIGHT_CLASS_TINY
diff --git a/code/modules/paperwork/paper.dm b/code/modules/paperwork/paper.dm
index 01c25b57f1..20ec678e45 100644
--- a/code/modules/paperwork/paper.dm
+++ b/code/modules/paperwork/paper.dm
@@ -1,4 +1,4 @@
-/*
+/**
* Paper
* also scraps of paper
*
@@ -13,54 +13,19 @@
/**
- ** This is a custom ui state. All it really does is keep track of pen
- ** being used and if they are editing it or not. This way we can keep
- ** the data with the ui rather than on the paper
- **/
-/datum/ui_state/default/paper_state
- /// What edit mode we are in and who is
- /// writing on it right now
- var/edit_mode = MODE_READING
- /// Setup for writing to a sheet
- var/pen_color = "black"
- var/pen_font = ""
- var/is_crayon = FALSE
- /// Setup for stamping a sheet
- // Why not the stamp obj? I have no idea
- // what happens to states out of scope so
- // don't want to put instances in this
- var/stamp_icon_state = ""
- var/stamp_name = ""
- var/stamp_class = ""
-
-
-/datum/ui_state/default/paper_state/proc/copy_from(datum/ui_state/default/paper_state/from)
- switch(from.edit_mode)
- if(MODE_READING)
- edit_mode = MODE_READING
- if(MODE_WRITING)
- edit_mode = MODE_WRITING
- pen_color = from.pen_color
- pen_font = from.pen_font
- is_crayon = from.is_crayon
- if(MODE_STAMPING)
- edit_mode = MODE_STAMPING
- stamp_icon_state = from.stamp_icon_state
- stamp_class = from.stamp_class
- stamp_name = from.stamp_name
-
-
-/**
- ** Paper is now using markdown (like in github pull notes) for ALL rendering
- ** so we do loose a bit of functionality but we gain in easy of use of
- ** paper and getting rid of that crashing bug
- **/
+ * Paper is now using markdown (like in github pull notes) for ALL rendering
+ * so we do loose a bit of functionality but we gain in easy of use of
+ * paper and getting rid of that crashing bug
+ */
/obj/item/paper
name = "paper"
gender = NEUTER
icon = 'icons/obj/bureaucracy.dmi'
icon_state = "paper"
item_state = "paper"
+ // inhand_icon_state = "paper"
+ // worn_icon_state = "paper"
+ // custom_fire_overlay = "paper_onfire_overlay"
throwforce = 0
w_class = WEIGHT_CLASS_TINY
throw_range = 1
@@ -71,6 +36,8 @@
resistance_flags = FLAMMABLE
max_integrity = 50
dog_fashion = /datum/dog_fashion/head
+ // drop_sound = 'sound/items/handling/paper_drop.ogg'
+ // pickup_sound = 'sound/items/handling/paper_pickup.ogg'
grind_results = list(/datum/reagent/cellulose = 3)
color = "white"
/// What's actually written on the paper.
@@ -81,45 +48,26 @@
var/list/stamps /// Positioning for the stamp in tgui
var/list/stamped /// Overlay info
- /// This REALLY should be a componenet. Basicly used during, april fools
- /// to honk at you
- var/rigged = 0
- var/spam_flag = 0
-
var/contact_poison // Reagent ID to transfer on contact
var/contact_poison_volume = 0
- // ui stuff
- var/ui_x = 600
- var/ui_y = 800
- // Ok, so WHY are we caching the ui's?
- // Since we are not using autoupdate we
- // need some way to update the ui's of
- // other people looking at it and if
- // its been updated. Yes yes, lame
- // but canot be helped. However by
- // doing it this way, we can see
- // live updates and have multipule
- // people look at it
- var/list/viewing_ui = list()
-
-
/// When the sheet can be "filled out"
/// This is an associated list
var/list/form_fields = list()
var/field_counter = 1
/obj/item/paper/Destroy()
- close_all_ui()
stamps = null
stamped = null
+ form_fields = null
+ stamped = null
. = ..()
/**
- ** This proc copies this sheet of paper to a new
- ** sheet, Makes it nice and easy for carbon and
- ** the copyer machine
- **/
+ * This proc copies this sheet of paper to a new
+ * sheet, Makes it nice and easy for carbon and
+ * the copyer machine
+ */
/obj/item/paper/proc/copy()
var/obj/item/paper/N = new(arglist(args))
N.info = info
@@ -133,10 +81,10 @@
return N
/**
- ** This proc sets the text of the paper and updates the
- ** icons. You can modify the pen_color after if need
- ** be.
- **/
+ * This proc sets the text of the paper and updates the
+ * icons. You can modify the pen_color after if need
+ * be.
+ */
/obj/item/paper/proc/setText(text)
info = text
form_fields = null
@@ -152,36 +100,22 @@
contact_poison = null
. = ..()
-
/obj/item/paper/Initialize()
. = ..()
pixel_y = rand(-8, 8)
pixel_x = rand(-9, 9)
update_icon()
-
/obj/item/paper/update_icon_state()
if(info && show_written_words)
icon_state = "[initial(icon_state)]_words"
-/obj/item/paper/ui_base_html(html)
- /// This might change in a future PR
- var/datum/asset/spritesheet/assets = get_asset_datum(/datum/asset/spritesheet/simple/paper)
- . = replacetext(html, "", assets.css_tag())
-
-
-/obj/item/paper/examine_more(mob/user)
- ui_interact(user)
-
-/obj/item/paper/proc/show_content(mob/user)
- user.examinate(src)
-
/obj/item/paper/verb/rename()
set name = "Rename paper"
set category = "Object"
set src in usr
- if(usr.incapacitated() || !usr.is_literate())
+ if(!usr.can_read(src) || usr.incapacitated(TRUE, TRUE) || (isobserver(usr) && !IsAdminGhost(usr)))
return
if(ishuman(usr))
var/mob/living/carbon/human/H = usr
@@ -195,26 +129,10 @@
name = "paper[(n_name ? text("- '[n_name]'") : null)]"
add_fingerprint(usr)
-
/obj/item/paper/suicide_act(mob/user)
user.visible_message("[user] scratches a grid on [user.p_their()] wrist with the paper! It looks like [user.p_theyre()] trying to commit sudoku...")
return (BRUTELOSS)
-
-/// ONLY USED FOR APRIL FOOLS
-/obj/item/paper/proc/reset_spamflag()
- spam_flag = FALSE
-
-
-/obj/item/paper/attack_self(mob/user)
- if(rigged && (SSevents.holidays && SSevents.holidays[APRIL_FOOLS]))
- if(!spam_flag)
- spam_flag = TRUE
- playsound(loc, 'sound/items/bikehorn.ogg', 50, TRUE)
- addtimer(CALLBACK(src, .proc/reset_spamflag), 20)
- . = ..()
-
-
/obj/item/paper/proc/clearpaper()
info = ""
stamps = null
@@ -222,28 +140,38 @@
cut_overlays()
update_icon_state()
-
/obj/item/paper/examine(mob/user)
- ui_interact(user)
+ . = ..()
+ if(!in_range(user, src) && !isobserver(user))
+ . += "You're too far away to read it!"
+ return
+ if(user.can_read(src))
+ ui_interact(user)
+ return
+ . += "You cannot read it!"
+
+/obj/item/paper/ui_status(mob/user,/datum/ui_state/state)
+ // Are we on fire? Hard ot read if so
+ if(resistance_flags & ON_FIRE)
+ return UI_CLOSE
+ if(!in_range(user,src))
+ return UI_CLOSE
+ if(user.incapacitated(TRUE, TRUE) || (isobserver(user) && !IsAdminGhost(user)))
+ return UI_UPDATE
+ // Even harder to read if your blind...braile? humm
+ // .. or if you cannot read
+ if(!user.can_read(src))
+ return UI_CLOSE
+ if(in_contents_of(/obj/machinery/door/airlock))
+ return UI_INTERACTIVE
+ return ..()
+
/obj/item/paper/can_interact(mob/user)
- if(!..())
- return FALSE
- if(resistance_flags & ON_FIRE) // Are we on fire? Hard ot read if so
- return FALSE
- if(user.is_blind()) // Even harder to read if your blind...braile? humm
- return FALSE
- return user.can_read(src) // checks if the user can read.
-
-
-/**
- ** This creates the ui, since we are using a custom state but not much else
- ** just makes it easyer to make it. Also we make a custom ui_key as I am
- ** not sure how tgui handles many producers?
-**/
-/obj/item/paper/proc/create_ui(mob/user, datum/ui_state/default/paper_state/state)
- ui_interact(user, "main", null, FALSE, null, state)
+ if(in_contents_of(/obj/machinery/door/airlock))
+ return TRUE
+ return ..()
/obj/item/proc/burn_paper_product_attackby_check(obj/item/I, mob/living/user, bypass_clumsy)
@@ -268,49 +196,22 @@
/obj/item/paper/attackby(obj/item/P, mob/living/user, params)
if(burn_paper_product_attackby_check(P, user))
- close_all_ui()
+ SStgui.close_uis(src)
return
if(istype(P, /obj/item/pen) || istype(P, /obj/item/toy/crayon))
if(length(info) >= MAX_PAPER_LENGTH) // Sheet must have less than 1000 charaters
to_chat(user, "This sheet of paper is full!")
return
-
- var/datum/ui_state/default/paper_state/state = new
- state.edit_mode = MODE_WRITING
- // should a crayon be in the same subtype as a pen? How about a brush or charcoal?
- // TODO: Convert all writing stuff to one type, /obj/item/art_tool maybe?
- state.is_crayon = istype(P, /obj/item/toy/crayon);
- if(state.is_crayon)
- var/obj/item/toy/crayon/PEN = P
- state.pen_font = CRAYON_FONT
- state.pen_color = PEN.paint_color
- else
- var/obj/item/pen/PEN = P
- state.pen_font = PEN.font
- state.pen_color = PEN.colour
-
- create_ui(user, state)
+ ui_interact(user)
return
else if(istype(P, /obj/item/stamp))
-
- var/datum/ui_state/default/paper_state/state = new
- state.edit_mode = MODE_STAMPING // we are read only becausse the sheet is full
- state.stamp_icon_state = P.icon_state
- state.stamp_name = P.name
-
- var/datum/asset/spritesheet/sheet = get_asset_datum(/datum/asset/spritesheet/simple/paper)
- state.stamp_class = sheet.icon_class_name(P.icon_state)
-
to_chat(user, "You ready your stamp over the paper! ")
-
- create_ui(user, state)
+ ui_interact(user)
return /// Normaly you just stamp, you don't need to read the thing
else
// cut paper? the sky is the limit!
- var/datum/ui_state/default/paper_state/state = new
- state.edit_mode = MODE_READING
- create_ui(user, state) // The other ui will be created with just read mode outside of this
+ ui_interact(user) // The other ui will be created with just read mode outside of this
return ..()
@@ -320,71 +221,69 @@
if(.)
info = "[stars(info)]"
-/obj/item/paper/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/default/paper_state/state = new)
- ui_key = "main-[REF(user)]"
- ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+/obj/item/paper/ui_assets(mob/user)
+ return list(
+ get_asset_datum(/datum/asset/spritesheet/simple/paper),
+ )
+
+/obj/item/paper/ui_interact(mob/user, datum/tgui/ui)
+ // Update the UI
+ ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
- var/datum/asset/assets = get_asset_datum(/datum/asset/spritesheet/simple/paper)
- assets.send(user)
- // The x size is because we double the width for the editor
- ui = new(user, src, ui_key, "PaperSheet", name, ui_x, ui_y, master_ui, state)
- ui.set_autoupdate(FALSE)
- viewing_ui[user] = ui
+ ui = new(user, src, "PaperSheet", name)
ui.open()
- else
- var/datum/ui_state/default/paper_state/last_state = ui.state
- if(last_state)
- last_state.copy_from(state)
- else
- ui.state = state
-/obj/item/paper/ui_close(mob/user)
- /// close the editing window and change the mode
- viewing_ui[user] = null
- . = ..()
+/obj/item/paper/ui_static_data(mob/user)
+ . = list()
+ .["text"] = info
+ .["max_length"] = MAX_PAPER_LENGTH
+ .["paper_color"] = !color || color == "white" ? "#FFFFFF" : color // color might not be set
+ .["paper_state"] = icon_state /// TODO: show the sheet will bloodied or crinkling?
+ .["stamps"] = stamps
-// Again, we have to do this as autoupdate is off
-/obj/item/paper/proc/update_all_ui()
- for(var/datum/tgui/ui in viewing_ui)
- ui.update()
-// Again, we have to do this as autoupdate is off
-/obj/item/paper/proc/close_all_ui()
- for(var/datum/tgui/ui in viewing_ui)
- ui.close()
- viewing_ui = list()
/obj/item/paper/ui_data(mob/user)
var/list/data = list()
-
- var/datum/tgui/ui = viewing_ui[user]
- var/datum/ui_state/default/paper_state/state = ui.state
-
- // Should all this go in static data and just do a forced update?
- data["text"] = info
- data["max_length"] = MAX_PAPER_LENGTH
- data["paper_state"] = icon_state /// TODO: show the sheet will bloodied or crinkling?
- data["paper_color"] = !color || color == "white" ? "#FFFFFF" : color // color might not be set
- data["stamps"] = stamps
-
- data["edit_mode"] = state.edit_mode
- data["edit_usr"] = "[ui.user]";
-
- // pen info for editing
- data["is_crayon"] = state.is_crayon
- data["pen_font"] = state.pen_font
- data["pen_color"] = state.pen_color
- // stamping info for..stamping
- data["stamp_class"] = state.stamp_class
-
+ var/obj/O = user.get_active_held_item()
+ if(istype(O, /obj/item/toy/crayon))
+ var/obj/item/toy/crayon/PEN = O
+ data["pen_font"] = CRAYON_FONT
+ data["pen_color"] = PEN.paint_color
+ data["edit_mode"] = MODE_WRITING
+ data["is_crayon"] = TRUE
+ data["stamp_class"] = "FAKE"
+ data["stamp_icon_state"] = "FAKE"
+ else if(istype(O, /obj/item/pen))
+ var/obj/item/pen/PEN = O
+ data["pen_font"] = PEN.font
+ data["pen_color"] = PEN.colour
+ data["edit_mode"] = MODE_WRITING
+ data["is_crayon"] = FALSE
+ data["stamp_class"] = "FAKE"
+ data["stamp_icon_state"] = "FAKE"
+ else if(istype(O, /obj/item/stamp))
+ var/datum/asset/spritesheet/sheet = get_asset_datum(/datum/asset/spritesheet/simple/paper)
+ data["stamp_icon_state"] = O.icon_state
+ data["stamp_class"] = sheet.icon_class_name(O.icon_state)
+ data["edit_mode"] = MODE_STAMPING
+ data["pen_font"] = "FAKE"
+ data["pen_color"] = "FAKE"
+ data["is_crayon"] = FALSE
+ else
+ data["edit_mode"] = MODE_READING
+ data["pen_font"] = "FAKE"
+ data["pen_color"] = "FAKE"
+ data["is_crayon"] = FALSE
+ data["stamp_icon_state"] = "FAKE"
+ data["stamp_class"] = "FAKE"
data["field_counter"] = field_counter
data["form_fields"] = form_fields
return data
-
-/obj/item/paper/ui_act(action, params, datum/tgui/ui, datum/ui_state/default/paper_state/state)
+/obj/item/paper/ui_act(action, params,datum/tgui/ui)
if(..())
return
switch(action)
@@ -392,34 +291,33 @@
var/stamp_x = text2num(params["x"])
var/stamp_y = text2num(params["y"])
var/stamp_r = text2num(params["r"]) // rotation in degrees
-
+ var/stamp_icon_state = params["stamp_icon_state"]
+ var/stamp_class = params["stamp_class"]
if (isnull(stamps))
- stamps = new/list()
+ stamps = list()
if(stamps.len < MAX_PAPER_STAMPS)
// I hate byond when dealing with freaking lists
- stamps += list(list(state.stamp_class, stamp_x, stamp_y,stamp_r)) /// WHHHHY
+ stamps[++stamps.len] = list(stamp_class, stamp_x, stamp_y, stamp_r) /// WHHHHY
/// This does the overlay stuff
if (isnull(stamped))
- stamped = new/list()
+ stamped = list()
if(stamped.len < MAX_PAPER_STAMPS_OVERLAYS)
- var/mutable_appearance/stampoverlay = mutable_appearance('icons/obj/bureaucracy.dmi', "paper_[state.stamp_icon_state]")
+ var/mutable_appearance/stampoverlay = mutable_appearance('icons/obj/bureaucracy.dmi', "paper_[stamp_icon_state]")
stampoverlay.pixel_x = rand(-2, 2)
stampoverlay.pixel_y = rand(-3, 2)
add_overlay(stampoverlay)
- LAZYADD(stamped, state.stamp_icon_state)
+ LAZYADD(stamped, stamp_icon_state)
- ui.user.visible_message("[ui.user] stamps [src] with [state.stamp_name]!", "You stamp [src] with [state.stamp_name]!")
+ update_static_data(usr,ui)
+ ui.user.visible_message("[ui.user] stamps [src] with [stamp_class]!", "You stamp [src] with [stamp_class]!")
else
to_chat(usr, pick("You try to stamp but you miss!", "There is no where else you can stamp!"))
-
- update_all_ui()
. = TRUE
if("save")
var/in_paper = params["text"]
var/paper_len = length(in_paper)
- var/list/fields = params["form_fields"]
field_counter = params["field_counter"] ? text2num(params["field_counter"]) : field_counter
if(paper_len > MAX_PAPER_LENGTH)
@@ -435,31 +333,24 @@
if(info != in_paper)
to_chat(ui.user, "You have added to your paper masterpiece!");
info = in_paper
-
- for(var/key in fields)
- form_fields[key] = fields[key];
+ update_static_data(usr,ui)
- update_all_ui()
update_icon()
-
. = TRUE
-
-/*
+/**
* Construction paper
*/
-
/obj/item/paper/construction
/obj/item/paper/construction/Initialize()
. = ..()
color = pick("FF0000", "#33cc33", "#ffb366", "#551A8B", "#ff80d5", "#4d94ff")
-/*
+/**
* Natural paper
*/
-
/obj/item/paper/natural/Initialize()
. = ..()
color = "#FFF5ED"
diff --git a/code/modules/paperwork/paper_cutter.dm b/code/modules/paperwork/paper_cutter.dm
index 3937720f74..a3b9b23141 100644
--- a/code/modules/paperwork/paper_cutter.dm
+++ b/code/modules/paperwork/paper_cutter.dm
@@ -26,11 +26,11 @@
var/obj/item/bodypart/BP = C.get_bodypart(BODY_ZONE_HEAD)
if(BP)
BP.drop_limb()
- playsound(loc,pick('sound/misc/desceration-01.ogg','sound/misc/desceration-02.ogg','sound/misc/desceration-01.ogg') ,50, 1, -1)
+ playsound(loc, pick('sound/misc/desceration-01.ogg','sound/misc/desceration-02.ogg','sound/misc/desceration-01.ogg'),50, TRUE, -1)
return (BRUTELOSS)
else
user.visible_message("[user] repeatedly bashes [src.name] against [user.p_their()] head! It looks like [user.p_theyre()] trying to commit suicide!")
- playsound(loc, 'sound/items/gavel.ogg', 50, 1, -1)
+ playsound(loc, 'sound/items/gavel.ogg', 50, TRUE, -1)
return (BRUTELOSS)
@@ -42,11 +42,12 @@
if(storedpaper)
. += "paper"
+
/obj/item/papercutter/attackby(obj/item/P, mob/user, params)
if(istype(P, /obj/item/paper) && !storedpaper)
if(!user.transferItemToLoc(P, src))
return
- playsound(loc, "pageturn", 60, 1)
+ playsound(loc, "pageturn", 60, TRUE)
to_chat(user, "You place [P] in [src].")
storedpaper = P
update_icon()
@@ -59,17 +60,17 @@
storedcutter = P
update_icon()
return
- if(istype(P, /obj/item/screwdriver) && storedcutter)
+ if(P.tool_behaviour == TOOL_SCREWDRIVER && storedcutter)
P.play_tool_sound(src)
to_chat(user, "[storedcutter] has been [cuttersecured ? "unsecured" : "secured"].")
cuttersecured = !cuttersecured
return
..()
-/obj/item/papercutter/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
+/obj/item/papercutter/on_attack_hand(mob/user)
add_fingerprint(user)
if(!storedcutter)
- to_chat(user, "The cutting blade is gone! You can't use [src] now.")
+ to_chat(user, "The cutting blade is gone! You can't use [src] now.")
return
if(!cuttersecured)
@@ -79,7 +80,7 @@
update_icon()
if(storedpaper)
- playsound(src.loc, 'sound/weapons/slash.ogg', 50, 1)
+ playsound(src.loc, 'sound/weapons/slash.ogg', 50, TRUE)
to_chat(user, "You neatly cut [storedpaper].")
storedpaper = null
qdel(storedpaper)
@@ -88,6 +89,7 @@
update_icon()
/obj/item/papercutter/MouseDrop(atom/over_object)
+ . = ..()
var/mob/M = usr
if(M.incapacitated() || !Adjacent(M))
return
@@ -98,10 +100,6 @@
else if(istype(over_object, /obj/screen/inventory/hand))
var/obj/screen/inventory/hand/H = over_object
M.putItemFromInventoryInHandIfPossible(src, H.held_index)
-
- else
- . = ..()
-
add_fingerprint(M)
/obj/item/paperslip
@@ -112,6 +110,12 @@
resistance_flags = FLAMMABLE
max_integrity = 50
+/obj/item/paperslip/attackby(obj/item/I, mob/living/user, params)
+ if(burn_paper_product_attackby_check(I, user))
+ return
+ return ..()
+
+
/obj/item/paperslip/Initialize()
. = ..()
pixel_x = rand(-5, 5)
@@ -124,5 +128,6 @@
icon = 'icons/obj/bureaucracy.dmi'
icon_state = "cutterblade"
item_state = "knife"
+ // inhand_icon_state = "knife"
lefthand_file = 'icons/mob/inhands/equipment/kitchen_lefthand.dmi'
righthand_file = 'icons/mob/inhands/equipment/kitchen_righthand.dmi'
diff --git a/code/modules/paperwork/paper_premade.dm b/code/modules/paperwork/paper_premade.dm
index 414026924a..5d6d3d45e8 100644
--- a/code/modules/paperwork/paper_premade.dm
+++ b/code/modules/paperwork/paper_premade.dm
@@ -4,53 +4,22 @@
/obj/item/paper/fluff/sop
name = "paper- 'Standard Operating Procedure'"
- info = {"
-Alert Levels:
-* Blue - Emergency
- * Caused by fire
- * Caused by manual interaction
- * Action: Close all fire doors. These can only be opened by resetting the alarm
-* Red- Ejection/Self Destruct
- * Caused by module operating computer.
- * Action: After the specified time the module will eject completely.
-Engine Maintenance Instructions:
-1. Shut off ignition systems:
-2. Activate internal power
-3. Activate orbital balance matrix
-4. Remove volatile liquids from area
-5. Wear a fire suit
-6. After Decontaminate Visit medical examiner
-Toxin Laboratory Procedure:
-1. Wear a gas mask regardless
-2. Get an oxygen tank.
-3. Activate internal atmosphere
-4. After Decontaminate Visit medical examiner
-Disaster Procedure:
-Fire:
-1. Activate sector fire alarm.
-2. Move to a safe area.
-3. Get a fire suit
-* After:
- 1. Assess Damage
- 2. Repair damages
- 3. If needed, Evacuate
-Meteor Shower:
-1. Activate fire alarm
-2. Move to the back of ship
-* After
- 1. Repair damage
- 2. If needed, Evacuate
-Accidental Reentry:
-1. Activate fire alarms in front of ship.
-2. Move volatile matter to a fire proof area!
-3. Get a fire suit.
-4. Stay secure until an emergency ship arrives.
-5. If ship does not arrive-Evacuate to a nearby safe area!
-"};
+ info = "Alert Levels: \nBlue- Emergency \n\t1. Caused by fire \n\t2. Caused by manual interaction \n\tAction: \n\t\tClose all fire doors. These can only be opened by resetting the alarm \nRed- Ejection/Self-Destruct \n\t1. Caused by module operating computer. \n\tAction: \n\t\tAfter the specified time the module will eject completely. \n \nEngine Maintenance Instructions: \n\tShut off ignition systems: \n\tActivate internal power \n\tActivate orbital balance matrix \n\tRemove volatile liquids from area \n\tWear a fire suit \n \n\tAfter \n\t\tDecontaminate \n\t\tVisit medical examiner \n \nToxin Laboratory Procedure: \n\tWear a gas mask regardless \n\tGet an oxygen tank. \n\tActivate internal atmosphere \n \n\tAfter \n\t\tDecontaminate \n\t\tVisit medical examiner \n \nDisaster Procedure: \n\tFire: \n\t\tActivate sector fire alarm. \n\t\tMove to a safe area. \n\t\tGet a fire suit \n\t\tAfter: \n\t\t\tAssess Damage \n\t\t\tRepair damages \n\t\t\tIf needed, Evacuate \n\tMeteor Shower: \n\t\tActivate fire alarm \n\t\tMove to the back of ship \n\t\tAfter \n\t\t\tRepair damage \n\t\t\tIf needed, Evacuate \n\tAccidental Reentry: \n\t\tActivate fire alarms in front of ship. \n\t\tMove volatile matter to a fire proof area! \n\t\tGet a fire suit. \n\t\tStay secure until an emergency ship arrives. \n \n\t\tIf ship does not arrive- \n\t\t\tEvacuate to a nearby safe area!"
/obj/item/paper/fluff/shuttles/daniel
info = "i love daniel daniel is my best friend
you are tearing me apart elise"
+/obj/item/paper/fluff/jobs/prisoner/letter
+ name = "letter from home"
+ info = {"Dearest sweetheart,
+ It is truly saddening you must spend your time locked up in an awful prison on that dangerous station. I have spoken to your lawyer who will attempt to appeal to the judge so your sentence may hopefully be reduced.
+ Regardless, I just want you to understand that all of us out here still love you, and want to see you released safely some day! I know that prison can be a very vicious place, so please promise us you'll avoid getting into any fights or trouble, okay?
+ We all care for your safety deeply, and could not live with ourselves if you ended up getting hurt. We've scheduled a visit to see you, and with any luck, hopefully our request will be granted soon.
+ Anyways, please do your best to make it by in that place, and never forget we'll be always here for you, no matter if we're separated.
+
+ Please stay safe,
+ -Love, Your Dearest"}
+
//////////// Job guides n' fluff
@@ -90,13 +59,46 @@ Accidental Reentry:
name = "paper- 'Chemical Information'"
info = "Known Onboard Toxins: \n\tGrade A Semi-Liquid Plasma: \n\t\tHighly poisonous. You cannot sustain concentrations above 15 units. \n\t\tA gas mask fails to filter plasma after 50 units. \n\t\tWill attempt to diffuse like a gas. \n\t\tFiltered by scrubbers. \n\t\tThere is a bottled version which is very different \n\t\t\tfrom the version found in canisters! \n \n\t\tWARNING: Highly Flammable. Keep away from heat sources \n\t\texcept in an enclosed fire area! \n\t\tWARNING: It is a crime to use this without authorization. \nKnown Onboard Anti-Toxin: \n\tAnti-Toxin Type 01P: Works against Grade A Plasma. \n\t\tBest if injected directly into bloodstream. \n\t\tA full injection is in every regular Med-Kit. \n\t\tSpecial toxin Kits hold around 7. \n \nKnown Onboard Chemicals (other): \n\tRejuvenation T#001: \n\t\tEven 1 unit injected directly into the bloodstream \n\t\t\twill cure unconscious and sleep toxins. \n\t\tIf administered to a dying patient it will prevent \n\t\t\tfurther damage for about units*3 seconds. \n\t\t\tit will not cure them or allow them to be cured. \n\t\tIt can be administered to a non-dying patient \n\t\t\tbut the chemicals disappear just as fast. \n\tMorphine T#054: \n\t\t5 units will induce precisely 1 minute of sleep. \n\t\t\tThe effect are cumulative. \n\t\tWARNING: It is a crime to use this without authorization"
+/obj/item/paper/fluff/jobs/medical/hippocratic
+ name = "paper- 'Hippocratic Oath'"
+ info = {"I swear to fulfill, to the best of my ability and judgment, this covenant:
+
+ I will respect the hard-won scientific gains of those physicians in whose steps I walk,
+ and gladly share such knowledge as is mine with those who are to follow.
+
+ I will apply, for the benefit of the sick, all measures that are required,
+ avoiding those twin traps of overtreatment and therapeutic nihilism.
+
+ I will remember that there is art to medicine as well as science,
+ and that warmth, sympathy, and understanding may outweigh the surgeon's knife or the chemist's drug.
+
+ I will not be ashamed to say "I know not,"
+ nor will I fail to call in my colleagues when the skills of another are needed for a patient's recovery.
+
+ I will respect the privacy of my patients, for their problems are not disclosed to me that the world may know. Most especially must I tread with care in matters of life and death.
+ If it is given me to save a life, all thanks. But it may also be within my power to take a life;
+ this awesome responsibility must be faced with great humbleness and awareness of my own frailty. Above all, I must not play at God.
+
+ I will remember that I do not treat a fever chart, a cancerous growth, but a sick human being, whose illness may affect the person's family and economic stability.
+ My responsibility includes these related problems, if I am to care adequately for the sick.
+
+ I will prevent disease whenever I can, for prevention is preferable to cure.
+
+ I will remember that I remain a member of society, with special obligations to all my fellow human beings,
+ those sound of mind and body as well as the infirm.
+
+ If I do not violate this oath, may I enjoy life and art, respected while I live and remembered with affection thereafter.
+ May I always act so as to preserve the finest traditions of my calling and may I long experience the joy of healing those who seek my help.
+ "}
+
/*
* Stations
*/
+////////// cogstation.
/obj/item/paper/guides/cogstation/job_changes
name = "MEMO: Job Changes"
- info = "To ensure minimal employee downtime, please take note of the following changes to select professions that CogStation specifically requires: \n \n- Scientists are to have access to chemistry in order to reach the MedSci router. \n \n- Chemists should at the very least be provided with an encryption key for the Science channel, if not basic access to the Research department at large. \n- Roboticists are to have basic Medical and Morgue access. \n- Engineers and Atmospheric Technicians are to have Warehouse and Mining access. \n- The Cook should not have Morgue access \n- The Clown and Mime are to have Maintenance access. This is necessary due to the location of their offices. \n \nGenerated by Organic Resources Bot #2053"
+ info = "To ensure minimal employee downtime, please take note of the following changes to select professions that CogStation specifically requires: \n \n- Engineers and Atmospheric Technicians are to have Warehouse and Mining access. \n- The Cook should not have Morgue access. \n- The Clown and Mime are to have Maintenance access. This is necessary due to the location of their offices. \n \nGenerated by Organic Resources Bot #2053"
/obj/item/paper/guides/cogstation/letter_sec
name = "To future Security personnel"
@@ -104,7 +106,7 @@ Accidental Reentry:
/obj/item/paper/guides/cogstation/disposals
name = "Regarding the disposal system:"
- info = "As you might have noticed, this station has far more disposal pipes than you may expect from your average Nanotrasen research facility. Part of the reason for this is specialization - mail, trash, even corpses have their own disposal systems. Unfortunately, the convenient color-coding was lost in translocation and we've had to compensate by marking the area around each bin. \n \n- WHITE/GRAY STRIPES is for DELIVERIES. \n- RED STRIPES is for CORPSES. \n- EVERYTHING ELSE is for TRASH, barring a few exceptions that should be labeled as such. \n \nIdeally the station won't sustain any heavy structural damage during your time here but if it does, or someone decides to tamper with/sabotage this system, you'll be forgiven if you can't put it back together perfectly. \n \n-C. Donnelly Architectural Analyst"
+ info = "As you might have noticed, this station has far more disposal pipes than you may expect from your average Nanotrasen research facility. Part of the reason for this is specialization - mail, trash, even corpses have their own disposal systems. Unfortunately, the convenient color-coding was lost in translocation and we've had to compensate by marking the area around each bin. \n \n- WHITE/GRAY STRIPES are for DELIVERIES. \n- RED STRIPES are for CORPSES. \n- EVERYTHING ELSE is for TRASH, barring a few exceptions that should be labeled as such. \n \nIdeally the station won't sustain any heavy structural damage during your time here but if it does, or someone decides to tamper with/sabotage this system, you'll be forgiven if you can't put it back together perfectly. \n \n-C. Donnelly Architectural Analyst"
/obj/item/paper/guides/cogstation/janitor
name = "a quick tip"
@@ -128,7 +130,7 @@ Accidental Reentry:
/obj/item/paper/guides/cogstation/letter_eng
name = "To future Engineering staff:"
- info = "I'm not gonna sugarcoat this. Compared to other departments, you might have your work cut out for you. CogStation is an entirely different beast than your standard Box, but everyone's still gonna expect you to keep the place running. \n \n If there's any good news, it's your time to shine if you know how to run a thermo-electric generator. That's what this station runs on, and that isn't likely to change anytime soon. If it's absolutely critical you might be able to run a singularity or tesla engine east of mining, but it won't have any sort of shielding out there. \n \nYou still have three solar arrays to work with, two of them being on each end of the starboard side. The port side array will need you to either get access from a head of staff or security, unless you want to spacewalk around the whole station. Don't be afraid to ask the latter - they're there for you, after all. As for other utilities the air system is a bit different than you'd expect, but fortunately you should have the atmos techs this station needed a long time ago. The disposal network is significantly more complicated, yet more capable. I've already elaborated on it, so I'll let you find and read my write-up for that. As for the routing system, it's just begging to get hit by a stray meteor so consider other utilities a higher priority. \n \nGood luck. You're gonna need it. \n \n-C. Donnelly Architectural Analyst"
+ info = "I'm not gonna sugarcoat this. Compared to other departments, you might have your work cut out for you. CogStation is an entirely different beast than your standard Box, but everyone's still gonna expect you to keep the place running. \n \n If there's any good news, it's your time to shine if you know how to run a thermo-electric generator. That's what this station runs on, and that isn't likely to change anytime soon. If it's absolutely critical you might be able to run a singularity or tesla engine east of mining, but it won't have any sort of protection out there. \n \nYou still have three solar arrays to work with, two of them being on each end of the starboard side. The port side array will need you to either get access from a head of staff or security, unless you want to spacewalk around the whole station. Don't be afraid to ask the latter - they're there for you, after all. As for other utilities the air system is a bit different than you'd expect, but fortunately you should have the atmos techs this station needed a long time ago. The disposal network is significantly more complicated, yet more capable. I've already elaborated on it, so I'll let you find and read my write-up for that. As for the routing system, it's just begging to get hit by a stray meteor so consider other utilities a higher priority. \n \nGood luck. You're gonna need it. \n \n-C. Donnelly Architectural Analyst"
/obj/item/paper/guides/cogstation/letter_atmos
name = "To future Atmospheric Technicians:"
@@ -140,15 +142,15 @@ Accidental Reentry:
/obj/item/paper/guides/cogstation/letter_hos
name = "To the future HoS"
- info = "I'm gonna be rather disappointed if CentCom doesn't brief you about this station, but if they don't I wrote up another letter for your department that should cover it pretty well. Make sure your officers read it if they aren't up to speed. \n \nSomething you in particular should know is that if someone's getting to be too much to handle, the boys and I have constructed a 'discount transfer centre' just behind the router. Use it only as a last resort - the walls may be reinforced but they're still thin, and you'll have big trouble on your hands if the AI or any cyborgs find out about it. \n -LC"
+ info = "I'm gonna be rather disappointed if Central Command doesn't brief you about this station, but if they don't I wrote up another letter for your department that should cover it pretty well. Make sure your officers read it if they aren't up to speed. \n \nSomething you in particular should know is that if someone's getting to be too much to handle, the boys and I have constructed a 'discount transfer centre' just behind the router. Use it only as a last resort - the walls may be reinforced but they're still thin, and you'll have big trouble on your hands if the AI or any cyborgs find out about it. \n -LC"
/obj/item/paper/guides/cogstation/letter_supp
name = "To future Supply Staff:"
- info = "Cargo, move freight. Miners, don't die. Your jobs are pretty straightforward, which is likely why they originally fell under Engineering on this station as opposed to their own department. Although we've considerably readjusted this part of the station to accommodate you, there are potential differences you should be aware of. \n \nEngineeringwill have access to some of your department, namely the warehouse and mining dock. Mining operations on this station were originally asteroid-based, hence the catwalk into the great beyond. Although you won't need to worry about being space-worthy due to a newly installed shuttle dock, they might need to get out there. \n \nYou'll have all your usual means of shipping out goods, but the disposal network is more complex with a separate line for mail and trash. I've left another note that explains this in detail, but know trash is the janitor's responsibility, not yours. \n \nThe biggest difference has to be this station's router system, which allows departments to ship goods between themselves. Even if the belts aren't working properly they'll still have their own request consoles, so you'll want to check for orders regularly. \n \n-C. Donnelly \nArchitectural Analyst"
+ info = "Cargo, move freight. Miners, don't die. Your jobs are pretty straightforward, which is likely why they originally fell under Engineering on this station as opposed to their own department. Although we've considerably readjusted this part of the station to accommodate you, there are potential differences you should be aware of. \n \nEngineeringwill have access to some of your department, namely the warehouse and mining dock. Mining operations on this station were originally asteroid-based, hence the catwalk into the great beyond. Although you won't need to worry about being space-worthy due to a newly installed shuttle dock, they might need to get out there. \n \nYou'll have all your usual means of shipping out goods, but the disposal network is more complex with a separate line for mail and trash. It also isn't fully space-proofed, meanin it may not be the best choice for livestock, monkey cubes, or clowns. I've left another note that explains this in detail, but know trash is the janitor's responsibility, not yours. \n \nThe biggest difference has to be this station's router system, which allows departments to ship goods between themselves. Even if the belts aren't working properly they'll still have their own request consoles, so you'll want to check for orders regularly. \n \n-C. Donnelly \nArchitectural Analyst"
-/obj/item/paper/fluff/cogstation/sleepers
- name = "Re: Sleepers?"
- info = "Yes, the sleepers are meant to be publicly accessible. Policies in this station's original location encouraged crew to visit the clinic or treat themselves when it came to minor injuries. \n \nThis is no excuse for you not to do your jobs. You may wish to keep an eye on the sleepers as to ensure they're being used responsibly. Remember, allowing an overdose to happen under your watch isn't much different from administering that overdose yourself. \n \n- Dr. Halley"
+/obj/item/paper/guides/cogstation/letter_med
+ name = "Re: Future Medical Staff"
+ info = "With this station nearing approval for regular use, I've been told to consolidate anything noteworthy about its general medical department into a single document. As you may be able to guess, this is that document. \n \n- First, you should know a medical clinic is present in the civilian (starboard bow) wing. If you have personnel to spare, it's recommended you have someone staff it - that way people with minor injuries can report there instead of clogging up the research wing. \n \n- Despite recent renovations to bring this station in line with regional policy, you'll still find the robotics lab directly adjacent to your department. I advise you take full advantage of this, whether it's requesting prosthetics in advance or harvesting organs from those who have undergone more...permanent procedures. \n \n- Lastly, please make a habit of checking the morgue on a regular basis. Thanks to the Corpse Disposal Network (or CDN for short), you may find the station's deceased delivered directly to you. Some may be employees capable of being revived - more information can be found in the morgue itself. \n \n- Dr. Halley"
/obj/item/paper/fluff/cogstation/cloner
name = "Re: Issue with the cloner?"
@@ -172,7 +174,7 @@ Accidental Reentry:
/obj/item/paper/fluff/cogstation/letter_chap
name = "A message from the DHDA"
- info = "Regardless of what the name leads you to believe, CogStation is neither Ratvarian in origin nor designed by members of this so-called 'clock cult'. Despite a potential common enemy and instances of exhibiting peaceful behavior, their beliefs have been labeled 'Heretical' by the Department of Higher-Dimensional Affairs and following them is grounds for immediate termination. \nAs the station's designated Chaplain, it is advised you correct anyone who claims this station and/or its designers are Ratvarian. While they are most likely misinformed or 'joking around', untruths gain credibility the more they are repeated. \n \nSoulstone Obelisk \n \nDepartment of Higher-Dimensional Affairs"
+ info = "Regardless of what the name leads you to believe, CogStation is neither Ratvarian in origin nor designed by members of this so-called 'clock cult'. Despite a potential common enemy and instances of exhibiting peaceful behavior, their beliefs have been labeled 'Heretical' by the Department of Higher-Dimensional Affairs and following them is grounds for immediate termination. \nAs the station's designated Chaplain, it is advised you correct anyone who claims this station and/or its designers are Ratvarian. While they are most likely misinformed or 'joking around', untruths gain credibility the more they are repeated. \n \nSoulstone Obelisk \n \nDepartment of Higher-Dimensional Affairs"
/obj/item/paper/fluff/cogstation/cluwne
name = "Mysterious Note"
@@ -184,7 +186,7 @@ Accidental Reentry:
/obj/item/paper/fluff/cogstation/eva
name = "MEMO: Spacesuits"
- info = "As a Head of Personnel, you may be familiar with crew members requesting EVA access, particularly when there is an absence of credible threats on the station. While it is your decision to grant or deny access unless overriden by your superior(s), you should be aware of an abundance of spacesuits on this station. While intended for emergencies, these suits are cheaper to replace. You may find it beneficial to direct aspiring 'space explorers' towards finding one of these suits instead, although it is advised you order replacement suits in advance through the cargo department. \n \n-Generated by Organic Resources Bot #2053"
+ info = "As a Head of Personnel, you may be familiar with crew members requesting EVA access, particularly when there is an absence of credible threats on the station. While it is your decision to grant or deny access unless overriden by your superior(s), you should be aware of an abundance of spacesuits on this station. While intended for emergencies, these suits are cheaper to replace. You may find it beneficial to direct aspiring 'space explorers' towards finding one of these suits instead, although it is advised you order replacement suits in advance through the cargo department. \n \nGenerated by Organic Resources Bot #2053"
/obj/item/paper/fluff/cogstation/chemists
name = "Re: Scientists?!"
@@ -214,9 +216,9 @@ Accidental Reentry:
name = "ROUTER STATUS: LIMITED"
info = "Currently, this router cannot receive deliveries from the Airbridge, MedSci, Security, or Service Routers. Cargo and the recycler are the only points currently accepting deliveries from here, although manual input from the routing depot is currently required. \n \n-C. Donnelly \n \nArchitectural Analyst"
-/obj/item/paper/fluff/cogstation/router_cargo
- name = "ROUTER STATUS: VERY LIMITED"
- info = "Currently, this router cannot receive deliveries from the Airbridge, MedSci, Security, or Service Routers. It is not yet capable of making deliveries, beyond sending items to the recycler. \n \n-C. Donnelly \n \nArchitectural Analyst"
+/obj/item/paper/fluff/cogstation/mulebot
+ name = "MEMO: MULEbots"
+ info = "As you may know, MULEbots have been coded to minimize travel distance for maximum efficiency. In the case of this station, that may include travelling through depressurized areas exposed to space. Please bear this in mind before using them to transport living tissue. \n \nGenerated by Organic Resources Bot #2053"
/////////// CentCom
diff --git a/code/modules/paperwork/paperbin.dm b/code/modules/paperwork/paperbin.dm
index 28cb5ffae9..b8bbd0a30e 100644
--- a/code/modules/paperwork/paperbin.dm
+++ b/code/modules/paperwork/paperbin.dm
@@ -4,6 +4,7 @@
icon = 'icons/obj/bureaucracy.dmi'
icon_state = "paper_bin1"
item_state = "sheet-metal"
+ // inhand_icon_state = "sheet-metal"
lefthand_file = 'icons/mob/inhands/misc/sheets_lefthand.dmi'
righthand_file = 'icons/mob/inhands/misc/sheets_righthand.dmi'
throwforce = 0
@@ -43,6 +44,7 @@
..()
/obj/item/paper_bin/MouseDrop(atom/over_object)
+ . = ..()
var/mob/living/M = usr
if(!istype(M) || M.incapacitated() || !Adjacent(M))
return
@@ -54,17 +56,18 @@
var/obj/screen/inventory/hand/H = over_object
M.putItemFromInventoryInHandIfPossible(src, H.held_index)
- else
- . = ..()
-
add_fingerprint(M)
/obj/item/paper_bin/attack_paw(mob/user)
return attack_hand(user)
-/obj/item/paper_bin/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
- if(user.lying)
- return
+//ATTACK HAND IGNORING PARENT RETURN VALUE
+/obj/item/paper_bin/on_attack_hand(mob/user)
+ if(isliving(user))
+ var/mob/living/L = user
+ if(!(L.mobility_flags & MOBILITY_PICKUP))
+ return
+ // user.changeNext_move(CLICK_CD_MELEE)
if(bin_pen)
var/obj/item/pen/P = bin_pen
P.add_fingerprint(user)
@@ -85,8 +88,8 @@
P = new papertype(src)
if(SSevents.holidays && SSevents.holidays[APRIL_FOOLS])
if(prob(30))
- P.info = "*HONK HONK HONK HONK HONK HONK HONK HOOOOOOOOOOOOOOOOOOOOOONK*\n*APRIL FOOLS*\n"
- P.rigged = 1
+ P.info = "HONK HONK HONK HONK HONK HONK HONK HOOOOOOOOOOOOOOOOOOOOOONK APRIL FOOLS"
+ P.AddComponent(/datum/component/honkspam)
P.add_fingerprint(user)
P.forceMove(user.loc)
@@ -148,8 +151,7 @@
papertype = /obj/item/paper/natural
resistance_flags = FLAMMABLE
-/obj/item/paper_bin/bundlenatural/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
- ..()
+/obj/item/paper_bin/bundlenatural/on_attack_hand(mob/user)
if(total_paper < 1)
qdel(src)
@@ -173,3 +175,9 @@
qdel(src)
else
..()
+
+/obj/item/paper_bin/carbon
+ name = "carbon paper bin"
+ desc = "Contains all the paper you'll ever need, in duplicate!"
+ icon_state = "paper_bin_carbon"
+ papertype = /obj/item/paper/carbon
diff --git a/code/modules/paperwork/paperplane.dm b/code/modules/paperwork/paperplane.dm
index c6a1ee1389..53fe886d65 100644
--- a/code/modules/paperwork/paperplane.dm
+++ b/code/modules/paperwork/paperplane.dm
@@ -1,9 +1,9 @@
-
/obj/item/paperplane
name = "paper plane"
desc = "Paper, folded in the shape of a plane."
icon = 'icons/obj/bureaucracy.dmi'
icon_state = "paperplane"
+ // custom_fire_overlay = "paperplane_onfire"
throw_range = 7
throw_speed = 1
throwforce = 0
@@ -11,7 +11,7 @@
resistance_flags = FLAMMABLE
max_integrity = 50
- var/hit_probability = 2//%
+ var/hit_probability = 2 //%
var/obj/item/paper/internalPaper
/obj/item/paperplane/origami
@@ -41,6 +41,13 @@
qdel(src)
return ..()
+/obj/item/paperplane/Exited(atom/movable/AM, atom/newLoc)
+ . = ..()
+ if (AM == internalPaper)
+ internalPaper = null
+ if(!QDELETED(src))
+ qdel(src)
+
/obj/item/paperplane/Destroy()
QDEL_NULL(internalPaper)
return ..()
@@ -71,31 +78,19 @@
user.put_in_hands(internal_paper_tmp)
/obj/item/paperplane/attackby(obj/item/P, mob/living/carbon/human/user, params)
- ..()
+ if(burn_paper_product_attackby_check(P, user))
+ return
if(istype(P, /obj/item/pen) || istype(P, /obj/item/toy/crayon))
- to_chat(user, "You should unfold [src] before changing it.")
+ to_chat(user, "You should unfold [src] before changing it!")
return
else if(istype(P, /obj/item/stamp)) //we don't randomize stamps on a paperplane
internalPaper.attackby(P, user) //spoofed attack to update internal paper.
update_icon()
+ add_fingerprint(user)
+ return
- else if(P.get_temperature())
- if(HAS_TRAIT(user, TRAIT_CLUMSY) && prob(10))
- user.visible_message("[user] accidentally ignites [user.p_them()]self!", \
- "You miss [src] and accidentally light yourself on fire!")
- user.dropItemToGround(P)
- user.adjust_fire_stacks(1)
- user.IgniteMob()
- return
-
- if(!(in_range(user, src))) //to prevent issues as a result of telepathically lighting a paper
- return
- user.dropItemToGround(src)
- user.visible_message("[user] lights [src] ablaze with [P]!", "You light [src] on fire!")
- fire_act()
-
- add_fingerprint(user)
+ return ..()
/obj/item/paperplane/throw_at(atom/target, range, speed, mob/thrower, spin=FALSE, diagonals_first = FALSE, datum/callback/callback)
@@ -112,26 +107,28 @@
if(..() || !ishuman(hit_atom))//if the plane is caught or it hits a nonhuman
return
var/mob/living/carbon/human/H = hit_atom
+ var/obj/item/organ/eyes/eyes = H.getorganslot(ORGAN_SLOT_EYES)
if(prob(hit_probability))
if(H.is_eyes_covered())
return
- var/obj/item/organ/eyes/eyes = H.getorganslot(ORGAN_SLOT_EYES)
- visible_message("\The [src] hits [H] in the eye!")
+ visible_message("\The [src] hits [H] in the eye[eyes ? "" : " socket"]!")
H.adjust_blurriness(6)
- if(eyes)
- eyes.applyOrganDamage(rand(6,8))
+ eyes?.applyOrganDamage(rand(6,8))
H.DefaultCombatKnockdown(40)
H.emote("scream")
-
/obj/item/paper/examine(mob/user)
. = ..()
. += "Alt-click [src] to fold it into a paper plane."
/obj/item/paper/AltClick(mob/living/carbon/user, obj/item/I)
- . = ..()
- if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, ismonkey(user), NO_TK))
+ if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, ismonkey(user)))
return
+ if(istype(src, /obj/item/paper/carbon))
+ var/obj/item/paper/carbon/Carbon = src
+ if(!Carbon.iscopy && !Carbon.copied)
+ to_chat(user, "Take off the carbon copy first.")
+ return
to_chat(user, "You fold [src] into the shape of a plane!")
user.temporarilyRemoveItemFromInventory(src)
var/obj/item/paperplane/plane_type = /obj/item/paperplane
@@ -142,4 +139,3 @@
I = new plane_type(user, src)
user.put_in_hands(I)
- return TRUE
diff --git a/code/modules/paperwork/pen.dm b/code/modules/paperwork/pen.dm
index 08252d4e05..13890ae69e 100644
--- a/code/modules/paperwork/pen.dm
+++ b/code/modules/paperwork/pen.dm
@@ -16,6 +16,8 @@
icon = 'icons/obj/bureaucracy.dmi'
icon_state = "pen"
item_state = "pen"
+ // inhand_icon_state = "pen"
+ // worn_icon_state = "pen"
slot_flags = ITEM_SLOT_BELT | ITEM_SLOT_EARS
throwforce = 0
w_class = WEIGHT_CLASS_TINY
@@ -28,6 +30,7 @@
var/degrees = 0
var/font = PEN_FONT
embedding = list()
+ sharpness = SHARP_POINTY
/obj/item/pen/suicide_act(mob/user)
user.visible_message("[user] is scribbling numbers all over [user.p_them()]self with [src]! It looks like [user.p_theyre()] trying to commit sudoku...")
@@ -75,6 +78,22 @@
icon_state = "pen-fountain"
font = FOUNTAIN_PEN_FONT
+/obj/item/pen/charcoal
+ name = "charcoal stylus"
+ desc = "It's just a wooden stick with some compressed ash on the end. At least it can write."
+ icon_state = "pen-charcoal"
+ colour = "dimgray"
+ font = CHARCOAL_FONT
+ custom_materials = null
+ grind_results = list(/datum/reagent/ash = 5, /datum/reagent/cellulose = 10)
+
+/datum/crafting_recipe/charcoal_stylus
+ name = "Charcoal Stylus"
+ result = /obj/item/pen/charcoal
+ reqs = list(/obj/item/stack/sheet/mineral/wood = 1, /datum/reagent/ash = 30)
+ time = 30
+ category = CAT_PRIMAL
+
/obj/item/pen/fountain/captain
name = "captain's fountain pen"
desc = "It's an expensive Oak fountain pen. The nib is quite sharp."
@@ -84,7 +103,7 @@
throw_speed = 4
colour = "crimson"
custom_materials = list(/datum/material/gold = 750)
- sharpness = IS_SHARP
+ sharpness = SHARP_EDGED
resistance_flags = FIRE_PROOF
unique_reskin = list("Oak" = "pen-fountain-o",
"Gold" = "pen-fountain-g",
@@ -92,6 +111,7 @@
"Black and Silver" = "pen-fountain-b",
"Command Blue" = "pen-fountain-cb"
)
+ embedding = list("embed_chance" = 75)
/obj/item/pen/fountain/captain/Initialize()
. = ..()
@@ -138,20 +158,18 @@
if(QDELETED(O) || !user.canUseTopic(O, BE_CLOSE))
return
if(oldname == input)
- to_chat(user, "You changed \the [O.name] to... well... \the [O.name].")
+ to_chat(user, "You changed \the [O.name] to... well... \the [O.name].")
else
O.name = input
- to_chat(user, "\The [oldname] has been successfully been renamed to \the [input].")
+ to_chat(user, "\The [oldname] has been successfully been renamed to \the [input].")
O.renamedByPlayer = TRUE
- log_game("[user] [key_name(user)] has renamed [O] to [input]")
if(penchoice == "Change description")
var/input = stripped_input(user,"Describe \the [O.name] here", ,"", 100)
if(QDELETED(O) || !user.canUseTopic(O, BE_CLOSE))
return
O.desc = input
- to_chat(user, "You have successfully changed \the [O.name]'s description.")
- log_game("[user] [key_name(user)] has changed [O]'s description to to [input]")
+ to_chat(user, "You have successfully changed \the [O.name]'s description.")
/*
* Sleepypens
@@ -180,9 +198,10 @@
*/
/obj/item/pen/edagger
attack_verb = list("slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut") //these wont show up if the pen is off
- sharpness = IS_SHARP
+ // attack_verb_continuous = list("slashes", "stabs", "slices", "tears", "lacerates", "rips", "dices", "cuts") //these won't show up if the pen is off
+ // attack_verb_simple = list("slash", "stab", "slice", "tear", "lacerate", "rip", "dice", "cut")
+ sharpness = SHARP_EDGED
var/on = FALSE
- embedding = list(embed_chance = EMBED_CHANCE)
/obj/item/pen/edagger/ComponentInitialize()
. = ..()
@@ -192,29 +211,38 @@
/obj/item/pen/edagger/get_sharpness()
return on * sharpness
+/obj/item/pen/edagger/suicide_act(mob/user)
+ . = BRUTELOSS
+ if(on)
+ user.visible_message("[user] forcefully rams the pen into their mouth!")
+ else
+ user.visible_message("[user] is holding a pen up to their mouth! It looks like [user.p_theyre()] trying to commit suicide!")
+ attack_self(user)
+
/obj/item/pen/edagger/attack_self(mob/living/user)
if(on)
on = FALSE
force = initial(force)
+ throw_speed = initial(throw_speed)
w_class = initial(w_class)
name = initial(name)
hitsound = initial(hitsound)
- embedding = null
+ embedding = list(embed_chance = EMBED_CHANCE)
throwforce = initial(throwforce)
- playsound(user, 'sound/weapons/saberoff.ogg', 5, 1)
+ playsound(user, 'sound/weapons/saberoff.ogg', 5, TRUE)
to_chat(user, "[src] can now be concealed.")
- updateEmbedding()
else
on = TRUE
force = 18
+ throw_speed = 4
w_class = WEIGHT_CLASS_NORMAL
name = "energy dagger"
hitsound = 'sound/weapons/blade1.ogg'
- embedding = list(embed_chance = 100, fall_chance = 0) //rule of cool
+ embedding = list(embed_chance = 100) //rule of cool
throwforce = 35
- playsound(user, 'sound/weapons/saberon.ogg', 5, 1)
+ playsound(user, 'sound/weapons/saberon.ogg', 5, TRUE)
to_chat(user, "[src] is now active.")
- updateEmbedding()
+ updateEmbedding()
update_icon()
/obj/item/pen/edagger/update_icon_state()
@@ -234,6 +262,8 @@
icon = 'icons/obj/bureaucracy.dmi'
icon_state = "digging_pen"
item_state = "pen"
+ // inhand_icon_state = "pen"
+ // worn_icon_state = "pen"
force = 3
w_class = WEIGHT_CLASS_TINY
custom_materials = list(/datum/material/iron=10, /datum/material/diamond=100, /datum/material/titanium = 10)
diff --git a/code/modules/paperwork/photocopier.dm b/code/modules/paperwork/photocopier.dm
index 0b1f3bb01d..25a9cf0043 100644
--- a/code/modules/paperwork/photocopier.dm
+++ b/code/modules/paperwork/photocopier.dm
@@ -1,12 +1,20 @@
-/* Photocopiers!
- * Contains:
- * Photocopier
- * Toner Cartridge
- */
-/*
- * Photocopier
- */
+/// For use with the `color_mode` var. Photos will be printed in greyscale while the var has this value.
+#define PHOTO_GREYSCALE "Greyscale"
+/// For use with the `color_mode` var. Photos will be printed in full color while the var has this value.
+#define PHOTO_COLOR "Color"
+
+/// How much toner is used for making a copy of a paper.
+#define PAPER_TONER_USE 0.125
+/// How much toner is used for making a copy of a photo.
+#define PHOTO_TONER_USE 0.625
+/// How much toner is used for making a copy of a document.
+#define DOCUMENT_TONER_USE 0.75
+/// How much toner is used for making a copy of an ass.
+#define ASS_TONER_USE 0.625
+/// The maximum amount of copies you can make with one press of the copy button.
+#define MAX_COPIES_AT_ONCE 10
+
/obj/machinery/photocopier
name = "photocopier"
desc = "Used to copy important documents and anatomy studies."
@@ -19,183 +27,317 @@
power_channel = EQUIP
max_integrity = 300
integrity_failure = 0.33
- var/obj/item/paper/copy = null //what's in the copier!
- var/obj/item/photo/photocopy = null
- var/obj/item/documents/doccopy = null
- var/copies = 1 //how many copies to print!
- var/toner = 40 //how much toner is left! woooooo~
- var/maxcopies = 10 //how many copies can be copied at once- idea shamelessly stolen from bs12's copier!
- var/greytoggle = "Greyscale"
- var/mob/living/ass //i can't believe i didn't write a stupid-ass comment about this var when i first coded asscopy.
+ /// A reference to an `/obj/item/paper` inside the copier, if one is inserted. Otherwise null.
+ var/obj/item/paper/paper_copy
+ /// A reference to an `/obj/item/photo` inside the copier, if one is inserted. Otherwise null.
+ var/obj/item/photo/photo_copy
+ /// A reference to an `/obj/item/documents` inside the copier, if one is inserted. Otherwise null.
+ var/obj/item/documents/document_copy
+ /// A reference to a mob on top of the photocopier trying to copy their ass. Null if there is no mob.
+ var/mob/living/ass
+ /// A reference to the toner cartridge that's inserted into the copier. Null if there is no cartridge.
+ var/obj/item/toner/toner_cartridge
+ /// How many copies will be printed with one click of the "copy" button.
+ var/num_copies = 1
+ /// Used with photos. Determines if the copied photo will be in greyscale or color.
+ var/color_mode = PHOTO_COLOR
+ /// Indicates whether the printer is currently busy copying or not.
var/busy = FALSE
-/obj/machinery/photocopier/ui_interact(mob/user)
+/obj/machinery/photocopier/Initialize()
. = ..()
- var/list/dat = list("Photocopier
")
- if(copy || photocopy || doccopy || (ass && (ass.loc == src.loc)))
- dat += "Remove Paper "
- if(toner)
- dat += "Copy "
- dat += "Printing: [copies] copies."
- dat += "- "
- dat += "+
"
- if(photocopy)
- dat += "Printing in [greytoggle]
"
- else if(toner)
- dat += "Please insert paper to copy.
")
- popup.set_title_image(usr.browse_rsc_icon(src.icon, src.icon_state))
popup.open()
/obj/machinery/computer/custom_shuttle/Topic(href, href_list)
diff --git a/code/modules/shuttle/emergency.dm b/code/modules/shuttle/emergency.dm
index 637d9ae334..810cadcd2c 100644
--- a/code/modules/shuttle/emergency.dm
+++ b/code/modules/shuttle/emergency.dm
@@ -45,11 +45,14 @@
say("Please equip your ID card into your ID slot to authenticate.")
. = ..()
-/obj/machinery/computer/emergency_shuttle/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.human_adjacent_state)
+/obj/machinery/computer/emergency_shuttle/ui_state(mob/user)
+ return GLOB.human_adjacent_state
- ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+/obj/machinery/computer/emergency_shuttle/ui_interact(mob/user, datum/tgui/ui)
+
+ ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
- ui = new(user, src, ui_key, "EmergencyShuttleConsole", name, ui_x, ui_y, master_ui, state)
+ ui = new(user, src, "EmergencyShuttleConsole", name)
ui.open()
/obj/machinery/computer/emergency_shuttle/ui_data()
@@ -65,8 +68,8 @@
var/job = ID.assignment
if(obj_flags & EMAGGED)
- name = Gibberish(name, 0)
- job = Gibberish(job, 0)
+ name = Gibberish(name)
+ job = Gibberish(job)
A += list(list("name" = name, "job" = job))
data["authorizations"] = A
diff --git a/code/modules/shuttle/shuttle_creation/shuttle_creator.dm b/code/modules/shuttle/shuttle_creation/shuttle_creator.dm
index f5a11db60f..b3d99f22ab 100644
--- a/code/modules/shuttle/shuttle_creation/shuttle_creator.dm
+++ b/code/modules/shuttle/shuttle_creation/shuttle_creator.dm
@@ -43,6 +43,7 @@ GLOBAL_LIST_EMPTY(custom_shuttle_machines) //Machines that require updating (He
. = ..()
internal_shuttle_creator = new()
internal_shuttle_creator.owner_rsd = src
+ desc += " Attention, the max size of the shuttle is [SHUTTLE_CREATOR_MAX_SIZE]."
overlay_holder = new()
/obj/item/shuttle_creator/Destroy()
@@ -237,13 +238,13 @@ GLOBAL_LIST_EMPTY(custom_shuttle_machines) //Machines that require updating (He
port.register()
- icon_state = "rsd_used"
+ icon_state = "rsd_empty"
//Clear highlights
overlay_holder.clear_highlights()
GLOB.custom_shuttle_count ++
- message_admins("[ADMIN_LOOKUPFLW(user)] created a new shuttle with a [src] at [ADMIN_VERBOSEJMP(user)] ([GLOB.custom_shuttle_count] custom shuttles, limit is [CUSTOM_SHUTTLE_LIMIT])")
- log_game("[key_name(user)] created a new shuttle with a [src] at [AREACOORD(user)] ([GLOB.custom_shuttle_count] custom shuttles, limit is [CUSTOM_SHUTTLE_LIMIT])")
+ message_admins("[ADMIN_LOOKUPFLW(user)] created a new shuttle with a [src] at [ADMIN_VERBOSEJMP(user)] ([GLOB.custom_shuttle_count] custom shuttles)")
+ log_game("[key_name(user)] created a new shuttle with a [src] at [AREACOORD(user)] ([GLOB.custom_shuttle_count] custom shuttles)")
return TRUE
/obj/item/shuttle_creator/proc/create_shuttle_area(mob/user)
@@ -350,7 +351,7 @@ GLOBAL_LIST_EMPTY(custom_shuttle_machines) //Machines that require updating (He
loggedOldArea = get_area(get_turf(user))
loggedTurfs |= turfs
overlay_holder.highlight_area(turfs)
- //TODO READD THIS SHIT: icon_state = "rsd_used"
+ //TODO READD THIS SHIT: icon_state = "rsd_empty"
to_chat(user, "You add the area into the buffer of the [src], you made add more areas or select an airlock to act as a docking port to complete the shuttle.")
return turfs
diff --git a/code/modules/smithing/anvil.dm b/code/modules/smithing/anvil.dm
new file mode 100644
index 0000000000..19d48119e6
--- /dev/null
+++ b/code/modules/smithing/anvil.dm
@@ -0,0 +1,315 @@
+#define WORKPIECE_PRESENT 1
+#define WORKPIECE_INPROGRESS 2
+#define WORKPIECE_FINISHED 3
+#define WORKPIECE_SLAG 5
+
+#define RECIPE_SMALLPICK "dbp" //draw bend punch
+#define RECIPE_LARGEPICK "ddbp" //draw draw bend punch
+#define RECIPE_SHOVEL "dfup" //draw fold upset punch
+#define RECIPE_HAMMER "sfp" //shrink fold punch
+
+
+#define RECIPE_SMALLKNIFE "sdd" //shrink draw draw
+#define RECIPE_SHORTSWORD "dff" //draw fold fold
+#define RECIPE_WAKI "dfsf" //draw fold shrink fold
+#define RECIPE_SCIMITAR "dfb" //draw fold bend
+#define RECIPE_SABRE "ddsf" //draw draw shrink fold
+#define RECIPE_RAPIER "sdfd" //shrink draw fold draw
+#define RECIPE_BROADSWORD "dfuf" //draw fold upset fold
+#define RECIPE_ZWEIHANDER "udfsf" //upset draw fold shrink fold
+#define RECIPE_KATANA "fffff" //fold fold fold fold fold
+
+
+#define RECIPE_SCYTHE "bdf" //bend draw fold
+#define RECIPE_COGHEAD "bsf" //bend shrink fold.
+
+
+#define RECIPE_JAVELIN "dbf" //draw bend fold
+#define RECIPE_HALBERD "duffp" //draw upset fold fold punch
+#define RECIPE_GLAIVE "usfp" //upset shrink fold punch
+#define RECIPE_PIKE "ddbf" //draw draw bend fold
+
+/obj/structure/anvil
+ name = "anvil"
+ desc = "Base class of anvil. This shouldn't exist, but is useable."
+ icon = 'icons/obj/smith.dmi'
+ icon_state = "anvil"
+ density = TRUE
+ anchored = TRUE
+ var/workpiece_state = FALSE
+ var/datum/material/workpiece_material
+ var/anvilquality = 0
+ var/currentquality = 0 //lolman? what the fuck do these vars do?
+ var/currentsteps = 0 //even i don't know
+ var/outrightfailchance = 1 //todo: document this shit
+ var/stepsdone = ""
+ var/rng = FALSE
+ var/debug = FALSE //vv this if you want an artifact
+ var/artifactrolled = FALSE
+ var/itemqualitymax = 20
+ var/list/smithrecipes = list(RECIPE_HAMMER = /obj/item/smithing/hammerhead,
+ RECIPE_SCYTHE = /obj/item/smithing/scytheblade,
+ RECIPE_SHOVEL = /obj/item/smithing/shovelhead,
+ RECIPE_COGHEAD = /obj/item/smithing/cogheadclubhead,
+ RECIPE_JAVELIN = /obj/item/smithing/javelinhead,
+ RECIPE_LARGEPICK = /obj/item/smithing/pickaxehead,
+ RECIPE_SMALLPICK = /obj/item/smithing/prospectingpickhead,
+ RECIPE_SHORTSWORD = /obj/item/smithing/shortswordblade,
+ RECIPE_SCIMITAR = /obj/item/smithing/scimitarblade,
+ RECIPE_WAKI = /obj/item/smithing/wakiblade,
+ RECIPE_RAPIER = /obj/item/smithing/rapierblade,
+ RECIPE_SABRE = /obj/item/smithing/sabreblade,
+ RECIPE_SMALLKNIFE = /obj/item/smithing/knifeblade,
+ RECIPE_BROADSWORD = /obj/item/smithing/broadblade,
+ RECIPE_ZWEIHANDER = /obj/item/smithing/zweiblade,
+ RECIPE_KATANA = /obj/item/smithing/katanablade,
+ RECIPE_HALBERD = /obj/item/smithing/halberdhead,
+ RECIPE_GLAIVE = /obj/item/smithing/glaivehead,
+ RECIPE_PIKE = /obj/item/smithing/pikehead)
+
+/obj/structure/anvil/Initialize()
+ ..()
+ currentquality = anvilquality
+
+/obj/structure/anvil/attackby(obj/item/I, mob/user)
+ if(istype(I, /obj/item/ingot))
+ var/obj/item/ingot/notsword = I
+ if(workpiece_state)
+ to_chat(user, "There's already a workpiece! Finish it or take it off.")
+ return FALSE
+ if(notsword.workability == "shapeable")
+ workpiece_state = WORKPIECE_PRESENT
+ workpiece_material = notsword.custom_materials
+ to_chat(user, "You place the [notsword] on the [src].")
+ currentquality = anvilquality
+ var/skillmod = 0
+ if(user.mind.skill_holder)
+ skillmod = user.mind.get_skill_level(/datum/skill/level/dorfy/blacksmithing)/2
+ currentquality += skillmod
+ qdel(notsword)
+ else
+ to_chat(user, "The ingot isn't workable yet!")
+ return FALSE
+ return
+ else if(istype(I, /obj/item/melee/smith/hammer))
+ var/obj/item/melee/smith/hammer/hammertime = I
+ if(workpiece_state == WORKPIECE_PRESENT || workpiece_state == WORKPIECE_INPROGRESS)
+ do_shaping(user, hammertime.qualitymod)
+ return
+ else
+ to_chat(user, "You can't work an empty anvil!")
+ return FALSE
+ return ..()
+
+/obj/structure/anvil/wrench_act(mob/living/user, obj/item/I)
+ ..()
+ default_unfasten_wrench(user, I, 5)
+ return TRUE
+
+
+/obj/structure/anvil/proc/do_shaping(mob/user, var/qualitychange)
+ currentquality += qualitychange
+ var/list/shapingsteps = list("weak hit", "strong hit", "heavy hit", "fold", "draw", "shrink", "bend", "punch", "upset") //weak/strong/heavy hit affect strength. All the other steps shape.
+ workpiece_state = WORKPIECE_INPROGRESS
+ var/stepdone = input(user, "How would you like to work the metal?") in shapingsteps
+ var/steptime = 50
+ if(user.mind.skill_holder)
+ var/skillmod = user.mind.get_skill_level(/datum/skill/level/dorfy/blacksmithing)/10 + 1
+ steptime = 50 / skillmod
+ playsound(src, 'sound/effects/clang2.ogg',40, 2)
+ if(!do_after(user, steptime, target = src))
+ return FALSE
+ switch(stepdone)
+ if("weak hit")
+ currentsteps += 1
+ outrightfailchance += 5
+ currentquality += 1
+ if("strong hit")
+ currentsteps += 2
+ outrightfailchance += 9.5
+ currentquality += 2
+ if("heavy hit")
+ currentsteps += 3
+ outrightfailchance += 12.5
+ currentquality += 3
+ if("fold")
+ stepsdone += "f"
+ currentsteps += 1
+ currentquality -= 1
+ if("draw")
+ stepsdone += "d"
+ currentsteps += 1
+ currentquality -= 1
+ if("shrink")
+ stepsdone += "s"
+ currentsteps += 1
+ currentquality -= 1
+ if("bend")
+ stepsdone += "b"
+ currentsteps += 1
+ currentquality -= 1
+ if("punch")
+ stepsdone += "p"
+ currentsteps += 1
+ currentquality -= 1
+ if("upset")
+ stepsdone += "u"
+ currentsteps += 1
+ currentquality -= 1
+ user.visible_message("[user] works the metal on the anvil with their hammer with a loud clang!", \
+ "You [stepdone] the metal with a loud clang!")
+ playsound(src, 'sound/effects/clang2.ogg',40, 2)
+ addtimer(CALLBACK(GLOBAL_PROC, .proc/playsound, src, 'sound/effects/clang2.ogg', 40, 2), 15)
+ if(length(stepsdone) >= 3)
+ tryfinish(user)
+
+/obj/structure/anvil/proc/tryfinish(mob/user)
+ var/artifactchance = 0
+ if(!artifactrolled)
+ artifactchance = (1+(user.mind.get_skill_level(/datum/skill/level/dorfy/blacksmithing)/4))/2500
+ artifactrolled = TRUE
+ var/artifact = max(prob(artifactchance), debug)
+ var/finalfailchance = outrightfailchance
+ if(user.mind.skill_holder)
+ var/skillmod = user.mind.get_skill_level(/datum/skill/level/dorfy/blacksmithing)/10 + 1
+ finalfailchance = max(0, finalfailchance / skillmod) //lv 2 gives 20% less to fail, 3 30%, etc
+ if((currentsteps > 10 || (rng && prob(finalfailchance))) && !artifact)
+ to_chat(user, "You overwork the metal, causing it to turn into useless slag!")
+ var/turf/T = get_turf(user)
+ workpiece_state = FALSE
+ new /obj/item/stack/ore/slag(T)
+ currentquality = anvilquality
+ stepsdone = ""
+ currentsteps = 0
+ outrightfailchance = 1
+ artifactrolled = FALSE
+ if(user.mind.skill_holder)
+ user.mind.auto_gain_experience(/datum/skill/level/dorfy/blacksmithing, 25, 400, silent = FALSE)
+ for(var/i in smithrecipes)
+ if(i == stepsdone)
+ var/turf/T = get_turf(user)
+ var/obj/item/smithing/create = smithrecipes[stepsdone]
+ var/obj/item/smithing/finisheditem = new create(T)
+ to_chat(user, "You finish your [finisheditem]!")
+ if(artifact)
+ to_chat(user, "It is an artifact, a creation whose legacy shall live on forevermore.") //todo: SSblackbox
+ currentquality = max(currentquality, 2)
+ finisheditem.quality = currentquality * 3//this is insane i know it's 1/2500 for most of the time and 0.8% at best
+ finisheditem.artifact = TRUE
+ else
+ finisheditem.quality = min(currentquality, itemqualitymax)
+ switch(finisheditem.quality)
+ if(-1000 to -8)
+ finisheditem.desc = "It looks to be the most awfully made object you've ever seen."
+ if(-8)
+ finisheditem.desc = "It looks to be the second most awfully made object you've ever seen."
+ if(-8 to 0)
+ finisheditem.desc = "It looks to be barely passable as... whatever it's trying to pass for."
+ if(0)
+ finisheditem.desc = "It looks to be totally average."
+ if(0 to INFINITY)
+ finisheditem.desc = "It looks to be better than average."
+ workpiece_state = FALSE
+ finisheditem.set_custom_materials(workpiece_material)
+ currentquality = anvilquality
+ stepsdone = ""
+ currentsteps = 0
+ outrightfailchance = 1
+ artifactrolled = FALSE
+ if(user.mind.skill_holder)
+ user.mind.auto_gain_experience(/datum/skill/level/dorfy/blacksmithing, 50, 10000000, silent = FALSE)
+ break
+
+/obj/structure/anvil/debugsuper
+ name = "super ultra epic anvil of debugging."
+ desc = "WOW. A DEBUG ITEM STRUCTURE. EPIC."
+ icon_state = "anvil"
+ anvilquality = 10
+ itemqualitymax = 9001
+ outrightfailchance = 0
+
+/obj/structure/anvil/obtainable
+ name = "anvil"
+ desc = "Base class of anvil. This shouldn't exist, but is useable."
+ anvilquality = 0
+ outrightfailchance = 5
+ rng = TRUE
+
+/obj/structure/anvil/obtainable/table
+ name = "table anvil"
+ desc = "A slightly reinforced table. Good luck."
+ icon_state = "tablevil"
+ anvilquality = -2
+ itemqualitymax = 0
+
+
+/obj/structure/anvil/obtainable/table/do_shaping(mob/user, var/qualitychange)
+ if(prob(5))
+ to_chat(user, "The [src] breaks under the strain!")
+ take_damage(max_integrity)
+ return FALSE
+ else
+ ..()
+
+/obj/structure/anvil/obtainable/bronze
+ name = "slab of bronze"
+ desc = "A big block of bronze. Useable as an anvil."
+ custom_materials = list(/datum/material/bronze=8000)
+ icon_state = "ratvaranvil"
+ anvilquality = -0.5
+ itemqualitymax = 2
+
+/obj/structure/anvil/obtainable/sandstone
+ name = "sandstone brick anvil"
+ desc = "A big block of sandstone. Useable as an anvil."
+ custom_materials = list(/datum/material/sandstone=8000)
+ icon_state = "sandvil"
+ anvilquality = -1
+ itemqualitymax = 2
+
+/obj/structure/anvil/obtainable/basalt
+ name = "basalt brick anvil"
+ desc = "A big block of basalt. Useable as an anvil, better than sandstone. Igneous!"
+ icon_state = "sandvilnoir"
+ anvilquality = -0.5
+ itemqualitymax = 4
+
+/obj/structure/anvil/obtainable/basic
+ name = "anvil"
+ desc = "An anvil. It's got wheels bolted to the bottom."
+ anvilquality = 0
+ itemqualitymax = 6
+
+/obj/structure/anvil/obtainable/ratvar
+ name = "brass anvil"
+ desc = "A big block of what appears to be brass. Useable as an anvil, if whatever's holding the brass together lets you."
+ custom_materials = list(/datum/material/bronze=8000)
+ icon_state = "ratvaranvil"
+ anvilquality = 1
+ itemqualitymax = 8
+
+/obj/structure/anvil/obtainable/ratvar/attackby(obj/item/I, mob/user)
+ if(is_servant_of_ratvar(user))
+ return ..()
+ else
+ to_chat(user, "KNPXWN, QNJCQNW!") //rot13 then rot22 if anyone wants to decode
+
+/obj/structure/anvil/obtainable/narsie
+ name = "runic anvil"
+ desc = "An anvil made of a strange, runic metal."
+ custom_materials = list(/datum/material/runedmetal=8000)
+ icon = 'icons/obj/smith.dmi'
+ icon_state = "evil"
+ anvilquality = 1
+ itemqualitymax = 8
+
+/obj/structure/anvil/obtainable/narsie/attackby(obj/item/I, mob/user)
+ if(iscultist(user))
+ return ..()
+ else
+ to_chat(user, "That is not yours to use!")
+
+#undef WORKPIECE_PRESENT
+#undef WORKPIECE_INPROGRESS
+#undef WORKPIECE_FINISHED
+#undef WORKPIECE_SLAG
diff --git a/code/modules/smithing/finished_items.dm b/code/modules/smithing/finished_items.dm
new file mode 100644
index 0000000000..5db6b09452
--- /dev/null
+++ b/code/modules/smithing/finished_items.dm
@@ -0,0 +1,329 @@
+
+/obj/item/melee/smith
+ name = "base class obj/item/melee/smith" //tin. handles overlay and quality and shit.
+ desc = "cringe"
+ icon = 'icons/obj/smith.dmi'
+ icon_state = "claymore"
+ item_state = "claymore"
+ material_flags = MATERIAL_COLOR | MATERIAL_AFFECT_STATISTICS
+ total_mass = TOTAL_MASS_MEDIEVAL_WEAPON //yeah ok
+ slot_flags = ITEM_SLOT_BELT
+ w_class = WEIGHT_CLASS_NORMAL
+ force = 6
+ lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi'
+ var/quality
+ var/overlay_state = "stick"
+ var/mutable_appearance/overlay
+ var/wielded_mult = 1
+ var/wield_force = 15
+
+/obj/item/melee/smith/Initialize()
+ ..()
+ if(desc == "cringe")
+ desc = "A handmade [name]."
+ overlay = mutable_appearance(icon, overlay_state)
+ overlay.appearance_flags = RESET_COLOR
+ add_overlay(overlay)
+ if(force < 0)
+ force = 0
+
+
+/obj/item/melee/smith/twohand
+ item_flags = NEEDS_PERMIT //it's a bigass sword/spear. beepsky is going to give you shit for it.
+ sharpness = SHARP_EDGED
+ material_flags = MATERIAL_COLOR | MATERIAL_AFFECT_STATISTICS
+ force = 10
+ wielded_mult = 1.75
+ w_class = WEIGHT_CLASS_BULKY
+
+/obj/item/melee/smith/twohand/ComponentInitialize()
+ . = ..()
+ AddComponent(/datum/component/butchering, 100, 70) //decent in a pinch, but pretty bad.
+ AddElement(/datum/element/sword_point)
+
+
+
+///////////////////////////
+// Mining //
+///////////////////////////
+/obj/item/mining_scanner/prospector
+ name = "prospector's pickaxe"
+ desc = "A pickaxe that can sound rocks to find mineral deposits and stop gibtonite detonations."
+ material_flags = MATERIAL_COLOR | MATERIAL_AFFECT_STATISTICS
+ icon = 'icons/obj/smith.dmi'
+ icon_state = "minipick" //todo:sprite
+ sharpness = SHARP_POINTY//it doesnt have a blade it has a point
+
+/obj/item/mining_scanner/prospector/Initialize()
+ ..()
+ var/mutable_appearance/overlay
+ desc = "A handmade [name]."
+ overlay = mutable_appearance(icon, "minihandle")
+ overlay.appearance_flags = RESET_COLOR
+ add_overlay(overlay)
+ if(force < 0)
+ force = 0
+
+/obj/item/pickaxe/smithed
+ name = "pickaxe"
+ desc = "A pickaxe."
+ material_flags = MATERIAL_COLOR | MATERIAL_AFFECT_STATISTICS
+ icon = 'icons/obj/smith.dmi'
+ icon_state = "pickaxe"
+ sharpness = SHARP_POINTY
+
+/obj/item/pickaxe/smithed/Initialize()
+ ..()
+ desc = "A handmade [name]."
+ var/mutable_appearance/overlay
+ overlay = mutable_appearance(icon, "stick")
+ overlay.appearance_flags = RESET_COLOR
+ add_overlay(overlay)
+ if(force < 0)
+ force = 0
+
+/obj/item/pickaxe/smithed/attack_self(mob/user)
+ to_chat(user, "Tool does not have a configureable dig range.")
+
+/obj/item/shovel/smithed
+ name = "shovel"
+ desc = "A shovel."
+ icon = 'icons/obj/smith.dmi'
+ material_flags = MATERIAL_COLOR | MATERIAL_AFFECT_STATISTICS
+ icon_state = "shovel"
+ sharpness = SHARP_EDGED //it cuts through the earth
+
+/obj/item/shovel/smithed/Initialize()
+ ..()
+ desc = "A handmade [name]."
+ var/mutable_appearance/overlay
+ overlay = mutable_appearance(icon, "shovelhandle")
+ overlay.appearance_flags = RESET_COLOR
+ add_overlay(overlay)
+ if(force < 0)
+ force = 0
+
+
+///////////////////////////
+// Spears //
+///////////////////////////
+
+
+/obj/item/melee/smith/twohand/halberd
+ name = "halberd"
+ icon_state = "halberd"
+ w_class = WEIGHT_CLASS_HUGE
+ overlay_state = "spearhandle"
+ slot_flags = ITEM_SLOT_BACK
+ wielded_mult = 2.5
+
+/obj/item/melee/smith/twohand/halberd/ComponentInitialize()
+ . = ..()
+ AddComponent(/datum/component/jousting)
+
+/obj/item/melee/smith/twohand/javelin
+ name = "javelin"
+ icon_state = "javelin"
+ overlay_state = "longhandle"
+ wielded_mult = 1.5
+ slot_flags = ITEM_SLOT_BACK
+ sharpness = SHARP_POINTY
+
+
+/obj/item/melee/smith/twohand/javelin/ComponentInitialize()
+ . = ..()
+ AddComponent(/datum/component/jousting)
+
+/obj/item/melee/smith/twohand/glaive
+ name = "glaive"
+ icon_state = "glaive"
+ overlay_state = "longhandle"
+ slot_flags = ITEM_SLOT_BACK
+ wielded_mult = 2
+
+/obj/item/melee/smith/twohand/glaive/ComponentInitialize()
+ . = ..()
+ AddComponent(/datum/component/jousting)
+
+
+/obj/item/melee/smith/twohand/pike
+ name = "pike"
+ icon_state = "pike"
+ w_class = WEIGHT_CLASS_HUGE
+ overlay_state = "longhandle"
+ reach = 2 //yeah ok
+ slot_flags = ITEM_SLOT_BACK
+ sharpness = SHARP_POINTY
+
+//////////////////////////
+// Other Melee //
+///////////////////////////
+
+
+/obj/item/melee/smith/hammer//blacksmithing, not warhammer.
+ name = "hammer"
+ icon_state = "hammer"
+ overlay_state = "hammerhandle"
+ var/qualitymod = 0
+
+/obj/item/scythe/smithed //we need to inherit scythecode, but that's about it.
+ material_flags = MATERIAL_COLOR | MATERIAL_AFFECT_STATISTICS
+
+
+/obj/item/melee/smith/cogheadclub
+ name = "coghead club"
+ icon_state = "coghead"
+ item_flags = NEEDS_PERMIT
+ overlay_state = "stick"
+
+/obj/item/melee/smith/shortsword
+ name = "gladius"
+ force = 9
+ item_flags = NEEDS_PERMIT
+ sharpness = SHARP_EDGED
+ icon_state = "gladius"
+ overlay_state = "gladiushilt"
+
+/obj/item/melee/smith/shortsword/scimitar
+ name = "scimitar"
+ sharpness = SHARP_EDGED
+ icon_state = "scimitar"
+ overlay_state = "scimitarhilt"
+
+/obj/item/melee/smith/wakizashi
+ name = "wakizashi"
+ sharpness = SHARP_EDGED
+ force = 7
+ item_flags = NEEDS_PERMIT | ITEM_CAN_PARRY
+ obj_flags = UNIQUE_RENAME
+ icon_state = "waki"
+ overlay_state = "wakihilt"
+ block_parry_data = /datum/block_parry_data/waki
+
+/datum/block_parry_data/waki //like longbokken but worse reflect
+ parry_stamina_cost = 6
+ parry_time_windup = 0
+ parry_time_active = 15 //decent window
+ parry_time_spindown = 0
+ parry_time_perfect = 2
+ parry_time_perfect_leeway = 0.75
+ parry_imperfect_falloff_percent = 7.5
+ parry_efficiency_to_counterattack = 100
+ parry_efficiency_considered_successful = 80
+ parry_efficiency_perfect = 120
+ parry_failed_stagger_duration = 3 SECONDS
+ parry_data = list(PARRY_COUNTERATTACK_MELEE_ATTACK_CHAIN = 1.9)
+
+/obj/item/melee/smith/twohand/broadsword
+ name = "broadsword"
+ icon_state = "broadsword"
+ overlay_state = "broadhilt"
+ wielded_mult = 1.8
+
+/obj/item/melee/smith/twohand/zweihander
+ name = "zweihander"
+ icon_state = "zwei"
+ overlay_state = "zweihilt"
+ total_mass = TOTAL_MASS_MEDIEVAL_WEAPON * 2
+ force = 4
+ wielded_mult = 3 //affected more by quality. a -1 is 25% less damage, a +1 is 25% more. These bonuses are tripled when wielded.
+
+/obj/item/melee/smith/twohand/katana
+ name = "katana"
+ icon_state = "katana"
+ overlay_state = "katanahilt"
+ force = 7
+ wielded_mult = 2
+ item_flags = ITEM_CAN_PARRY | NEEDS_PERMIT //want to name your katana "DEMON BLADE" or some shit? go ahead, idiot.
+ obj_flags = UNIQUE_RENAME
+ block_parry_data = /datum/block_parry_data/captain_saber //todo
+
+/obj/item/melee/smith/sabre
+ name = "sabre"
+ icon_state = "sabre"
+ sharpness = SHARP_EDGED
+ overlay_state = "sabrehilt"
+ armour_penetration = 15
+ force = 9
+ hitsound = 'sound/weapons/rapierhit.ogg'
+ item_flags = NEEDS_PERMIT | ITEM_CAN_PARRY
+ block_parry_data = /datum/block_parry_data/captain_saber //yeah this is fine i guess
+
+/obj/item/melee/smith/sabre/rapier
+ name = "rapier"
+ icon_state = "rapier"
+ sharpness = SHARP_EDGED
+ overlay_state = "rapierhilt"
+ force = 6 //less force, stronger parry
+ sharpness = SHARP_POINTY
+ armour_penetration = 30
+ block_parry_data = /datum/block_parry_data/smithrapier
+
+/datum/block_parry_data/smithrapier //parry into riposte. i am pretty sure this is going to be nearly fucking impossible to land.
+ parry_stamina_cost = 12 //dont miss
+ parry_time_active = 4
+ parry_time_perfect = 2
+ parry_time_perfect_leeway = 2
+ parry_failed_stagger_duration = 3 SECONDS
+ parry_failed_clickcd_duration = 3 SECONDS
+ parry_time_windup = 0
+ parry_time_spindown = 0
+ parry_imperfect_falloff_percent = 0
+ parry_efficiency_to_counterattack = 100
+ parry_efficiency_considered_successful = 120
+ parry_efficiency_perfect = 120
+ parry_data = list(PARRY_COUNTERATTACK_MELEE_ATTACK_CHAIN = 4)
+
+//unique hammers
+/obj/item/melee/smith/hammer/toolbox
+ name = "toolbox hammer"
+ desc = "A metal filled toolbox on a stick. Useable as a really shitty hammer."
+ w_class = WEIGHT_CLASS_BULKY
+ icon_state = "toolbox"
+ overlay_state = "hammerhandle"
+ qualitymod = -2
+
+/obj/item/melee/smith/hammer/narsie
+ name = "runemetal hammer"
+ custom_materials = list(/datum/material/runedmetal = 12000)
+ desc = "A metal hammer inscribed with geometeric runes."
+ qualitymod = 1
+
+/obj/item/melee/smith/hammer/narsie/attack(mob/living/target, mob/living/carbon/human/user)
+ if(!iscultist(user))
+ user.DefaultCombatKnockdown(100)
+ user.dropItemToGround(src, TRUE)
+ user.visible_message("A powerful force shoves [user] away from [target]!", \
+ "\"You shouldn't be touching tools that aren't yours.\"")
+ if(ishuman(user))
+ var/mob/living/carbon/human/H = user
+ H.apply_damage(rand(force/2, force), BRUTE, pick(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM))
+ else
+ user.adjustBruteLoss(rand(force/2,force))
+ return
+ ..()
+
+/obj/item/melee/smith/hammer/ratvar
+ name = "brass hammer"
+ custom_materials = list(/datum/material/bronze = 12000)
+ desc = "A brass hammer inscribed with... writing? You can't read it."
+ qualitymod = 1
+
+/obj/item/melee/smith/hammer/ratvar/attack(mob/living/target, mob/living/carbon/human/user)
+ if(!is_servant_of_ratvar(user))
+ user.DefaultCombatKnockdown(100)
+ user.dropItemToGround(src, TRUE)
+ user.visible_message("A powerful force shoves [user] away from [target]!", "\"You shouldn't be touching tools that aren't yours.\"")
+ if(ishuman(user))
+ var/mob/living/carbon/human/H = user
+ H.apply_damage(rand(force/2, force), BRUTE, pick(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM))
+ else
+ user.adjustBruteLoss(rand(force/2,force))
+ return
+ ..()
+
+/obj/item/melee/smith/hammer/debug
+ name = "debugging hammer"
+ desc = "A DEBUGGING HAMMER!! EPIC!!."
+ qualitymod = 10
diff --git a/code/modules/smithing/furnace.dm b/code/modules/smithing/furnace.dm
new file mode 100644
index 0000000000..ee1d6a1b93
--- /dev/null
+++ b/code/modules/smithing/furnace.dm
@@ -0,0 +1,75 @@
+/obj/structure/furnace
+ name = "furnace"
+ desc = "A furnace."
+ icon = 'icons/obj/smith.dmi'
+ icon_state = "furnace0"
+ density = TRUE
+ anchored = TRUE
+ var/debug = FALSE //debugging only
+ var/working = TRUE
+ var/fueluse = 1
+
+
+/obj/structure/furnace/Initialize()
+ ..()
+ create_reagents(250, TRANSPARENT)
+ START_PROCESSING(SSobj, src)
+
+/obj/structure/furnace/Destroy()
+ ..()
+ STOP_PROCESSING(SSobj, src)
+
+/obj/structure/furnace/process()
+ if(debug)
+ reagents.add_reagent(/datum/reagent/fuel, 1)
+ return TRUE
+ if(reagents.remove_reagent(/datum/reagent/fuel, fueluse))
+ working = TRUE
+ if(icon_state == "furnace0")
+ icon_state = "furnace1"
+ else
+ working = FALSE
+ icon_state = "furnace0"
+
+/obj/structure/furnace/attackby(obj/item/I, mob/user)
+ if(istype(I, /obj/item/ingot))
+ var/obj/item/ingot/notsword = I
+ if(working)
+ to_chat(user, "You heat the [notsword] in the [src].")
+ notsword.workability = "shapeable"
+ else
+ to_chat(user, "The furnace isn't working!.")
+ else
+ ..()
+
+/obj/structure/furnace/wrench_act(mob/living/user, obj/item/I)
+ ..()
+ default_unfasten_wrench(user, I, 5)
+ return TRUE
+
+/obj/structure/furnace/attackby(obj/item/W, mob/user, params)
+ if(W.reagents)
+ W.reagents.trans_to(src, 250)
+ else
+ return ..()
+
+/obj/structure/furnace/plunger_act(obj/item/plunger/P, mob/living/user, reinforced)
+ to_chat(user, "You start furiously plunging [name].")
+ if(do_after(user, 30, target = src))
+ to_chat(user, "You finish plunging the [name].")
+ reagents.reaction(get_turf(src), TOUCH) //splash on the floor
+ reagents.clear_reagents()
+
+/obj/structure/furnace/infinite
+ name = "fuelless furnace"
+ debug = TRUE
+ icon_state = "ratfurnace"
+
+
+/obj/structure/furnace/infinite/ratvar
+ name = "brass furnace"
+ desc = "A brass furnace. Powered by... something, but seems otherwise safe." //todo:sprites they're safe for noncultists because you're just putting ingots in them. also there';s a reason to steal them ig
+
+/obj/structure/furnace/infinite/narsie
+ name = "rune furnace"
+ desc = "A runed furnace. Powered by... something, but seems otherwise safe."
diff --git a/code/modules/smithing/smithed_items.dm b/code/modules/smithing/smithed_items.dm
new file mode 100644
index 0000000000..6d10d33a75
--- /dev/null
+++ b/code/modules/smithing/smithed_items.dm
@@ -0,0 +1,419 @@
+/obj/item/basaltblock
+ name = "basalt block"
+ desc = "A block of basalt."
+ icon = 'icons/obj/smith.dmi'
+ icon_state = "sandvilnoir"
+
+
+/obj/item/smithing
+ name = "base class /obj/item/smithing"
+ icon = 'icons/obj/smith.dmi'
+ icon_state = "unfinished"
+ material_flags = MATERIAL_COLOR | MATERIAL_ADD_PREFIX
+ var/quality = 0 //quality. Changed by the smithing process.
+ var/obj/item/finishingitem = /obj/item/stick //What this item needs to be hit by to create finalitem
+ var/obj/item/finalitem
+ var/artifact = FALSE
+
+/obj/item/ingot
+ name = "ingot"
+ icon = 'icons/obj/smith.dmi'
+ icon_state = "ingot"
+ material_flags = MATERIAL_COLOR | MATERIAL_ADD_PREFIX
+ var/workability = 0
+
+
+/obj/item/ingot/on_attack_hand(mob/user)
+ var/mob/living/carbon/human/H
+ if(workability != "shapeable")
+ return ..()
+ var/prot = 0
+ if(ishuman(user))
+ H = user
+ if(H.gloves)
+ var/obj/item/clothing/gloves/G = H.gloves
+ if(G.max_heat_protection_temperature)
+ prot = (G.max_heat_protection_temperature > 360)
+ else
+ prot = 1
+ if(prot > 0 || HAS_TRAIT(user, TRAIT_RESISTHEAT) || HAS_TRAIT(user, TRAIT_RESISTHEATHANDS))
+ to_chat(user, "You pick up the [src].")
+ return ..()
+ else
+ to_chat(user, "You try to move the [src], but you burn your hand on it!")
+ if(H)
+ var/obj/item/bodypart/affecting = H.get_bodypart("[(user.active_hand_index % 2 == 0) ? "r" : "l" ]_arm")
+ if(affecting && affecting.receive_damage( 0, 5 )) // 5 burn damage
+ H.update_damage_overlays()
+
+/obj/item/ingot/iron
+ custom_materials = list(/datum/material/iron=12000)
+
+/obj/item/ingot/diamond
+ custom_materials = list(/datum/material/diamond=12000) //yeah ok
+
+/obj/item/ingot/uranium
+ custom_materials = list(/datum/material/uranium=12000)
+
+/obj/item/ingot/plasma
+ custom_materials = list(/datum/material/plasma=12000)//yeah ok
+
+/obj/item/ingot/gold
+ custom_materials = list(/datum/material/gold=12000)
+
+/obj/item/ingot/silver
+ custom_materials = list(/datum/material/silver=12000)
+
+/obj/item/ingot/bananium
+ custom_materials = list(/datum/material/bananium=12000)
+
+/obj/item/ingot/titanium
+ custom_materials = list(/datum/material/titanium=12000)
+
+/obj/item/ingot/adamantine
+ custom_materials = list(/datum/material/adamantine=12000)
+
+/obj/item/ingot/cult
+ custom_materials = list(/datum/material/runedmetal=12000)
+
+/obj/item/ingot/bronze
+ custom_materials = list(/datum/material/bronze=12000)
+
+/obj/item/ingot/ratvar
+ custom_materials = list(/datum/material/brass=12000)
+ desc = "On closer inspection, what appears to be wholly-unsuitable-for-smithing brass is actually primarily replicant alloy. Nezbere must have switched it while you weren't looking."
+
+
+/obj/item/smithing/attackby(obj/item/I, mob/user)
+ if(istype(I, finishingitem))
+ qdel(I)
+ startfinish()
+ else
+ return ..()
+
+/obj/item/smithing/proc/startfinish()
+ dofinish()
+
+/obj/item/smithing/proc/dofinish()
+ var/qualname
+ switch(quality)
+ if(-1000 to -5)
+ qualname = "awful"
+ if(-1000 to -2)
+ qualname = "shoddy"
+ if(-1000 to -1)
+ qualname = "poor"
+ if(-1 to 1)
+ qualname = "normal"
+ if(10 to INFINITY)
+ qualname = "legendary"
+ if(7.5 to 10)
+ qualname = "masterwork"
+ if(5.5 to 7.5)
+ qualname = "excellent"
+ if(3.5 to 5.5)
+ qualname = "good"
+ if(0 to 3.5)
+ qualname = "above-average"
+ var/datum/material/mat = custom_materials[1]
+ finalitem.set_custom_materials(custom_materials)
+ mat = mat.name
+ if(artifact)
+ dwarfyartifact(finalitem, mat)
+ else
+ finalitem.name = "[qualname] [mat] [initial(finalitem.name)]"
+ finalitem.desc = "A [qualname] [initial(finalitem.name)]. Its quality is [quality]."
+ finalitem.forceMove(get_turf(src))
+ qdel(src)
+
+
+/obj/item/smithing/proc/dwarfyartifact(var/obj/item/finalitem, var/mat)
+ var/finaldesc = "A [initial(finalitem.name)] made of [mat], all craftsmanship is of the highest quality. It "
+ switch(pick(1,2,3,4,5))
+ if(1)
+ finaldesc += "is encrusted with [pick("","synthetic ","multi-faceted ","magical ","sparkling ") + pick("rubies","emeralds","jade","opals","lapiz lazuli")]."
+ if(2)
+ finaldesc += "is laced with studs of [pick("gold","silver","aluminium","titanium")]."
+ if(3)
+ finaldesc += "is encircled with bands of [pick("durasteel","metallic hydrogen","ferritic-alloy","plasteel","duranium")]."
+ if(4)
+ finaldesc += "menaces with spikes of [pick("ytterbium","uranium","white pearl","black steel")]."
+ if(5)
+ finaldesc += "is encrusted with [pick("","synthetic ","multi-faceted ","magical ","sparkling ") + pick("rubies","emeralds","jade","opals","lapis lazuli")],laced with studs of [pick("gold","silver","aluminium","titanium")], encircled with bands of [pick("durasteel","metallic hydrogen","ferritic-alloy","plasteel","duranium")] and menaces with spikes of [pick("ytterbium","uranium","white pearl","black steel")]."
+ finalitem.desc = finaldesc
+ finalitem.name = pick("Delersibnir", "Nekolangrir", "Zanoreshik","ÖntakrÃtin", "Nogzatan", "Vunomam", "Nazushagsaldôbar", "Sergeb", "Zafaldastot", "Vudnis", "Dostust", "Shotom", "Mugshith", "Angzak", "Oltud", "Deleratîs", "Nökornomal") //one of these is literally BLOOD POOL CREATE.iirc its Nazushagsaldôbar.
+
+/obj/item/smithing/hammerhead
+ name = "smithed hammer head"
+ finalitem = /obj/item/melee/smith/hammer
+ icon_state = "hammer"
+
+/obj/item/smithing/hammerhead/startfinish()
+ var/obj/item/melee/smith/hammer/finalforreal = new /obj/item/melee/smith/hammer(src)
+ finalforreal.force += quality/2
+ finalforreal.qualitymod = quality/4
+ finalitem = finalforreal
+ ..()
+
+
+
+/obj/item/smithing/scytheblade
+ name = "smithed scythe head"
+ finalitem = /obj/item/scythe/smithed
+ icon_state = "scythe"
+
+/obj/item/smithing/scytheblade/startfinish()
+ finalitem = new /obj/item/scythe/smithed(src)
+ finalitem.force += quality
+ ..()
+
+/obj/item/smithing/shovelhead
+ name = "smithed shovel head"
+ finalitem = /obj/item/shovel/smithed
+ icon_state = "shovel"
+
+/obj/item/smithing/shovelhead/startfinish()
+ finalitem = new /obj/item/shovel/smithed(src)
+ finalitem.force += quality/2
+ if(quality > 0)
+ finalitem.toolspeed = max(0.05,(1-(quality/10)))
+ else
+ finalitem.toolspeed *= max(1, (quality * -1))
+ ..()
+
+/obj/item/smithing/cogheadclubhead
+ name = "smithed coghead club head"
+ finalitem = /obj/item/melee/smith/cogheadclub
+ icon_state = "coghead"
+
+/obj/item/smithing/cogheadclubhead/startfinish()
+ finalitem = new /obj/item/melee/smith/cogheadclub(src)
+ finalitem.force += quality
+ ..()
+
+/obj/item/smithing/javelinhead
+ name = "smithed javelin head"
+ finalitem = /obj/item/melee/smith/twohand/javelin
+ icon_state = "javelin"
+
+/obj/item/smithing/javelinhead/startfinish()
+ var/obj/item/melee/smith/twohand/javelin/finalforreal = new /obj/item/melee/smith/twohand/javelin(src)
+ finalforreal.force += quality
+ finalforreal.wield_force = finalforreal.force*finalforreal.wielded_mult
+ finalforreal.AddComponent(/datum/component/two_handed, force_unwielded=finalforreal.force, force_wielded=finalforreal.wield_force, icon_wielded="[icon_state]")
+ finalforreal.throwforce = finalforreal.force*2
+ finalitem = finalforreal
+ ..()
+
+/obj/item/smithing/pikehead
+ name = "smithed pike head"
+ finalitem = /obj/item/melee/smith/twohand/pike
+ icon_state = "pike"
+
+/obj/item/smithing/pikehead/startfinish()
+ var/obj/item/melee/smith/twohand/pike/finalforreal = new /obj/item/melee/smith/twohand/pike(src)
+ finalforreal.force += quality
+ finalforreal.wield_force = finalforreal.force*finalforreal.wielded_mult
+ finalforreal.AddComponent(/datum/component/two_handed, force_unwielded=finalforreal.force, force_wielded=finalforreal.wield_force, icon_wielded="[icon_state]")
+ finalforreal.throwforce = finalforreal.force/10 //its a pike not a javelin
+ finalitem = finalforreal
+ ..()
+
+/obj/item/smithing/pickaxehead
+ name = "smithed pickaxe head"
+ finalitem = /obj/item/pickaxe/smithed
+ icon_state = "pickaxe"
+
+/obj/item/smithing/pickaxehead/startfinish()
+ var/obj/item/pickaxe/smithed/finalforreal = new /obj/item/pickaxe/smithed(src)
+ finalforreal.force += quality/2
+ if(quality > 0)
+ finalforreal.toolspeed = max(0.05,(1-(quality/10)))
+ else
+ finalforreal.toolspeed *= max(1, (quality * -1))
+ switch(quality)
+ if(10 to INFINITY)
+ finalforreal.digrange = 4
+ if(5 to 9)
+ finalforreal.digrange = 3
+ if(3,4)
+ finalforreal.digrange = 2
+ else
+ finalforreal.digrange = 1
+ finalitem = finalforreal
+ ..()
+
+
+/obj/item/smithing/prospectingpickhead
+ name = "smithed prospector's pickaxe head"
+ finalitem = /obj/item/mining_scanner/prospector
+ icon_state = "minipick"
+
+/obj/item/smithing/prospectingpickhead/startfinish()
+ var/obj/item/mining_scanner/prospector/finalforreal = new /obj/item/mining_scanner/prospector(src)
+ finalforreal.range = 2 + quality
+ if(quality)
+ finalforreal.cooldown = 100/quality
+ finalitem = finalforreal
+ ..()
+
+
+/obj/item/smithing/shortswordblade
+ name = "smithed gladius blade"
+ finishingitem = /obj/item/swordhandle
+ finalitem = /obj/item/melee/smith/shortsword
+ icon_state = "gladius"
+
+/obj/item/smithing/shortswordblade/startfinish()
+ finalitem = new /obj/item/melee/smith/shortsword(src)
+ finalitem.force += quality
+ ..()
+
+/obj/item/smithing/scimitarblade
+ name = "smithed scimitar blade"
+ finishingitem = /obj/item/swordhandle
+ finalitem = /obj/item/melee/smith/shortsword/scimitar
+ icon_state = "scimitar"
+
+/obj/item/smithing/scimitarblade/startfinish()
+ finalitem = new /obj/item/melee/smith/shortsword/scimitar(src)
+ finalitem.force += quality
+ ..()
+
+/obj/item/smithing/wakiblade
+ name = "smithed wakizashi blade"
+ finishingitem = /obj/item/swordhandle
+ finalitem = /obj/item/melee/smith/wakizashi
+ icon_state = "waki"
+
+/obj/item/smithing/wakiblade/startfinish()
+ finalitem = new /obj/item/melee/smith/wakizashi(src)
+ finalitem.force += quality
+ ..()
+
+/obj/item/smithing/sabreblade
+ name = "smithed sabre blade"
+ finishingitem = /obj/item/swordhandle
+ finalitem = /obj/item/melee/smith/sabre
+ icon_state = "sabre"
+
+/obj/item/smithing/sabreblade/startfinish()
+ finalitem = new /obj/item/melee/smith/sabre(src)
+ finalitem.force += quality
+ ..()
+
+/obj/item/smithing/rapierblade
+ name = "smithed rapier blade"
+ finishingitem = /obj/item/swordhandle
+ finalitem = /obj/item/melee/smith/sabre/rapier
+ icon_state = "rapier"
+
+/obj/item/smithing/rapierblade/startfinish()
+ finalitem = new /obj/item/melee/smith/sabre/rapier(src)
+ finalitem.force += quality
+ ..()
+
+/obj/item/smithing/knifeblade
+ name = "smithed knife blade"
+ finishingitem = /obj/item/swordhandle
+ finalitem = /obj/item/kitchen/knife
+ icon_state = "dagger"
+
+/obj/item/smithing/knifeblade/startfinish()
+ finalitem = new /obj/item/kitchen/knife(src)
+ finalitem.force = 4 + quality/2
+ finalitem.icon = 'icons/obj/smith.dmi'
+ finalitem.icon_state = "dagger"
+ finalitem.name = "dagger"
+ finalitem.desc = "A dagger."
+ var/mutable_appearance/overlay = mutable_appearance('icons/obj/smith.dmi', "daggerhilt")
+ overlay.appearance_flags = RESET_COLOR
+ finalitem.add_overlay(overlay)
+ if(finalitem.force < 0)
+ finalitem.force = 0
+ finalitem.material_flags = MATERIAL_COLOR | MATERIAL_AFFECT_STATISTICS
+ ..()
+
+/obj/item/smithing/broadblade
+ name = "smithed broadsword blade"
+ finishingitem = /obj/item/swordhandle
+ finalitem = /obj/item/melee/smith/twohand/broadsword
+ icon_state = "broadsword"
+
+/obj/item/smithing/broadblade/startfinish()
+ var/obj/item/melee/smith/twohand/broadsword/finalforreal = new /obj/item/melee/smith/twohand/broadsword(src)
+ finalforreal.force += quality
+ finalforreal.wield_force = finalforreal.force*finalforreal.wielded_mult
+ finalforreal.AddComponent(/datum/component/two_handed, force_unwielded=finalforreal.force, force_wielded=finalforreal.wield_force, icon_wielded="[icon_state]")
+ finalitem = finalforreal
+ ..()
+
+/obj/item/smithing/zweiblade
+ name = "smithed zweihander blade"
+ finishingitem = /obj/item/swordhandle
+ finalitem = /obj/item/melee/smith/twohand/zweihander
+ icon_state = "zwei"
+
+/obj/item/smithing/zweiblade/startfinish()
+ var/obj/item/melee/smith/twohand/zweihander/finalforreal = new /obj/item/melee/smith/twohand/zweihander(src)
+ finalforreal.force += quality
+ finalforreal.wield_force = finalforreal.force*finalforreal.wielded_mult
+ finalforreal.AddComponent(/datum/component/two_handed, force_unwielded=finalforreal.force, force_wielded=finalforreal.wield_force, icon_wielded="[icon_state]")
+ finalitem = finalforreal
+ ..()
+
+/obj/item/smithing/halberdhead
+ name = "smithed halberd head"
+ finalitem = /obj/item/melee/smith/twohand/halberd
+ icon_state = "halberd"
+
+/obj/item/smithing/halberdhead/startfinish()
+ var/obj/item/melee/smith/twohand/halberd/finalforreal = new /obj/item/melee/smith/twohand/halberd(src)
+ finalforreal.force += quality
+ finalforreal.wield_force = finalforreal.force*finalforreal.wielded_mult
+ finalforreal.throwforce = finalforreal.force/3
+ finalforreal.AddComponent(/datum/component/two_handed, force_unwielded=finalforreal.force, force_wielded=finalforreal.wield_force, icon_wielded="[icon_state]")
+ finalitem = finalforreal
+ ..()
+
+/obj/item/smithing/glaivehead
+ name = "smithed glaive head"
+ finalitem = /obj/item/melee/smith/twohand/glaive
+ icon_state = "glaive"
+
+/obj/item/smithing/glaivehead/startfinish()
+ var/obj/item/melee/smith/twohand/glaive/finalforreal = new /obj/item/melee/smith/twohand/glaive(src)
+ finalforreal.force += quality
+ finalforreal.wield_force = finalforreal.force*finalforreal.wielded_mult
+ finalforreal.throwforce = finalforreal.force
+ finalforreal.AddComponent(/datum/component/two_handed, force_unwielded=finalforreal.force, force_wielded=finalforreal.wield_force, icon_wielded="[icon_state]")
+ finalitem = finalforreal
+ ..()
+
+/obj/item/smithing/katanablade
+ name = "smithed katana blade"
+ finishingitem = /obj/item/swordhandle
+ finalitem = /obj/item/melee/smith/twohand/katana
+ icon_state = "katana"
+
+
+/obj/item/smithing/katanablade/startfinish()
+ var/obj/item/melee/smith/twohand/katana/finalforreal = new /obj/item/melee/smith/twohand/katana(src)
+ finalforreal.force += quality
+ finalforreal.wield_force = finalforreal.force*finalforreal.wielded_mult
+ finalforreal.AddComponent(/datum/component/two_handed, force_unwielded=finalforreal.force, force_wielded=finalforreal.wield_force, icon_wielded="[icon_state]")
+ finalitem = finalforreal
+ ..()
+
+/obj/item/stick
+ name = "wooden rod"
+ desc = "It's a rod, suitable for use of a handle of a tool. Also could serve as a weapon, in a pinch."
+ icon = 'icons/obj/smith.dmi'
+ icon_state = "stick"
+ force = 7
+
+/obj/item/swordhandle
+ name = "sword handle"
+ desc = "It's a crudlely shaped wooden sword hilt."
+ icon = 'icons/obj/smith.dmi'
+ icon_state = "shorthilt"
diff --git a/code/modules/spells/spell.dm b/code/modules/spells/spell.dm
index 57502e2cd5..2272a14612 100644
--- a/code/modules/spells/spell.dm
+++ b/code/modules/spells/spell.dm
@@ -224,7 +224,15 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell)) //needed for th
/obj/effect/proc_holder/spell/proc/choose_targets(mob/user = usr) //depends on subtype - /targeted or /aoe_turf
return
-/obj/effect/proc_holder/spell/proc/can_target(mob/living/target)
+/**
+ * can_target: Checks if we are allowed to cast the spell on a target.
+ *
+ * Arguments:
+ * * target The atom that is being targeted by the spell.
+ * * user The mob using the spell.
+ * * silent If the checks should not give any feedback messages.
+ */
+/obj/effect/proc_holder/spell/proc/can_target(atom/target, mob/user, silent = FALSE)
return TRUE
/obj/effect/proc_holder/spell/proc/start_recharge()
@@ -296,6 +304,13 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell)) //needed for th
/obj/effect/proc_holder/spell/proc/cast(list/targets,mob/user = usr)
return
+/obj/effect/proc_holder/spell/proc/view_or_range(distance = world.view, center=usr, type="view")
+ switch(type)
+ if("view")
+ . = view(distance,center)
+ if("range")
+ . = range(distance,center)
+
/obj/effect/proc_holder/spell/proc/revert_cast(mob/user = usr) //resets recharge or readds a charge
switch(charge_type)
if("recharge")
@@ -345,7 +360,7 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell)) //needed for th
switch(max_targets)
if(0) //unlimited
for(var/mob/living/target in view_or_range(range, user, selection_type))
- if(!can_target(target))
+ if(!can_target(target, user, TRUE))
continue
targets += target
if(1) //single target can be picked
@@ -357,7 +372,7 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell)) //needed for th
for(var/mob/living/M in view_or_range(range, user, selection_type))
if(!include_user && user == M)
continue
- if(!can_target(M))
+ if(!can_target(M, user, TRUE))
continue
possible_targets += M
@@ -365,7 +380,7 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell)) //needed for th
//Adds a safety check post-input to make sure those targets are actually in range.
var/mob/M
if(!random_target)
- M = input("Choose the target for the spell.", "Targeting") as null|mob in possible_targets
+ M = input("Choose the target for the spell.", "Targeting") as null|mob in sortNames(possible_targets)
else
switch(random_target_priority)
if(TARGET_RANDOM)
@@ -385,7 +400,7 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell)) //needed for th
else
var/list/possible_targets = list()
for(var/mob/living/target in view_or_range(range, user, selection_type))
- if(!can_target(target))
+ if(!can_target(target, user, TRUE))
continue
possible_targets += target
for(var/i=1,i<=max_targets,i++)
@@ -411,7 +426,7 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell)) //needed for th
var/list/targets = list()
for(var/turf/target in view_or_range(range,user,selection_type))
- if(!can_target(target))
+ if(!can_target(target, user, TRUE))
continue
if(!(target in view_or_range(inner_radius,user,selection_type)))
targets += target
diff --git a/code/modules/spells/spell_types/cone_spells.dm b/code/modules/spells/spell_types/cone_spells.dm
new file mode 100644
index 0000000000..63bae4b7cf
--- /dev/null
+++ b/code/modules/spells/spell_types/cone_spells.dm
@@ -0,0 +1,117 @@
+/obj/effect/proc_holder/spell/cone
+ name = "Cone of Nothing"
+ desc = "Does nothing in a cone! Wow!"
+ school = "evocation"
+ charge_max = 100
+ clothes_req = FALSE
+ invocation = "FUKAN NOTHAN"
+ invocation_type = "shout"
+ sound = 'sound/magic/forcewall.ogg'
+ action_icon_state = "shield"
+ range = -1
+ cooldown_min = 0.5 SECONDS
+ ///This controls how many levels the cone has, increase this value to make a bigger cone.
+ var/cone_levels = 3
+ ///This value determines if the cone penetrates walls.
+ var/respect_density = FALSE
+
+/obj/effect/proc_holder/spell/cone/choose_targets(mob/user = usr)
+ perform(null, user=user)
+
+///This proc creates a list of turfs that are hit by the cone
+/obj/effect/proc_holder/spell/cone/proc/cone_helper(var/turf/starter_turf, var/dir_to_use, var/cone_levels = 3)
+ var/list/turfs_to_return = list()
+ var/turf/turf_to_use = starter_turf
+ var/turf/left_turf
+ var/turf/right_turf
+ var/right_dir
+ var/left_dir
+ switch(dir_to_use)
+ if(NORTH)
+ left_dir = WEST
+ right_dir = EAST
+ if(SOUTH)
+ left_dir = EAST
+ right_dir = WEST
+ if(EAST)
+ left_dir = NORTH
+ right_dir = SOUTH
+ if(WEST)
+ left_dir = SOUTH
+ right_dir = NORTH
+
+
+ for(var/i in 1 to cone_levels)
+ var/list/level_turfs = list()
+ turf_to_use = get_step(turf_to_use, dir_to_use)
+ level_turfs += turf_to_use
+ if(i != 1)
+ left_turf = get_step(turf_to_use, left_dir)
+ level_turfs += left_turf
+ right_turf = get_step(turf_to_use, right_dir)
+ level_turfs += right_turf
+ for(var/left_i in 1 to i -calculate_cone_shape(i))
+ if(left_turf.density && respect_density)
+ break
+ left_turf = get_step(left_turf, left_dir)
+ level_turfs += left_turf
+ for(var/right_i in 1 to i -calculate_cone_shape(i))
+ if(right_turf.density && respect_density)
+ break
+ right_turf = get_step(right_turf, right_dir)
+ level_turfs += right_turf
+ turfs_to_return += list(level_turfs)
+ if(i == cone_levels)
+ continue
+ if(turf_to_use.density && respect_density)
+ break
+ return turfs_to_return
+
+/obj/effect/proc_holder/spell/cone/cast(list/targets,mob/user = usr)
+ var/list/cone_turfs = cone_helper(get_turf(user), user.dir, cone_levels)
+ for(var/list/turf_list in cone_turfs)
+ do_cone_effects(turf_list)
+
+///This proc does obj, mob and turf cone effects on all targets in a list
+/obj/effect/proc_holder/spell/cone/proc/do_cone_effects(list/target_turf_list, level)
+ for(var/target_turf in target_turf_list)
+ if(!target_turf) //if turf is no longer there
+ continue
+ do_turf_cone_effect(target_turf, level)
+ if(isopenturf(target_turf))
+ var/turf/open/open_turf = target_turf
+ for(var/movable_content in open_turf)
+ if(isobj(movable_content))
+ do_obj_cone_effect(movable_content, level)
+ else if(isliving(movable_content))
+ do_mob_cone_effect(movable_content, level)
+
+///This proc deterimines how the spell will affect turfs.
+/obj/effect/proc_holder/spell/cone/proc/do_turf_cone_effect(turf/target_turf, level)
+ return
+
+///This proc deterimines how the spell will affect objects.
+/obj/effect/proc_holder/spell/cone/proc/do_obj_cone_effect(obj/target_obj, level)
+ return
+
+///This proc deterimines how the spell will affect mobs.
+/obj/effect/proc_holder/spell/cone/proc/do_mob_cone_effect(mob/living/target_mob, level)
+ return
+
+///This proc adjusts the cones width depending on the level.
+/obj/effect/proc_holder/spell/cone/proc/calculate_cone_shape(current_level)
+ var/end_taper_start = round(cone_levels * 0.8)
+ if(current_level > end_taper_start)
+ return (current_level % end_taper_start) * 2 //someone more talented and probably come up with a better formula.
+ else
+ return 2
+
+///This type of cone gradually affects each level of the cone instead of affecting the entire area at once.
+/obj/effect/proc_holder/spell/cone/staggered
+
+/obj/effect/proc_holder/spell/cone/staggered/cast(list/targets,mob/user = usr)
+ var/level_counter = 0
+ var/list/cone_turfs = cone_helper(get_turf(user), user.dir, cone_levels)
+ for(var/list/turf_list in cone_turfs)
+ level_counter++
+ addtimer(CALLBACK(src, .proc/do_cone_effects, turf_list, level_counter), 2 * level_counter)
diff --git a/code/modules/spells/spell_types/dumbfire.dm b/code/modules/spells/spell_types/dumbfire.dm
index 6931b4ac31..424c702a31 100644
--- a/code/modules/spells/spell_types/dumbfire.dm
+++ b/code/modules/spells/spell_types/dumbfire.dm
@@ -49,8 +49,8 @@
var/projectile_type = text2path(proj_type)
projectile = new projectile_type(user)
else if(istype(proj_type, /obj/effect/proc_holder/spell))
- projectile = new /obj/effect/proc_holder/spell/targeted/trigger(user)
- var/obj/effect/proc_holder/spell/targeted/trigger/T = projectile
+ projectile = new /obj/effect/proc_holder/spell/pointed/trigger(user)
+ var/obj/effect/proc_holder/spell/pointed/trigger/T = projectile
T.linked_spells += proj_type
else
projectile = new proj_type(user)
diff --git a/code/modules/spells/spell_types/ethereal_jaunt.dm b/code/modules/spells/spell_types/ethereal_jaunt.dm
index 8cf51d45c6..9d91b6534d 100644
--- a/code/modules/spells/spell_types/ethereal_jaunt.dm
+++ b/code/modules/spells/spell_types/ethereal_jaunt.dm
@@ -17,7 +17,7 @@
action_icon_state = "jaunt"
/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/cast(list/targets,mob/user = usr) //magnets, so mostly hardcoded
- playsound(get_turf(user), 'sound/magic/ethereal_enter.ogg', 50, 1, -1)
+ play_sound("enter",user)
for(var/mob/living/target in targets)
INVOKE_ASYNC(src, .proc/do_jaunt, target)
@@ -42,7 +42,7 @@
ADD_TRAIT(target, TRAIT_MOBILITY_NOMOVE, src)
target.update_mobility()
holder.reappearing = 1
- playsound(get_turf(target), 'sound/magic/ethereal_exit.ogg', 50, 1, -1)
+ play_sound("exit",target)
sleep(25 - jaunt_in_time)
new jaunt_in_type(mobloc, holder.dir)
target.setDir(holder.dir)
@@ -63,6 +63,13 @@
steam.set_up(10, 0, mobloc)
steam.start()
+/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/proc/play_sound(type,mob/living/target)
+ switch(type)
+ if("enter")
+ playsound(get_turf(target), 'sound/magic/ethereal_enter.ogg', 50, TRUE, -1)
+ if("exit")
+ playsound(get_turf(target), 'sound/magic/ethereal_exit.ogg', 50, TRUE, -1)
+
/obj/effect/dummy/phased_mob/spell_jaunt
name = "water"
icon = 'icons/effects/effects.dmi'
diff --git a/code/modules/spells/spell_types/mind_transfer.dm b/code/modules/spells/spell_types/mind_transfer.dm
deleted file mode 100644
index d2ef015d1d..0000000000
--- a/code/modules/spells/spell_types/mind_transfer.dm
+++ /dev/null
@@ -1,88 +0,0 @@
-/obj/effect/proc_holder/spell/targeted/mind_transfer
- name = "Mind Transfer"
- desc = "This spell allows the user to switch bodies with a target."
-
- school = "transmutation"
- charge_max = 600
- clothes_req = NONE
- invocation = "GIN'YU CAPAN"
- invocation_type = "whisper"
- range = 1
- cooldown_min = 200 //100 deciseconds reduction per rank
- var/unconscious_amount_caster = 400 //how much the caster is stunned for after the spell
- var/unconscious_amount_victim = 400 //how much the victim is stunned for after the spell
-
- action_icon_state = "mindswap"
-
-/*
-Urist: I don't feel like figuring out how you store object spells so I'm leaving this for you to do.
-Make sure spells that are removed from spell_list are actually removed and deleted when mind transferring.
-Also, you never added distance checking after target is selected. I've went ahead and did that.
-*/
-/obj/effect/proc_holder/spell/targeted/mind_transfer/cast(list/targets, mob/living/user = usr, distanceoverride, silent = FALSE)
- if(!targets.len)
- if(!silent)
- to_chat(user, "No mind found!")
- return
-
- if(targets.len > 1)
- if(!silent)
- to_chat(user, "Too many minds! You're not a hive damnit!")
- return
-
- var/mob/living/target = targets[1]
-
- var/t_He = target.p_they(TRUE)
- var/t_is = target.p_are()
-
- if(!(target in oview(range)) && !distanceoverride)//If they are not in overview after selection. Do note that !() is necessary for in to work because ! takes precedence over it.
- if(!silent)
- to_chat(user, "[t_He] [t_is] too far away!")
- return
-
- if(ismegafauna(target))
- if(!silent)
- to_chat(user, "This creature is too powerful to control!")
- return
-
- if(target.stat == DEAD)
- if(!silent)
- to_chat(user, "You don't particularly want to be dead!")
- return
-
- if(!target.key || !target.mind)
- if(!silent)
- to_chat(user, "[t_He] appear[target.p_s()] to be catatonic! Not even magic can affect [target.p_their()] vacant mind.")
- return
-
- if(user.suiciding)
- if(!silent)
- to_chat(user, "You're killing yourself! You can't concentrate enough to do this!")
- return
-
- var/datum/mind/TM = target.mind
- if(target.anti_magic_check(TRUE, FALSE) || TM.has_antag_datum(/datum/antagonist/wizard) || TM.has_antag_datum(/datum/antagonist/cult) || TM.has_antag_datum(/datum/antagonist/clockcult) || TM.has_antag_datum(/datum/antagonist/changeling) || TM.has_antag_datum(/datum/antagonist/rev) || target.key[1] == "@")
- if(!silent)
- to_chat(user, "[target.p_their(TRUE)] mind is resisting your spell!")
- return
-
- var/mob/living/victim = target//The target of the spell whos body will be transferred to.
- var/mob/living/caster = user//The wizard/whomever doing the body transferring.
-
- //MIND TRANSFER BEGIN
- var/mob/dead/observer/ghost = victim.ghostize(FALSE, TRUE)
- caster.mind.transfer_to(victim)
-
- ghost.mind.transfer_to(caster)
- if(ghost.key)
- ghost.transfer_ckey(caster) //have to transfer the key since the mind was not active
- qdel(ghost)
-
- //MIND TRANSFER END
-
- //Here we knock both mobs out for a time.
- caster.Unconscious(unconscious_amount_caster)
- victim.Unconscious(unconscious_amount_victim)
- SEND_SOUND(caster, sound('sound/magic/mandswap.ogg'))
- SEND_SOUND(victim, sound('sound/magic/mandswap.ogg'))// only the caster and victim hear the sounds, that way no one knows for sure if the swap happened
- return TRUE
diff --git a/code/modules/spells/spell_types/barnyard.dm b/code/modules/spells/spell_types/pointed/barnyard.dm
similarity index 52%
rename from code/modules/spells/spell_types/barnyard.dm
rename to code/modules/spells/spell_types/pointed/barnyard.dm
index 4b972e8030..61e3cf7127 100644
--- a/code/modules/spells/spell_types/barnyard.dm
+++ b/code/modules/spells/spell_types/pointed/barnyard.dm
@@ -1,51 +1,54 @@
-/obj/effect/proc_holder/spell/targeted/barnyardcurse
+/obj/effect/proc_holder/spell/pointed/barnyardcurse
name = "Curse of the Barnyard"
desc = "This spell dooms an unlucky soul to possess the speech and facial attributes of a barnyard animal."
school = "transmutation"
charge_type = "recharge"
charge_max = 150
charge_counter = 0
- clothes_req = NONE
- stat_allowed = 0
+ clothes_req = FALSE
+ stat_allowed = FALSE
invocation = "KN'A FTAGHU, PUCK 'BTHNK!"
invocation_type = "shout"
range = 7
cooldown_min = 30
- selection_type = "range"
- var/list/compatible_mobs = list(/mob/living/carbon/human, /mob/living/carbon/monkey)
-
+ ranged_mousepointer = 'icons/effects/mouse_pointers/barn_target.dmi'
action_icon_state = "barn"
+ active_msg = "You prepare to curse a target..."
+ deactive_msg = "You dispel the curse..."
+ /// List of mobs which are allowed to be a target of the spell
+ var/static/list/compatible_mobs_typecache = typecacheof(list(/mob/living/carbon/human, /mob/living/carbon/monkey))
-/obj/effect/proc_holder/spell/targeted/barnyardcurse/cast(list/targets, mob/user = usr)
+/obj/effect/proc_holder/spell/pointed/barnyardcurse/cast(list/targets, mob/user)
if(!targets.len)
- to_chat(user, "No target found in range.")
- return
+ to_chat(user, "No target found in range!")
+ return FALSE
+ if(!can_target(targets[1], user))
+ return FALSE
var/mob/living/carbon/target = targets[1]
-
- if(!(target.type in compatible_mobs))
- to_chat(user, "You are unable to curse [target]'s head!")
- return
-
- if(!(target in oview(range)))
- to_chat(user, "[target.p_theyre(TRUE)] too far away!")
- return
-
if(target.anti_magic_check())
to_chat(user, "The spell had no effect!")
target.visible_message("[target]'s face bursts into flames, which instantly burst outward, leaving [target] unharmed!", \
- "Your face starts burning up, but the flames are repulsed by your anti-magic protection!")
- return
+ "Your face starts burning up, but the flames are repulsed by your anti-magic protection!")
+ return FALSE
var/list/masks = list(/obj/item/clothing/mask/pig/cursed, /obj/item/clothing/mask/cowmask/cursed, /obj/item/clothing/mask/horsehead/cursed)
-
var/choice = pick(masks)
var/obj/item/clothing/mask/magichead = new choice(get_turf(target))
- magichead.flags_inv = null
+
target.visible_message("[target]'s face bursts into flames, and a barnyard animal's head takes its place!", \
"Your face burns up, and shortly after the fire you realise you have the face of a barnyard animal!")
if(!target.dropItemToGround(target.wear_mask))
qdel(target.wear_mask)
- target.equip_to_slot_if_possible(magichead, SLOT_WEAR_MASK, 1, 1)
-
+ target.equip_to_slot_if_possible(magichead, ITEM_SLOT_MASK, 1, 1)
target.flash_act()
+
+/obj/effect/proc_holder/spell/pointed/barnyardcurse/can_target(atom/target, mob/user, silent)
+ . = ..()
+ if(!.)
+ return FALSE
+ if(!is_type_in_typecache(target, compatible_mobs_typecache))
+ if(!silent)
+ to_chat(user, "You are unable to curse [target]!")
+ return FALSE
+ return TRUE
diff --git a/code/modules/spells/spell_types/pointed/blind.dm b/code/modules/spells/spell_types/pointed/blind.dm
new file mode 100644
index 0000000000..a773c5ad8d
--- /dev/null
+++ b/code/modules/spells/spell_types/pointed/blind.dm
@@ -0,0 +1,35 @@
+/obj/effect/proc_holder/spell/pointed/trigger/blind
+ name = "Blind"
+ desc = "This spell temporarily blinds a single target."
+ school = "transmutation"
+ charge_max = 300
+ clothes_req = FALSE
+ invocation = "STI KALY"
+ invocation_type = "whisper"
+ message = "Your eyes cry out in pain!"
+ cooldown_min = 50 //12 deciseconds reduction per rank
+ starting_spells = list("/obj/effect/proc_holder/spell/targeted/inflict_handler/blind", "/obj/effect/proc_holder/spell/targeted/genetic/blind")
+ ranged_mousepointer = 'icons/effects/mouse_pointers/blind_target.dmi'
+ action_icon_state = "blind"
+ active_msg = "You prepare to blind a target..."
+
+/obj/effect/proc_holder/spell/targeted/inflict_handler/blind
+ amt_eye_blind = 10
+ amt_eye_blurry = 20
+ sound = 'sound/magic/blind.ogg'
+
+/obj/effect/proc_holder/spell/targeted/genetic/blind
+ mutations = list(BLINDMUT)
+ duration = 300
+ charge_max = 400 // needs to be higher than the duration or it'll be permanent
+ sound = 'sound/magic/blind.ogg'
+
+/obj/effect/proc_holder/spell/pointed/trigger/blind/can_target(atom/target, mob/user, silent)
+ . = ..()
+ if(!.)
+ return FALSE
+ if(!isliving(target))
+ if(!silent)
+ to_chat(user, "You can only blind living beings!")
+ return FALSE
+ return TRUE
diff --git a/code/modules/spells/spell_types/pointed/mind_transfer.dm b/code/modules/spells/spell_types/pointed/mind_transfer.dm
new file mode 100644
index 0000000000..28d646f6b6
--- /dev/null
+++ b/code/modules/spells/spell_types/pointed/mind_transfer.dm
@@ -0,0 +1,103 @@
+/obj/effect/proc_holder/spell/pointed/mind_transfer
+ name = "Mind Transfer"
+ desc = "This spell allows the user to switch bodies with a target next to him."
+ school = "transmutation"
+ charge_max = 600
+ clothes_req = FALSE
+ invocation = "GIN'YU CAPAN"
+ invocation_type = "whisper"
+ range = 1
+ cooldown_min = 200 //100 deciseconds reduction per rank
+ ranged_mousepointer = 'icons/effects/mouse_pointers/mindswap_target.dmi'
+ action_icon_state = "mindswap"
+ active_msg = "You prepare to swap minds with a target..."
+ /// For how long is the caster stunned for after the spell
+ var/unconscious_amount_caster = 40 SECONDS
+ /// For how long is the victim stunned for after the spell
+ var/unconscious_amount_victim = 40 SECONDS
+
+/obj/effect/proc_holder/spell/pointed/mind_transfer/cast(list/targets, mob/living/user, silent = FALSE)
+ if(!targets.len)
+ if(!silent)
+ to_chat(user, "No mind found!")
+ return FALSE
+ if(targets.len > 1)
+ if(!silent)
+ to_chat(user, "Too many minds! You're not a hive damnit!")
+ return FALSE
+ if(!can_target(targets[1], user, silent))
+ return FALSE
+
+ var/mob/living/victim = targets[1] //The target of the spell whos body will be transferred to.
+ var/datum/mind/VM = victim.mind
+ if(victim.anti_magic_check(TRUE, FALSE) || VM.has_antag_datum(/datum/antagonist/wizard) || VM.has_antag_datum(/datum/antagonist/cult) || VM.has_antag_datum(/datum/antagonist/changeling) || VM.has_antag_datum(/datum/antagonist/rev) || victim.key[1] == "@")
+ if(!silent)
+ to_chat(user, "[victim.p_their(TRUE)] mind is resisting your spell!")
+ return FALSE
+ if(istype(victim, /mob/living/simple_animal/hostile/guardian))
+ var/mob/living/simple_animal/hostile/guardian/stand = victim
+ if(stand.summoner)
+ victim = stand.summoner
+
+ //You should not be able to enter one of the most powerful side-antags as a fucking wizard.
+ if(istype(victim,/mob/living/simple_animal/slaughter))
+ to_chat(user, "The devilish contract doesn't include the 'mind swappable' package, please try again another lifetime.")
+ return
+
+ //MIND TRANSFER BEGIN
+ var/mob/dead/observer/ghost = victim.ghostize()
+ user.mind.transfer_to(victim)
+
+ ghost.mind.transfer_to(user)
+ if(ghost.key)
+ user.key = ghost.key //have to transfer the key since the mind was not active
+ qdel(ghost)
+ //MIND TRANSFER END
+
+ //Here we knock both mobs out for a time.
+ user.Unconscious(unconscious_amount_caster)
+ victim.Unconscious(unconscious_amount_victim)
+ SEND_SOUND(user, sound('sound/magic/mandswap.ogg'))
+ SEND_SOUND(victim, sound('sound/magic/mandswap.ogg')) // only the caster and victim hear the sounds, that way no one knows for sure if the swap happened
+ return TRUE
+
+/obj/effect/proc_holder/spell/pointed/mind_transfer/can_target(atom/target, mob/user, silent)
+ . = ..()
+ if(!.)
+ return FALSE
+ if(!isliving(target))
+ if(!silent)
+ to_chat(user, "You can only swap minds with living beings!")
+ return FALSE
+ if(user == target)
+ if(!silent)
+ to_chat(user, "You can't swap minds with yourself!")
+ return FALSE
+
+ var/mob/living/victim = target
+ var/t_He = victim.p_they(TRUE)
+
+ if(ismegafauna(victim))
+ if(!silent)
+ to_chat(user, "This creature is too powerful to control!")
+ return FALSE
+ if(victim.stat == DEAD)
+ if(!silent)
+ to_chat(user, "You don't particularly want to be dead!")
+ return FALSE
+ if(!victim.key || !victim.mind)
+ if(!silent)
+ to_chat(user, "[t_He] appear[victim.p_s()] to be catatonic! Not even magic can affect [victim.p_their()] vacant mind.")
+ return FALSE
+ if(user.suiciding)
+ if(!silent)
+ to_chat(user, "You're killing yourself! You can't concentrate enough to do this!")
+ return FALSE
+ if(istype(victim, /mob/living/simple_animal/hostile/guardian))
+ var/mob/living/simple_animal/hostile/guardian/stand = victim
+ if(stand.summoner)
+ if(stand.summoner == user)
+ if(!silent)
+ to_chat(user, "Swapping minds with your own guardian would just put you back into your own head!")
+ return FALSE
+ return TRUE
diff --git a/code/modules/spells/spell_types/pointed/pointed.dm b/code/modules/spells/spell_types/pointed/pointed.dm
new file mode 100644
index 0000000000..7b942dee27
--- /dev/null
+++ b/code/modules/spells/spell_types/pointed/pointed.dm
@@ -0,0 +1,105 @@
+/obj/effect/proc_holder/spell/pointed
+ name = "pointed spell"
+ ranged_mousepointer = 'icons/effects/mouse_pointers/throw_target.dmi'
+ action_icon_state = "projectile"
+ /// Message showing to the spell owner upon deactivating pointed spell.
+ var/deactive_msg = "You dispel the magic..."
+ /// Message showing to the spell owner upon activating pointed spell.
+ var/active_msg = "You prepare to use the spell on a target..."
+ /// Variable dictating if the user is allowed to cast a spell on himself.
+ var/self_castable = FALSE
+ /// Variable dictating if the spell will use turf based aim assist
+ var/aim_assist = TRUE
+
+/obj/effect/proc_holder/spell/pointed/Trigger(mob/user, skip_can_cast = TRUE)
+ if(!istype(user))
+ return
+ var/msg
+ if(!can_cast(user))
+ msg = "You can no longer cast [name]!"
+ remove_ranged_ability(msg)
+ return
+ if(active)
+ msg = "[deactive_msg]"
+ remove_ranged_ability(msg)
+ else
+ msg = "[active_msg] Left-click to activate spell on a target!"
+ add_ranged_ability(user, msg, TRUE)
+ on_activation(user)
+
+/obj/effect/proc_holder/spell/pointed/on_lose(mob/living/user)
+ remove_ranged_ability()
+
+/obj/effect/proc_holder/spell/pointed/remove_ranged_ability(msg)
+ . = ..()
+ on_deactivation(ranged_ability_user)
+
+/obj/effect/proc_holder/spell/pointed/add_ranged_ability(mob/living/user, msg, forced)
+ . = ..()
+ on_activation(user)
+
+/**
+ * on_activation: What happens upon pointed spell activation.
+ *
+ * Arguments:
+ * * user The mob interacting owning the spell.
+ */
+/obj/effect/proc_holder/spell/pointed/proc/on_activation(mob/user)
+ return
+
+/**
+ * on_activation: What happens upon pointed spell deactivation.
+ *
+ * Arguments:
+ * * user The mob interacting owning the spell.
+ */
+/obj/effect/proc_holder/spell/pointed/proc/on_deactivation(mob/user)
+ return
+
+/obj/effect/proc_holder/spell/pointed/update_icon()
+ if(!action)
+ return
+ if(active)
+ action.button_icon_state = "[action_icon_state]1"
+ else
+ action.button_icon_state = "[action_icon_state]"
+ action.UpdateButtonIcon()
+
+/obj/effect/proc_holder/spell/pointed/InterceptClickOn(mob/living/caller, params, atom/target)
+ if(..())
+ return TRUE
+ if(aim_assist && isturf(target))
+ var/list/possible_targets = list()
+ for(var/A in target)
+ if(intercept_check(caller, A, TRUE))
+ possible_targets += A
+ if(possible_targets.len == 1)
+ target = possible_targets[1]
+ if(!intercept_check(caller, target))
+ return TRUE
+ if(!cast_check(FALSE, caller))
+ return TRUE
+ perform(list(target), user = caller)
+ remove_ranged_ability()
+ return TRUE // Do not do any underlying actions after the spell cast
+
+/**
+ * intercept_check: Specific spell checks for InterceptClickOn() targets.
+ *
+ * Arguments:
+ * * user The mob using the ranged spell via intercept.
+ * * target The atom that is being targeted by the spell via intercept.
+ * * silent If the checks should produce not any feedback messages for the user.
+ */
+/obj/effect/proc_holder/spell/pointed/proc/intercept_check(mob/user, atom/target, silent = FALSE)
+ if(!self_castable && target == user)
+ if(!silent)
+ to_chat(user, "You cannot cast the spell on yourself!")
+ return FALSE
+ if(!(target in view_or_range(range, user, selection_type)))
+ if(!silent)
+ to_chat(user, "[target.p_theyre(TRUE)] too far away!")
+ return FALSE
+ if(!can_target(target, user, silent))
+ return FALSE
+ return TRUE
diff --git a/code/modules/spells/spell_types/projectile.dm b/code/modules/spells/spell_types/projectile.dm
index be305520a2..3a8f48cf7c 100644
--- a/code/modules/spells/spell_types/projectile.dm
+++ b/code/modules/spells/spell_types/projectile.dm
@@ -37,8 +37,8 @@
var/projectile_type = text2path(proj_type)
projectile = new projectile_type(user)
if(istype(proj_type, /obj/effect/proc_holder/spell))
- projectile = new /obj/effect/proc_holder/spell/targeted/trigger(user)
- var/obj/effect/proc_holder/spell/targeted/trigger/T = projectile
+ projectile = new /obj/effect/proc_holder/spell/pointed/trigger(user)
+ var/obj/effect/proc_holder/spell/pointed/trigger/T = projectile
T.linked_spells += proj_type
projectile.icon = proj_icon
projectile.icon_state = proj_icon_state
diff --git a/code/modules/spells/spell_types/taeclowndo.dm b/code/modules/spells/spell_types/taeclowndo.dm
index 59826daf07..9d6d71a89f 100644
--- a/code/modules/spells/spell_types/taeclowndo.dm
+++ b/code/modules/spells/spell_types/taeclowndo.dm
@@ -42,6 +42,8 @@
return
. = ..()
+ if(!.)
+ return
new /obj/item/grown/bananapeel(target)
/obj/effect/proc_holder/spell/aimed/banana_peel/update_icon()
diff --git a/code/modules/spells/spell_types/trigger.dm b/code/modules/spells/spell_types/trigger.dm
index 39cff63d98..df579d9243 100644
--- a/code/modules/spells/spell_types/trigger.dm
+++ b/code/modules/spells/spell_types/trigger.dm
@@ -1,30 +1,26 @@
-/obj/effect/proc_holder/spell/targeted/trigger
+/obj/effect/proc_holder/spell/pointed/trigger
name = "Trigger"
desc = "This spell triggers another spell or a few."
-
var/list/linked_spells = list() //those are just referenced by the trigger spell and are unaffected by it directly
var/list/starting_spells = list() //those are added on New() to contents from default spells and are deleted when the trigger spell is deleted to prevent memory leaks
-/obj/effect/proc_holder/spell/targeted/trigger/Initialize()
+/obj/effect/proc_holder/spell/pointed/trigger/Initialize()
. = ..()
-
for(var/spell in starting_spells)
var/spell_to_add = text2path(spell)
new spell_to_add(src) //should result in adding to contents, needs testing
-/obj/effect/proc_holder/spell/targeted/trigger/Destroy()
+/obj/effect/proc_holder/spell/pointed/trigger/Destroy()
for(var/spell in contents)
qdel(spell)
linked_spells = null
starting_spells = null
return ..()
-/obj/effect/proc_holder/spell/targeted/trigger/cast(list/targets,mob/user = usr)
+/obj/effect/proc_holder/spell/pointed/trigger/cast(list/targets,mob/user = usr)
playMagSound()
for(var/mob/living/target in targets)
for(var/obj/effect/proc_holder/spell/spell in contents)
spell.perform(list(target),0)
for(var/obj/effect/proc_holder/spell/spell in linked_spells)
spell.perform(list(target),0)
-
- return
\ No newline at end of file
diff --git a/code/modules/spells/spell_types/wizard.dm b/code/modules/spells/spell_types/wizard.dm
index 13c47ab9ac..14f359ef81 100644
--- a/code/modules/spells/spell_types/wizard.dm
+++ b/code/modules/spells/spell_types/wizard.dm
@@ -207,40 +207,12 @@
summon_type = list(/mob/living/simple_animal/hostile/netherworld)
cast_sound = 'sound/magic/summonitems_generic.ogg'
-/obj/effect/proc_holder/spell/targeted/trigger/blind
- name = "Blind"
- desc = "This spell temporarily blinds a single person and does not require wizard garb."
-
- school = "transmutation"
- charge_max = 300
- clothes_req = NONE
- invocation = "STI KALY"
- invocation_type = "whisper"
- message = "Your eyes cry out in pain!"
- cooldown_min = 50 //12 deciseconds reduction per rank
-
- starting_spells = list("/obj/effect/proc_holder/spell/targeted/inflict_handler/blind","/obj/effect/proc_holder/spell/targeted/genetic/blind")
-
- action_icon_state = "blind"
-
/obj/effect/proc_holder/spell/aoe_turf/conjure/creature/cult
name = "Summon Creatures (DANGEROUS)"
clothes_req = SPELL_CULT_GARB
charge_max = 5000
summon_amt = 2
-
-
-/obj/effect/proc_holder/spell/targeted/inflict_handler/blind
- amt_eye_blind = 10
- amt_eye_blurry = 20
- sound = 'sound/magic/blind.ogg'
-
-/obj/effect/proc_holder/spell/targeted/genetic/blind
- mutations = list(BLINDMUT)
- duration = 300
- sound = 'sound/magic/blind.ogg'
-
/obj/effect/proc_holder/spell/aoe_turf/repulse
name = "Repulse"
desc = "This spell throws everything around the user away."
diff --git a/code/modules/station_goals/bsa.dm b/code/modules/station_goals/bsa.dm
index 2ca0e65477..9ebcfe91d6 100644
--- a/code/modules/station_goals/bsa.dm
+++ b/code/modules/station_goals/bsa.dm
@@ -31,6 +31,7 @@
anchored = TRUE
/obj/machinery/bsa/wrench_act(mob/living/user, obj/item/I)
+ ..()
default_unfasten_wrench(user, I, 10)
return TRUE
@@ -142,17 +143,17 @@
/obj/machinery/bsa/full/proc/get_front_turf()
switch(dir)
if(WEST)
- return locate(x - 6,y,z)
+ return locate(x - 7,y,z)
if(EAST)
- return locate(x + 4,y,z)
+ return locate(x + 7,y,z)
return get_turf(src)
/obj/machinery/bsa/full/proc/get_back_turf()
switch(dir)
if(WEST)
- return locate(x + 4,y,z)
+ return locate(x + 5,y,z)
if(EAST)
- return locate(x - 6,y,z)
+ return locate(x - 5,y,z)
return get_turf(src)
/obj/machinery/bsa/full/proc/get_target_turf()
@@ -169,27 +170,50 @@
switch(cannon_direction)
if(WEST)
setDir(WEST)
- pixel_x = -192
top_layer.icon_state = "top_west"
icon_state = "cannon_west"
if(EAST)
setDir(EAST)
+ pixel_x = -128
+ bound_x = -128
top_layer.icon_state = "top_east"
icon_state = "cannon_east"
add_overlay(top_layer)
reload()
/obj/machinery/bsa/full/proc/fire(mob/user, turf/bullseye)
- var/turf/point = get_front_turf()
- for(var/turf/T in getline(get_step(point,dir),get_target_turf()))
- T.ex_act(EXPLODE_DEVASTATE)
- point.Beam(get_target_turf(),icon_state="bsa_beam",time=50,maxdistance = world.maxx) //ZZZAP
-
- message_admins("[ADMIN_LOOKUPFLW(user)] has launched an artillery strike.")
- explosion(bullseye,ex_power,ex_power*2,ex_power*4)
-
reload()
+ var/turf/point = get_front_turf()
+ var/turf/target = get_target_turf()
+ var/atom/movable/blocker
+ for(var/T in getline(get_step(point, dir), target))
+ var/turf/tile = T
+ if(SEND_SIGNAL(tile, COMSIG_ATOM_BSA_BEAM) & COMSIG_ATOM_BLOCKS_BSA_BEAM)
+ blocker = tile
+ else
+ for(var/AM in tile)
+ var/atom/movable/stuff = AM
+ if(SEND_SIGNAL(stuff, COMSIG_ATOM_BSA_BEAM) & COMSIG_ATOM_BLOCKS_BSA_BEAM)
+ blocker = stuff
+ break
+ if(blocker)
+ target = tile
+ break
+ else
+ tile.ex_act(EXPLODE_DEVASTATE)
+ point.Beam(target, icon_state = "bsa_beam", time = 50, maxdistance = world.maxx) //ZZZAP
+ new /obj/effect/temp_visual/bsa_splash(point, dir)
+
+ if(!blocker)
+ message_admins("[ADMIN_LOOKUPFLW(user)] has launched an artillery strike targeting [ADMIN_VERBOSEJMP(bullseye)].")
+ log_game("[key_name(user)] has launched an artillery strike targeting [AREACOORD(bullseye)].")
+ explosion(bullseye, ex_power, ex_power*2, ex_power*4)
+ else
+ message_admins("[ADMIN_LOOKUPFLW(user)] has launched an artillery strike targeting [ADMIN_VERBOSEJMP(bullseye)] but it was blocked by [blocker] at [ADMIN_VERBOSEJMP(target)].")
+ log_game("[key_name(user)] has launched an artillery strike targeting [AREACOORD(bullseye)] but it was blocked by [blocker] at [AREACOORD(target)].")
+
+
/obj/machinery/bsa/full/proc/reload()
ready = FALSE
use_power(power_used_per_shot)
@@ -210,20 +234,23 @@
/obj/machinery/computer/bsa_control
name = "bluespace artillery control"
- var/obj/machinery/bsa/full/cannon
- var/notice
- var/target
use_power = NO_POWER_USE
circuit = /obj/item/circuitboard/computer/bsa_control
icon = 'icons/obj/machines/particle_accelerator.dmi'
icon_state = "control_boxp"
+
+ var/obj/machinery/bsa/full/cannon
+ var/notice
+ var/target
var/area_aim = FALSE //should also show areas for targeting
-/obj/machinery/computer/bsa_control/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \
- datum/tgui/master_ui = null, datum/ui_state/state = GLOB.physical_state)
- ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+/obj/machinery/computer/bsa_control/ui_state(mob/user)
+ return GLOB.physical_state
+
+/obj/machinery/computer/bsa_control/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
- ui = new(user, src, ui_key, "BluespaceArtillery", name, ui_x, ui_y, master_ui, state)
+ ui = new(user, src, "BluespaceArtillery", name)
ui.open()
/obj/machinery/computer/bsa_control/ui_data()
@@ -255,7 +282,7 @@
if(!GLOB.bsa_unlock)
return
var/list/gps_locators = list()
- for(var/obj/item/gps/G in GLOB.GPS_list) //nulls on the list somehow
+ for(var/datum/component/gps/G in GLOB.GPS_list) //nulls on the list somehow
if(G.tracking)
gps_locators[G.gpstag] = G
@@ -269,15 +296,16 @@
/obj/machinery/computer/bsa_control/proc/get_target_name()
if(istype(target, /area))
return get_area_name(target, TRUE)
- else if(istype(target, /obj/item/gps))
- var/obj/item/gps/G = target
+ else if(istype(target, /datum/component/gps))
+ var/datum/component/gps/G = target
return G.gpstag
/obj/machinery/computer/bsa_control/proc/get_impact_turf()
if(istype(target, /area))
return pick(get_area_turfs(target))
- else if(istype(target, /obj/item/gps))
- return get_turf(target)
+ else if(istype(target, /datum/component/gps))
+ var/datum/component/gps/G = target
+ return get_turf(G.parent)
/obj/machinery/computer/bsa_control/proc/fire(mob/user)
if(cannon.stat)
diff --git a/code/modules/station_goals/dna_vault.dm b/code/modules/station_goals/dna_vault.dm
index 6d8ab9cc7f..4ac3777a41 100644
--- a/code/modules/station_goals/dna_vault.dm
+++ b/code/modules/station_goals/dna_vault.dm
@@ -174,14 +174,13 @@
. = ..()
-/obj/machinery/dna_vault/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.physical_state)
- ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+/obj/machinery/dna_vault/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
roll_powers(user)
- ui = new(user, src, ui_key, "DnaVault", name, ui_x, ui_y, master_ui, state)
+ ui = new(user, src, "DnaVault", name)
ui.open()
-
/obj/machinery/dna_vault/proc/roll_powers(mob/user)
if(user in power_lottery)
return
diff --git a/code/modules/station_goals/shield.dm b/code/modules/station_goals/shield.dm
index 299fda4a26..c8fbda8988 100644
--- a/code/modules/station_goals/shield.dm
+++ b/code/modules/station_goals/shield.dm
@@ -42,10 +42,10 @@
circuit = /obj/item/circuitboard/computer/sat_control
var/notice
-/obj/machinery/computer/sat_control/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state)
- ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+/obj/machinery/computer/sat_control/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
- ui = new(user, src, ui_key, "SatelliteControl", name, ui_x, ui_y, master_ui, state)
+ ui = new(user, src, "SatelliteControl", name)
ui.open()
/obj/machinery/computer/sat_control/ui_act(action, params)
diff --git a/code/modules/surgery/bodyparts/_bodyparts.dm b/code/modules/surgery/bodyparts/_bodyparts.dm
index 07c80322ae..e90f86bf1c 100644
--- a/code/modules/surgery/bodyparts/_bodyparts.dm
+++ b/code/modules/surgery/bodyparts/_bodyparts.dm
@@ -86,12 +86,12 @@
/// A hat won't cover your face, but a shirt covering your chest will cover your... you know, chest
var/scars_covered_by_clothes = TRUE
- /// Descriptions for the locations on the limb for scars to be assigned, just cosmetic
- var/list/specific_locations = list("general area")
/// So we know if we need to scream if this limb hits max damage
var/last_maxed
/// How much generic bleedstacks we have on this bodypart
var/generic_bleedstacks
+ /// If we have a gauze wrapping currently applied (not including splints)
+ var/obj/item/stack/current_gauze
/obj/item/bodypart/examine(mob/user)
. = ..()
@@ -151,8 +151,20 @@
var/turf/T = get_turf(src)
if(status != BODYPART_ROBOTIC)
playsound(T, 'sound/misc/splort.ogg', 50, 1, -1)
- for(var/obj/item/I in src)
- I.forceMove(T)
+ if(current_gauze)
+ QDEL_NULL(current_gauze)
+ for(var/obj/item/organ/drop_organ in get_organs())
+ drop_organ.transfer_to_limb(src, owner)
+
+///since organs aren't actually stored in the bodypart themselves while attached to a person, we have to query the owner for what we should have
+/obj/item/bodypart/proc/get_organs()
+ if(!owner)
+ return
+ . = list()
+ for(var/i in owner.internal_organs) //internal organs inside the dismembered limb are dropped.
+ var/obj/item/organ/organ_check = i
+ if(check_zone(organ_check.zone) == body_zone)
+ . += organ_check
/obj/item/bodypart/proc/consider_processing()
if(stamina_dam > DAMAGE_PRECISION)
@@ -171,7 +183,7 @@
//Applies brute and burn damage to the organ. Returns 1 if the damage-icon states changed at all.
//Damage will not exceed max_damage using this proc
//Cannot apply negative damage
-/obj/item/bodypart/proc/receive_damage(brute = 0, burn = 0, stamina = 0, blocked = 0, updating_health = TRUE, required_status = null, wound_bonus = 0, bare_wound_bonus = 0, sharpness = FALSE) // maybe separate BRUTE_SHARP and BRUTE_OTHER eventually somehow hmm
+/obj/item/bodypart/proc/receive_damage(brute = 0, burn = 0, stamina = 0, blocked = 0, updating_health = TRUE, required_status = null, wound_bonus = 0, bare_wound_bonus = 0, sharpness = SHARP_NONE) // maybe separate BRUTE_SHARP and BRUTE_OTHER eventually somehow hmm
if(owner && (owner.status_flags & GODMODE))
return FALSE //godmode
var/dmg_mlt = CONFIG_GET(number/damage_multiplier)
@@ -192,16 +204,64 @@
if(ALIEN_BODYPART,LARVA_BODYPART) //aliens take some additional burn //nothing can burn with so much snowflake code around
burn *= 1.2
- var/wounding_type = (brute > burn ? WOUND_BRUTE : WOUND_BURN)
+ /*
+ // START WOUND HANDLING
+ */
+
+ // what kind of wounds we're gonna roll for, take the greater between brute and burn, then if it's brute, we subdivide based on sharpness
+ var/wounding_type = (brute > burn ? WOUND_BLUNT : WOUND_BURN)
var/wounding_dmg = max(brute, burn)
- if(wounding_type == WOUND_BRUTE && sharpness)
- wounding_type = WOUND_SHARP
- // i know this is effectively the same check as above but i don't know if those can null the damage by rounding and want to be safe
- if(owner && wounding_dmg > 4 && wound_bonus != CANT_WOUND)
- // if you want to make tox wounds or some other type, this will need to be expanded and made more modular
- // handle all our wounding stuff
+ var/mangled_state = get_mangled_state()
+ var/bio_state = owner.get_biological_state()
+ var/easy_dismember = HAS_TRAIT(owner, TRAIT_EASYDISMEMBER) // if we have easydismember, we don't reduce damage when redirecting damage to different types (slashing weapons on mangled/skinless limbs attack at 100% instead of 50%)
+
+ if(wounding_type == WOUND_BLUNT)
+ if(sharpness == SHARP_EDGED)
+ wounding_type = WOUND_SLASH
+ else if(sharpness == SHARP_POINTY)
+ wounding_type = WOUND_PIERCE
+
+ //Handling for bone only/flesh only(none right now)/flesh and bone targets
+ switch(bio_state)
+ // if we're bone only, all cutting attacks go straight to the bone
+ if(BIO_JUST_BONE)
+ if(wounding_type == WOUND_SLASH)
+ wounding_type = WOUND_BLUNT
+ wounding_dmg *= (easy_dismember ? 1 : 0.5)
+ else if(wounding_type == WOUND_PIERCE)
+ wounding_type = WOUND_BLUNT
+ wounding_dmg *= (easy_dismember ? 1 : 0.75)
+ if((mangled_state & BODYPART_MANGLED_BONE) && try_dismember(wounding_type, wounding_dmg, wound_bonus, bare_wound_bonus))
+ return
+ // note that there's no handling for BIO_JUST_FLESH since we don't have any that are that right now (slimepeople maybe someday)
+ // standard humanoids
+ if(BIO_FLESH_BONE)
+ // if we've already mangled the skin (critical slash or piercing wound), then the bone is exposed, and we can damage it with sharp weapons at a reduced rate
+ // So a big sharp weapon is still all you need to destroy a limb
+ if(mangled_state == BODYPART_MANGLED_FLESH && sharpness)
+ playsound(src, "sound/effects/wounds/crackandbleed.ogg", 100)
+ if(wounding_type == WOUND_SLASH && !easy_dismember)
+ wounding_dmg *= 0.5 // edged weapons pass along 50% of their wounding damage to the bone since the power is spread out over a larger area
+ if(wounding_type == WOUND_PIERCE && !easy_dismember)
+ wounding_dmg *= 0.75 // piercing weapons pass along 75% of their wounding damage to the bone since it's more concentrated
+ wounding_type = WOUND_BLUNT
+ else if(mangled_state == BODYPART_MANGLED_BOTH && try_dismember(wounding_type, wounding_dmg, wound_bonus, bare_wound_bonus))
+ return
+
+ // now we have our wounding_type and are ready to carry on with wounds and dealing the actual damage
+ if(owner && wounding_dmg >= WOUND_MINIMUM_DAMAGE && wound_bonus != CANT_WOUND)
check_wounding(wounding_type, wounding_dmg, wound_bonus, bare_wound_bonus)
+ for(var/i in wounds)
+ var/datum/wound/iter_wound = i
+ iter_wound.receive_damage(wounding_type, wounding_dmg, wound_bonus)
+
+ /*
+ // END WOUND HANDLING
+ */
+
+ //back to our regularly scheduled program, we now actually apply damage if there's room below limb damage cap
+
var/can_inflict = max_damage - get_damage()
var/total_damage = brute + burn
@@ -213,10 +273,6 @@
if(can_inflict <= 0)
return FALSE
- for(var/i in wounds)
- var/datum/wound/W = i
- W.receive_damage(wounding_type, wounding_dmg, wound_bonus)
-
brute_dam += brute
burn_dam += burn
@@ -236,14 +292,58 @@
update_disabled()
return update_bodypart_damage_state()
+/// Allows us to roll for and apply a wound without actually dealing damage. Used for aggregate wounding power with pellet clouds
+/obj/item/bodypart/proc/painless_wound_roll(wounding_type, phantom_wounding_dmg, wound_bonus, bare_wound_bonus, sharpness=SHARP_NONE)
+ if(!owner || phantom_wounding_dmg <= WOUND_MINIMUM_DAMAGE || wound_bonus == CANT_WOUND)
+ return
+
+ var/mangled_state = get_mangled_state()
+ var/bio_state = owner.get_biological_state()
+ var/easy_dismember = HAS_TRAIT(owner, TRAIT_EASYDISMEMBER) // if we have easydismember, we don't reduce damage when redirecting damage to different types (slashing weapons on mangled/skinless limbs attack at 100% instead of 50%)
+
+ if(wounding_type == WOUND_BLUNT)
+ if(sharpness == SHARP_EDGED)
+ wounding_type = WOUND_SLASH
+ else if(sharpness == SHARP_POINTY)
+ wounding_type = WOUND_PIERCE
+
+ //Handling for bone only/flesh only(none right now)/flesh and bone targets
+ switch(bio_state)
+ // if we're bone only, all cutting attacks go straight to the bone
+ if(BIO_JUST_BONE)
+ if(wounding_type == WOUND_SLASH)
+ wounding_type = WOUND_BLUNT
+ phantom_wounding_dmg *= (easy_dismember ? 1 : 0.5)
+ else if(wounding_type == WOUND_PIERCE)
+ wounding_type = WOUND_BLUNT
+ phantom_wounding_dmg *= (easy_dismember ? 1 : 0.75)
+ if((mangled_state & BODYPART_MANGLED_BONE) && try_dismember(wounding_type, phantom_wounding_dmg, wound_bonus, bare_wound_bonus))
+ return
+ // note that there's no handling for BIO_JUST_FLESH since we don't have any that are that right now (slimepeople maybe someday)
+ // standard humanoids
+ if(BIO_FLESH_BONE)
+ // if we've already mangled the skin (critical slash or piercing wound), then the bone is exposed, and we can damage it with sharp weapons at a reduced rate
+ // So a big sharp weapon is still all you need to destroy a limb
+ if(mangled_state == BODYPART_MANGLED_FLESH && sharpness)
+ playsound(src, "sound/effects/wounds/crackandbleed.ogg", 100)
+ if(wounding_type == WOUND_SLASH && !easy_dismember)
+ phantom_wounding_dmg *= 0.5 // edged weapons pass along 50% of their wounding damage to the bone since the power is spread out over a larger area
+ if(wounding_type == WOUND_PIERCE && !easy_dismember)
+ phantom_wounding_dmg *= 0.75 // piercing weapons pass along 75% of their wounding damage to the bone since it's more concentrated
+ wounding_type = WOUND_BLUNT
+ else if(mangled_state == BODYPART_MANGLED_BOTH && try_dismember(wounding_type, phantom_wounding_dmg, wound_bonus, bare_wound_bonus))
+ return
+
+ check_wounding(wounding_type, phantom_wounding_dmg, wound_bonus, bare_wound_bonus)
+
/**
* check_wounding() is where we handle rolling for, selecting, and applying a wound if we meet the criteria
*
- * We generate a "score" for how woundable the attack was based on the damage and other factors discussed in [check_wounding_mods()], then go down the list from most severe to least severe wounds in that category.
+ * We generate a "score" for how woundable the attack was based on the damage and other factors discussed in [/obj/item/bodypart/proc/check_wounding_mods], then go down the list from most severe to least severe wounds in that category.
* We can promote a wound from a lesser to a higher severity this way, but we give up if we have a wound of the given type and fail to roll a higher severity, so no sidegrades/downgrades
*
* Arguments:
- * * woundtype- Either WOUND_SHARP, WOUND_BRUTE, or WOUND_BURN based on the attack type.
+ * * woundtype- Either WOUND_BLUNT, WOUND_SLASH, WOUND_PIERCE, or WOUND_BURN based on the attack type.
* * damage- How much damage is tied to this attack, since wounding potential scales with damage in an attack (see: WOUND_DAMAGE_EXPONENT)
* * wound_bonus- The wound_bonus of an attack
* * bare_wound_bonus- The bare_wound_bonus of an attack
@@ -252,19 +352,24 @@
// actually roll wounds if applicable
if(HAS_TRAIT(owner, TRAIT_EASYLIMBDISABLE))
damage *= 1.5
+ else
+ damage = min(damage, WOUND_MAX_CONSIDERED_DAMAGE)
- var/base_roll = rand(1, round(damage ** WOUND_DAMAGE_EXPONENT))
+ var/base_roll = rand(max(damage/1.5,25), round(damage ** WOUND_DAMAGE_EXPONENT))
var/injury_roll = base_roll
injury_roll += check_woundings_mods(woundtype, damage, wound_bonus, bare_wound_bonus)
- var/list/wounds_checking
+ var/list/wounds_checking = GLOB.global_wound_types[woundtype]
- switch(woundtype)
- if(WOUND_SHARP)
- wounds_checking = WOUND_LIST_CUT
- if(WOUND_BRUTE)
- wounds_checking = WOUND_LIST_BONE
- if(WOUND_BURN)
- wounds_checking = WOUND_LIST_BURN
+ // quick re-check to see if bare_wound_bonus applies, for the benefit of log_wound(), see about getting the check from check_woundings_mods() somehow
+ if(ishuman(owner))
+ var/mob/living/carbon/human/human_wearer = owner
+ var/list/clothing = human_wearer.clothingonpart(src)
+ for(var/i in clothing)
+ var/obj/item/clothing/clothes_check = i
+ // unlike normal armor checks, we tabluate these piece-by-piece manually so we can also pass on appropriate damage the clothing's limbs if necessary
+ if(clothes_check.armor.getRating("wound"))
+ bare_wound_bonus = 0
+ break
//cycle through the wounds of the relevant category from the most severe down
for(var/PW in wounds_checking)
@@ -279,21 +384,22 @@
replaced_wound = existing_wound
if(initial(possible_wound.threshold_minimum) < injury_roll)
+ var/datum/wound/new_wound
if(replaced_wound)
- var/datum/wound/new_wound = replaced_wound.replace_wound(possible_wound)
- log_wound(owner, new_wound, damage, wound_bonus, bare_wound_bonus, base_roll)
+ new_wound = replaced_wound.replace_wound(possible_wound)
+ log_wound(owner, new_wound, damage, wound_bonus, bare_wound_bonus, base_roll) // dismembering wounds are logged in the apply_wound() for loss wounds since they delete themselves immediately, these will be immediately returned
else
- var/datum/wound/new_wound = new possible_wound
+ new_wound = new possible_wound
new_wound.apply_wound(src)
log_wound(owner, new_wound, damage, wound_bonus, bare_wound_bonus, base_roll)
- return
+ return new_wound
// try forcing a specific wound, but only if there isn't already a wound of that severity or greater for that type on this bodypart
/obj/item/bodypart/proc/force_wound_upwards(specific_woundtype, smited = FALSE)
var/datum/wound/potential_wound = specific_woundtype
for(var/i in wounds)
var/datum/wound/existing_wound = i
- if(existing_wound.type in (initial(potential_wound.wound_type)))
+ if(existing_wound.wound_type == initial(potential_wound.wound_type))
if(existing_wound.severity < initial(potential_wound.severity)) // we only try if the existing one is inferior to the one we're trying to force
existing_wound.replace_wound(potential_wound, smited)
return
@@ -301,12 +407,20 @@
var/datum/wound/new_wound = new potential_wound
new_wound.apply_wound(src, smited = smited)
+/**
+ * check_wounding_mods() is where we handle the various modifiers of a wound roll
+ *
+ * A short list of things we consider: any armor a human target may be wearing, and if they have no wound armor on the limb, if we have a bare_wound_bonus to apply, plus the plain wound_bonus
+ * We also flick through all of the wounds we currently have on this limb and add their threshold penalties, so that having lots of bad wounds makes you more liable to get hurt worse
+ * Lastly, we add the inherent wound_resistance variable the bodypart has (heads and chests are slightly harder to wound), and a small bonus if the limb is already disabled
+ *
+ * Arguments:
+ * * It's the same ones on [receive_damage]
+ */
/obj/item/bodypart/proc/check_woundings_mods(wounding_type, damage, wound_bonus, bare_wound_bonus)
var/armor_ablation = 0
var/injury_mod = 0
- //var/bwb = 0
-
if(owner && ishuman(owner))
var/mob/living/carbon/human/H = owner
var/list/clothing = H.clothingonpart(src)
@@ -314,7 +428,7 @@
var/obj/item/clothing/C = c
// unlike normal armor checks, we tabluate these piece-by-piece manually so we can also pass on appropriate damage the clothing's limbs if necessary
armor_ablation += C.armor.getRating("wound")
- if(wounding_type == WOUND_SHARP)
+ if(wounding_type == WOUND_SLASH)
C.take_damage_zone(body_zone, damage, BRUTE, armour_penetration)
else if(wounding_type == WOUND_BURN && damage >= 10) // lazy way to block freezing from shredding clothes without adding another var onto apply_damage()
C.take_damage_zone(body_zone, damage, BURN, armour_penetration)
@@ -330,7 +444,7 @@
injury_mod += W.threshold_penalty
var/part_mod = -wound_resistance
- if(is_disabled())
+ if(get_damage(TRUE) >= max_damage)
part_mod += disabled_wound_penalty
injury_mod += part_mod
@@ -383,11 +497,11 @@
return BODYPART_DISABLED_WOUND
if(can_dismember() && !HAS_TRAIT(owner, TRAIT_NODISMEMBER))
. = disabled //inertia, to avoid limbs healing 0.1 damage and being re-enabled
- if((get_damage(TRUE) >= max_damage) || (HAS_TRAIT(owner, TRAIT_EASYLIMBDISABLE) && (get_damage(TRUE) >= (max_damage * 0.6)))) //Easy limb disable disables the limb at 40% health instead of 0%
+ if(get_damage(TRUE) >= max_damage * (HAS_TRAIT(owner, TRAIT_EASYLIMBDISABLE) ? 0.6 : 1)) //Easy limb disable disables the limb at 40% health instead of 0%
if(!last_maxed)
owner.emote("scream")
last_maxed = TRUE
- if(!is_organic_limb())
+ if(!is_organic_limb() || stamina_dam >= max_damage)
return BODYPART_DISABLED_DAMAGE
else if(disabled && (get_damage(TRUE) <= (max_damage * 0.8))) // reenabled at 80% now instead of 50% as of wounds update
last_maxed = FALSE
@@ -484,7 +598,7 @@
var/datum/species/S = H.dna.species
base_bp_icon = S?.icon_limbs || DEFAULT_BODYPART_ICON
- species_id = S.limbs_id
+ species_id = S.mutant_bodyparts["limbs_id"]
species_flags_list = H.dna.species.species_traits
//body marking memes
@@ -499,7 +613,6 @@
if(S.use_skintones)
skin_tone = H.skin_tone
- base_bp_icon = (base_bp_icon == DEFAULT_BODYPART_ICON) ? DEFAULT_BODYPART_ICON_ORGANIC : base_bp_icon
else
skin_tone = ""
@@ -512,13 +625,9 @@
species_color = S.fixed_mut_color
else
species_color = H.dna.features["mcolor"]
- base_bp_icon = (base_bp_icon == DEFAULT_BODYPART_ICON) ? DEFAULT_BODYPART_ICON_ORGANIC : base_bp_icon
else
species_color = ""
- if(base_bp_icon != DEFAULT_BODYPART_ICON)
- color_src = mut_colors ? MUTCOLORS : ((H.dna.skin_tone_override && S.use_skintones == USE_SKINTONES_GRAYSCALE_CUSTOM) ? CUSTOM_SKINTONE : SKINTONE)
-
if(S.mutant_bodyparts["legs"])
if(body_zone == BODY_ZONE_L_LEG || body_zone == BODY_ZONE_R_LEG)
if(DIGITIGRADE in S.species_traits)
@@ -538,11 +647,16 @@
body_markings = "plain"
aux_marking = "plain"
markings_color = list(colorlist)
-
else
body_markings = null
aux_marking = null
+ if(species_id in GLOB.greyscale_limb_types) //should they have greyscales?
+ base_bp_icon = DEFAULT_BODYPART_ICON_ORGANIC
+
+ if(base_bp_icon != DEFAULT_BODYPART_ICON)
+ color_src = mut_colors ? MUTCOLORS : ((H.dna.skin_tone_override && S.use_skintones == USE_SKINTONES_GRAYSCALE_CUSTOM) ? CUSTOM_SKINTONE : SKINTONE)
+
if(!dropping_limb && H.dna.check_mutation(HULK))
mutation_color = "00aa00"
else
@@ -741,17 +855,29 @@
/obj/item/bodypart/proc/get_wound_type(checking_type)
if(isnull(wounds))
return
- for(var/thing in wounds)
- var/datum/wound/W = thing
- if(istype(W, checking_type))
- return W
+ for(var/i in wounds)
+ if(istype(i, checking_type))
+ return i
-/// very rough start for updating efficiency and other stats on a body part whenever a wound is gained/lost
-/obj/item/bodypart/proc/update_wounds()
+/**
+ * update_wounds() is called whenever a wound is gained or lost on this bodypart, as well as if there's a change of some kind on a bone wound possibly changing disabled status
+ *
+ * Covers tabulating the damage multipliers we have from wounds (burn specifically), as well as deleting our gauze wrapping if we don't have any wounds that can use bandaging
+ *
+ * Arguments:
+ * * replaced- If true, this is being called from the remove_wound() of a wound that's being replaced, so the bandage that already existed is still relevant, but the new wound hasn't been added yet
+ */
+/obj/item/bodypart/proc/update_wounds(replaced = FALSE)
var/dam_mul = 1 //initial(wound_damage_multiplier)
// we can only have one wound per type, but remember there's multiple types
- for(var/datum/wound/W in wounds)
- dam_mul *= W.damage_mulitplier_penalty
+ // we can (normally) only have one wound per type, but remember there's multiple types (smites like :B:loodless can generate multiple cuts on a limb)
+ for(var/i in wounds)
+ var/datum/wound/iter_wound = i
+ dam_mul *= iter_wound.damage_mulitplier_penalty
+
+ if(!LAZYLEN(wounds) && current_gauze && !replaced)
+ owner.visible_message("\The [current_gauze] on [owner]'s [name] fall away.", "The [current_gauze] on your [name] fall away.")
+ QDEL_NULL(current_gauze)
wound_damage_multiplier = dam_mul
update_disabled()
@@ -761,8 +887,6 @@
var/bleed_rate = 0
if(generic_bleedstacks > 0)
bleed_rate++
- if(brute_dam >= 40)
- bleed_rate += (brute_dam * 0.008)
//We want an accurate reading of .len
listclearnulls(embedded_objects)
@@ -773,5 +897,40 @@
for(var/thing in wounds)
var/datum/wound/W = thing
bleed_rate += W.blood_flow
-
+ if(owner.mobility_flags & ~MOBILITY_STAND)
+ bleed_rate *= 0.75
return bleed_rate
+
+/**
+ * apply_gauze() is used to- well, apply gauze to a bodypart
+ *
+ * As of the Wounds 2 PR, all bleeding is now bodypart based rather than the old bleedstacks system, and 90% of standard bleeding comes from flesh wounds (the exception is embedded weapons).
+ * The same way bleeding is totaled up by bodyparts, gauze now applies to all wounds on the same part. Thus, having a slash wound, a pierce wound, and a broken bone wound would have the gauze
+ * applying blood staunching to the first two wounds, while also acting as a sling for the third one. Once enough blood has been absorbed or all wounds with the ACCEPTS_GAUZE flag have been cleared,
+ * the gauze falls off.
+ *
+ * Arguments:
+ * * gauze- Just the gauze stack we're taking a sheet from to apply here
+ */
+/obj/item/bodypart/proc/apply_gauze(obj/item/stack/gauze)
+ if(!istype(gauze) || !gauze.absorption_capacity)
+ return
+ QDEL_NULL(current_gauze)
+ current_gauze = new gauze.type(src, 1)
+ gauze.use(1)
+
+/**
+ * seep_gauze() is for when a gauze wrapping absorbs blood or pus from wounds, lowering its absorption capacity.
+ *
+ * The passed amount of seepage is deducted from the bandage's absorption capacity, and if we reach a negative absorption capacity, the bandages fall off and we're left with nothing.
+ *
+ * Arguments:
+ * * seep_amt - How much absorption capacity we're removing from our current bandages (think, how much blood or pus are we soaking up this tick?)
+ */
+/obj/item/bodypart/proc/seep_gauze(seep_amt = 0)
+ if(!current_gauze)
+ return
+ current_gauze.absorption_capacity -= seep_amt
+ if(current_gauze.absorption_capacity < 0)
+ owner.visible_message("\The [current_gauze] on [owner]'s [name] fall away in rags.", "\The [current_gauze] on your [name] fall away in rags.", vision_distance=COMBAT_MESSAGE_RANGE)
+ QDEL_NULL(current_gauze)
diff --git a/code/modules/surgery/bodyparts/dismemberment.dm b/code/modules/surgery/bodyparts/dismemberment.dm
index 2ce6ca8862..fa003e3f3c 100644
--- a/code/modules/surgery/bodyparts/dismemberment.dm
+++ b/code/modules/surgery/bodyparts/dismemberment.dm
@@ -4,7 +4,7 @@
return TRUE
//Dismember a limb
-/obj/item/bodypart/proc/dismember(dam_type = BRUTE)
+/obj/item/bodypart/proc/dismember(dam_type = BRUTE, silent=TRUE)
if(!owner)
return FALSE
var/mob/living/carbon/C = owner
@@ -16,7 +16,8 @@
return FALSE
var/obj/item/bodypart/affecting = C.get_bodypart(BODY_ZONE_CHEST)
affecting.receive_damage(clamp(brute_dam/2 * affecting.body_damage_coeff, 15, 50), clamp(burn_dam/2 * affecting.body_damage_coeff, 0, 50), wound_bonus=CANT_WOUND) //Damage the chest based on limb's existing damage
- C.visible_message("[C]'s [src.name] has been violently dismembered!")
+ if(!silent)
+ C.visible_message("[C]'s [name] is violently dismembered!")
C.emote("scream")
SEND_SIGNAL(C, COMSIG_ADD_MOOD_EVENT, "dismembered", /datum/mood_event/dismembered)
drop_limb()
@@ -30,6 +31,7 @@
burn()
return TRUE
add_mob_blood(C)
+ C.bleed(rand(20, 40))
var/direction = pick(GLOB.cardinals)
var/t_range = rand(2,max(throw_range/2, 2))
var/turf/target_turf = get_turf(src)
@@ -80,8 +82,6 @@
if(organ_spilled)
C.visible_message("[C]'s internal organs spill out onto the floor!")
-
-
//limb removal. The "special" argument is used for swapping a limb with a new one without the effects of losing a limb kicking in.
/obj/item/bodypart/proc/drop_limb(special, dismembered)
if(!owner)
@@ -153,7 +153,52 @@
forceMove(Tsec)
+/**
+ * get_mangled_state() is relevant for flesh and bone bodyparts, and returns whether this bodypart has mangled skin, mangled bone, or both (or neither i guess)
+ *
+ * Dismemberment for flesh and bone requires the victim to have the skin on their bodypart destroyed (either a critical cut or piercing wound), and at least a hairline fracture
+ * (severe bone), at which point we can start rolling for dismembering. The attack must also deal at least 10 damage, and must be a brute attack of some kind (sorry for now, cakehat, maybe later)
+ *
+ * Returns: BODYPART_MANGLED_NONE if we're fine, BODYPART_MANGLED_FLESH if our skin is broken, BODYPART_MANGLED_BONE if our bone is broken, or BODYPART_MANGLED_BOTH if both are broken and we're up for dismembering
+ */
+/obj/item/bodypart/proc/get_mangled_state()
+ . = BODYPART_MANGLED_NONE
+ for(var/i in wounds)
+ var/datum/wound/iter_wound = i
+ if((iter_wound.wound_flags & MANGLES_BONE))
+ . |= BODYPART_MANGLED_BONE
+ if((iter_wound.wound_flags & MANGLES_FLESH))
+ . |= BODYPART_MANGLED_FLESH
+
+/**
+ * try_dismember() is used, once we've confirmed that a flesh and bone bodypart has both the skin and bone mangled, to actually roll for it
+ *
+ * Mangling is described in the above proc, [/obj/item/bodypart/proc/get_mangled_state()]. This simply makes the roll for whether we actually dismember or not
+ * using how damaged the limb already is, and how much damage this blow was for. If we have a critical bone wound instead of just a severe, we add +10% to the roll.
+ * Lastly, we choose which kind of dismember we want based on the wounding type we hit with. Note we don't care about all the normal mods or armor for this
+ *
+ * Arguments:
+ * * wounding_type: Either WOUND_BLUNT, WOUND_SLASH, or WOUND_PIERCE, basically only matters for the dismember message
+ * * wounding_dmg: The damage of the strike that prompted this roll, higher damage = higher chance
+ * * wound_bonus: Not actually used right now, but maybe someday
+ * * bare_wound_bonus: ditto above
+ */
+/obj/item/bodypart/proc/try_dismember(wounding_type, wounding_dmg, wound_bonus, bare_wound_bonus)
+ if(wounding_dmg < DISMEMBER_MINIMUM_DAMAGE)
+ return
+
+ var/base_chance = wounding_dmg + (get_damage() / max_damage * 50) // how much damage we dealt with this blow, + 50% of the damage percentage we already had on this bodypart
+ if(locate(/datum/wound/blunt/critical) in wounds) // we only require a severe bone break, but if there's a critical bone break, we'll add 10% more
+ base_chance += 10
+
+ if(!prob(base_chance))
+ return
+
+ var/datum/wound/loss/dismembering = new
+ dismembering.apply_dismember(src, wounding_type)
+
+ return TRUE
//when a limb is dropped, the internal organs are removed from the mob and put into the limb
/obj/item/organ/proc/transfer_to_limb(obj/item/bodypart/LB, mob/living/carbon/C)
@@ -378,7 +423,7 @@
/mob/living/carbon/regenerate_limb(limb_zone, noheal)
var/obj/item/bodypart/L
if(get_bodypart(limb_zone))
- return 0
+ return FALSE
L = newBodyPart(limb_zone, 0, 0)
if(L)
if(!noheal)
@@ -386,6 +431,8 @@
L.burn_dam = 0
L.brutestate = 0
L.burnstate = 0
-
+ var/datum/scar/scaries = new
+ var/datum/wound/loss/phantom_loss = new // stolen valor, really
+ scaries.generate(L, phantom_loss)
L.attach_limb(src, 1)
- return 1
+ return TRUE
diff --git a/code/modules/surgery/bodyparts/head.dm b/code/modules/surgery/bodyparts/head.dm
index a67f16b934..13b1140527 100644
--- a/code/modules/surgery/bodyparts/head.dm
+++ b/code/modules/surgery/bodyparts/head.dm
@@ -36,11 +36,10 @@
var/custom_head
wound_resistance = 10
- specific_locations = list("left eyebrow", "cheekbone", "neck", "throat", "jawline", "entire face")
scars_covered_by_clothes = FALSE
/obj/item/bodypart/head/can_dismember(obj/item/I)
- if(!((owner.stat == DEAD) || owner.InFullCritical()))
+ if(owner && !((owner.stat == DEAD) || owner.InFullCritical()))
return FALSE
return ..()
diff --git a/code/modules/surgery/bodyparts/parts.dm b/code/modules/surgery/bodyparts/parts.dm
index a18b31e4c1..5a887ee6b7 100644
--- a/code/modules/surgery/bodyparts/parts.dm
+++ b/code/modules/surgery/bodyparts/parts.dm
@@ -12,7 +12,7 @@
var/obj/item/cavity_item
/obj/item/bodypart/chest/can_dismember(obj/item/I)
- if(!((owner.stat == DEAD) || owner.InFullCritical()))
+ if(!((owner.stat == DEAD) || owner.InFullCritical()) || !get_organs())
return FALSE
return ..()
diff --git a/code/modules/surgery/bone_mending.dm b/code/modules/surgery/bone_mending.dm
index 81d9fa8d97..0c0083575b 100644
--- a/code/modules/surgery/bone_mending.dm
+++ b/code/modules/surgery/bone_mending.dm
@@ -8,7 +8,7 @@
target_mobtypes = list(/mob/living/carbon/human)
possible_locs = list(BODY_ZONE_R_ARM,BODY_ZONE_L_ARM,BODY_ZONE_R_LEG,BODY_ZONE_L_LEG,BODY_ZONE_CHEST,BODY_ZONE_HEAD)
requires_real_bodypart = TRUE
- targetable_wound = /datum/wound/brute/bone/severe
+ targetable_wound = /datum/wound/blunt/severe
/datum/surgery/repair_bone_hairline/can_start(mob/living/user, mob/living/carbon/target)
if(..())
@@ -23,7 +23,7 @@
target_mobtypes = list(/mob/living/carbon/human)
possible_locs = list(BODY_ZONE_R_ARM,BODY_ZONE_L_ARM,BODY_ZONE_R_LEG,BODY_ZONE_L_LEG,BODY_ZONE_CHEST,BODY_ZONE_HEAD)
requires_real_bodypart = TRUE
- targetable_wound = /datum/wound/brute/bone/critical
+ targetable_wound = /datum/wound/blunt/critical
/datum/surgery/repair_bone_compound/can_start(mob/living/user, mob/living/carbon/target)
if(..())
diff --git a/code/modules/surgery/burn_dressing.dm b/code/modules/surgery/burn_dressing.dm
index 1688f7b5d1..8bfa52d245 100644
--- a/code/modules/surgery/burn_dressing.dm
+++ b/code/modules/surgery/burn_dressing.dm
@@ -94,7 +94,8 @@
log_combat(user, target, "dressed burns in", addition="INTENT: [uppertext(user.a_intent)]")
burn_wound.sanitization += 3
burn_wound.flesh_healing += 5
- burn_wound.force_bandage(tool)
+ var/obj/item/bodypart/the_part = target.get_bodypart(target_zone)
+ the_part.apply_gauze(tool)
else
to_chat(user, "[target] has no burns there!")
return ..()
diff --git a/code/modules/surgery/organs/eyes.dm b/code/modules/surgery/organs/eyes.dm
index 049fc5b5d4..f23a03da5b 100644
--- a/code/modules/surgery/organs/eyes.dm
+++ b/code/modules/surgery/organs/eyes.dm
@@ -272,14 +272,24 @@
set_distance(clamp(range, 0, max_light_beam_distance))
assume_rgb(C)
+#define MAX_SATURATION 192
+#define MAX_LIGHTNESS 256
+
/obj/item/organ/eyes/robotic/glow/proc/assume_rgb(newcolor)
- current_color_string = newcolor
- eye_color = RGB2EYECOLORSTRING(current_color_string)
+ eye_color = RGB2EYECOLORSTRING(newcolor)
+ var/list/hsv = ReadHSV(RGBtoHSV(newcolor))
+ hsv[2] = clamp(hsv[2], 0, MAX_SATURATION)
+ hsv[3] = clamp(hsv[3], 0, MAX_LIGHTNESS)
+ var/new_hsv = hsv(hsv[1], hsv[2], hsv[3])
+ current_color_string = HSVtoRGB(new_hsv)
sync_light_effects()
cycle_mob_overlay()
if(!QDELETED(owner) && ishuman(owner)) //Other carbon mobs don't have eye color.
owner.dna.species.handle_body(owner)
+#undef MAX_SATURATION
+#undef MAX_LIGHTNESS
+
/obj/item/organ/eyes/robotic/glow/proc/cycle_mob_overlay()
remove_mob_overlay()
mob_overlay.color = current_color_string
@@ -396,4 +406,4 @@
#undef BLURRY_VISION_ONE
#undef BLURRY_VISION_TWO
-#undef BLIND_VISION_THREE
\ No newline at end of file
+#undef BLIND_VISION_THREE
diff --git a/code/modules/surgery/organs/stomach.dm b/code/modules/surgery/organs/stomach.dm
index d9cbf9be03..44b4f6362a 100755
--- a/code/modules/surgery/organs/stomach.dm
+++ b/code/modules/surgery/organs/stomach.dm
@@ -91,3 +91,36 @@
/obj/item/organ/stomach/ipc
name = "ipc stomach"
icon_state = "stomach-ipc"
+
+
+/obj/item/organ/stomach/ethereal
+ name = "biological battery"
+ icon_state = "stomach-p" //Welp. At least it's more unique in functionaliy.
+ desc = "A crystal-like organ that stores the electric charge of ethereals."
+ var/crystal_charge = ETHEREAL_CHARGE_FULL
+
+/obj/item/organ/stomach/ethereal/on_life()
+ ..()
+ adjust_charge(-ETHEREAL_CHARGE_FACTOR)
+
+/obj/item/organ/stomach/ethereal/Insert(mob/living/carbon/M, special = 0, drop_if_replaced = TRUE)
+ ..()
+ RegisterSignal(owner, COMSIG_PROCESS_BORGCHARGER_OCCUPANT, .proc/charge)
+ RegisterSignal(owner, COMSIG_LIVING_ELECTROCUTE_ACT, .proc/on_electrocute)
+
+/obj/item/organ/stomach/ethereal/Remove(mob/living/carbon/M, special = 0)
+ UnregisterSignal(owner, COMSIG_PROCESS_BORGCHARGER_OCCUPANT)
+ UnregisterSignal(owner, COMSIG_LIVING_ELECTROCUTE_ACT)
+ ..()
+
+/obj/item/organ/stomach/ethereal/proc/charge(datum/source, amount, repairs)
+ adjust_charge(amount / 70)
+
+/obj/item/organ/stomach/ethereal/proc/on_electrocute(datum/source, shock_damage, siemens_coeff = 1, flags = NONE)
+ if(flags & SHOCK_ILLUSION)
+ return
+ adjust_charge(shock_damage * siemens_coeff * 2)
+ to_chat(owner, "You absorb some of the shock into your body!")
+
+/obj/item/organ/stomach/ethereal/proc/adjust_charge(amount)
+ crystal_charge = clamp(crystal_charge + amount, ETHEREAL_CHARGE_NONE, ETHEREAL_CHARGE_DANGEROUS)
diff --git a/code/modules/surgery/organs/tails.dm b/code/modules/surgery/organs/tails.dm
index 55a656f42d..289e9df9d4 100644
--- a/code/modules/surgery/organs/tails.dm
+++ b/code/modules/surgery/organs/tails.dm
@@ -45,14 +45,20 @@
/obj/item/organ/tail/lizard/Insert(mob/living/carbon/human/H, special = 0, drop_if_replaced = TRUE)
..()
if(istype(H))
- // Checks here are necessary so it wouldn't overwrite the tail of a lizard it spawned in
+ // Checks here are necessary so it wouldn't overwrite the tail of a lizard it spawned in //yes, the if checks may cause snowflakes so that you can't insert another person's tail (haven't actually tested it but I assume that's the result of my addition) but it makes it so never again will lizards break their spine if set_species is called twice in a row (hopefully)
if(!H.dna.species.mutant_bodyparts["tail_lizard"])
- H.dna.features["tail_lizard"] = tail_type
- H.dna.species.mutant_bodyparts["tail_lizard"] = tail_type
+ if (!H.dna.features["tail_lizard"])
+ H.dna.features["tail_lizard"] = tail_type
+ H.dna.species.mutant_bodyparts["tail_lizard"] = tail_type
+ else
+ H.dna.species.mutant_bodyparts["tail_lizard"] = H.dna.features["tail_lizard"]
if(!H.dna.species.mutant_bodyparts["spines"])
- H.dna.features["spines"] = spines
- H.dna.species.mutant_bodyparts["spines"] = spines
+ if (!H.dna.features["spines"])
+ H.dna.features["spines"] = spines
+ H.dna.species.mutant_bodyparts["spines"] = spines
+ else
+ H.dna.species.mutant_bodyparts["spines"] = H.dna.features["spines"]
H.update_body()
/obj/item/organ/tail/lizard/Remove(special = FALSE)
diff --git a/code/modules/surgery/organs/tongue.dm b/code/modules/surgery/organs/tongue.dm
index 1c4a2d3043..929b42f96d 100644
--- a/code/modules/surgery/organs/tongue.dm
+++ b/code/modules/surgery/organs/tongue.dm
@@ -11,7 +11,8 @@
var/say_mod = null
var/taste_sensitivity = 15 // lower is more sensitive.
maxHealth = TONGUE_MAX_HEALTH
- var/modifies_speech = FALSE
+ var/list/initial_accents //the ones the tongue starts with, not what it currently has
+ var/list/accents = list() //done in order of priority (please always apply abductor accent and stuttering last)
var/static/list/languages_possible_base = typecacheof(list(
/datum/language/common,
/datum/language/draconic,
@@ -30,6 +31,8 @@
/obj/item/organ/tongue/Initialize(mapload)
. = ..()
+ for(var/accent in initial_accents)
+ accents += new accent
low_threshold_passed = "Your [name] feels a little sore."
low_threshold_cleared = "Your [name] soreness has subsided."
high_threshold_passed = "Your [name] is really starting to hurt."
@@ -38,12 +41,13 @@
now_fixed = "The excruciating pain of your [name] has subsided."
languages_possible = languages_possible_base
-/obj/item/organ/tongue/proc/handle_speech(datum/source, list/speech_args)
- return
+/obj/item/organ/tongue/proc/handle_speech(datum/source, list/speech_args) //this wont proc unless there's initial_accents on the tongue
+ for(var/datum/accent/speech_modifier in accents)
+ speech_args = speech_modifier.modify_speech(speech_args, source, owner)
/obj/item/organ/tongue/applyOrganDamage(d, maximum = maxHealth)
. = ..()
- if (damage >= maxHealth)
+ if(damage >= maxHealth)
to_chat(owner, "Your tongue is singed beyond recognition, and disintegrates!")
SSblackbox.record_feedback("tally", "fermi_chem", 1, "Tongues lost to Fermi")
qdel(src)
@@ -52,7 +56,7 @@
..()
if(say_mod && M.dna && M.dna.species)
M.dna.species.say_mod = say_mod
- if (modifies_speech)
+ if(length(initial_accents) || length(accents))
RegisterSignal(M, COMSIG_MOB_SAY, .proc/handle_speech)
M.UnregisterSignal(M, COMSIG_MOB_SAY)
@@ -74,16 +78,7 @@
say_mod = "hisses"
taste_sensitivity = 10 // combined nose + tongue, extra sensitive
maxHealth = 40 //extra sensitivity means tongue is more susceptible to damage
- modifies_speech = TRUE
-
-/obj/item/organ/tongue/lizard/handle_speech(datum/source, list/speech_args)
- var/static/regex/lizard_hiss = new("s+", "g")
- var/static/regex/lizard_hiSS = new("S+", "g")
- var/message = speech_args[SPEECH_MESSAGE]
- if(message[1] != "*")
- message = lizard_hiss.Replace(message, "sss")
- message = lizard_hiSS.Replace(message, "SSS")
- speech_args[SPEECH_MESSAGE] = message
+ initial_accents = list(/datum/accent/lizard)
/obj/item/organ/tongue/fly
name = "proboscis"
@@ -92,16 +87,7 @@
say_mod = "buzzes"
taste_sensitivity = 25 // you eat vomit, this is a mercy
maxHealth = 80 //years of eatting trash has made your tongue strong
- modifies_speech = TRUE
-
-/obj/item/organ/tongue/fly/handle_speech(datum/source, list/speech_args)
- var/static/regex/fly_buzz = new("z+", "g")
- var/static/regex/fly_buZZ = new("Z+", "g")
- var/message = speech_args[SPEECH_MESSAGE]
- if(message[1] != "*")
- message = fly_buzz.Replace(message, "zzz")
- message = fly_buZZ.Replace(message, "ZZZ")
- speech_args[SPEECH_MESSAGE] = message
+ initial_accents = list(/datum/accent/fly)
/obj/item/organ/tongue/abductor
name = "superlingual matrix"
@@ -110,7 +96,7 @@
say_mod = "gibbers"
taste_sensitivity = 101 // ayys cannot taste anything.
maxHealth = 120 //Ayys probe a lot
- modifies_speech = TRUE
+ initial_accents = list(/datum/accent/abductor)
var/mothership
/obj/item/organ/tongue/abductor/attack_self(mob/living/carbon/human/H)
@@ -138,26 +124,6 @@
else
. += "It is attuned to [mothership]."
-/obj/item/organ/tongue/abductor/handle_speech(datum/source, list/speech_args)
- //Hacks
- var/message = speech_args[SPEECH_MESSAGE]
- var/mob/living/carbon/human/user = source
- var/rendered = "[user.name]: [message]"
- user.log_talk(message, LOG_SAY, tag="abductor")
- for(var/mob/living/carbon/human/H in GLOB.alive_mob_list)
- var/obj/item/organ/tongue/T = H.getorganslot(ORGAN_SLOT_TONGUE)
- if(!T || T.type != type)
- continue
- if(H.dna && H.dna.species.id == "abductor" && user.dna && user.dna.species.id == "abductor")
- var/datum/antagonist/abductor/A = user.mind.has_antag_datum(/datum/antagonist/abductor)
- if(!A || !(H.mind in A.team.members))
- continue
- to_chat(H, rendered)
- for(var/mob/M in GLOB.dead_mob_list)
- var/link = FOLLOW_LINK(M, user)
- to_chat(M, "[link] [rendered]")
- speech_args[SPEECH_MESSAGE] = ""
-
/obj/item/organ/tongue/zombie
name = "rotting tongue"
desc = "Between the decay and the fact that it's just lying there you doubt a tongue has ever seemed less sexy."
@@ -165,23 +131,7 @@
say_mod = "moans"
taste_sensitivity = 32
maxHealth = 65 //Stop! It's already dead...!
- modifies_speech = TRUE
-
-/obj/item/organ/tongue/zombie/handle_speech(datum/source, list/speech_args)
- var/list/message_list = splittext(speech_args[SPEECH_MESSAGE], " ")
- var/maxchanges = max(round(message_list.len / 1.5), 2)
-
- for(var/i = rand(maxchanges / 2, maxchanges), i > 0, i--)
- var/insertpos = rand(1, message_list.len - 1)
- var/inserttext = message_list[insertpos]
-
- if(!(copytext(inserttext, -3) == "..."))//3 == length("...")
- message_list[insertpos] = inserttext + "..."
-
- if(prob(20) && message_list.len > 3)
- message_list.Insert(insertpos, "[pick("BRAINS", "Brains", "Braaaiinnnsss", "BRAAAIIINNSSS")]...")
-
- speech_args[SPEECH_MESSAGE] = jointext(message_list, " ")
+ initial_accents = list(/datum/accent/zombie)
/obj/item/organ/tongue/alien
name = "alien tongue"
@@ -190,7 +140,7 @@
say_mod = "hisses"
taste_sensitivity = 10 // LIZARDS ARE ALIENS CONFIRMED
maxHealth = 500 //They've a little mouth for a tongue, so it's pretty rhobust
- modifies_speech = TRUE // not really, they just hiss
+ initial_accents = list(/datum/accent/alien)
var/static/list/languages_possible_alien = typecacheof(list(
/datum/language/xenocommon,
/datum/language/common,
@@ -202,9 +152,6 @@
. = ..()
languages_possible = languages_possible_alien
-/obj/item/organ/tongue/alien/handle_speech(datum/source, list/speech_args)
- playsound(owner, "hiss", 25, 1, 1)
-
/obj/item/organ/tongue/bone
name = "bone \"tongue\""
desc = "Apparently skeletons alter the sounds they produce through oscillation of their teeth, hence their characteristic rattling."
@@ -214,14 +161,13 @@
attack_verb = list("bitten", "chattered", "chomped", "enamelled", "boned")
taste_sensitivity = 101 // skeletons cannot taste anything
maxHealth = 75 //Take brute damage instead
- modifies_speech = TRUE
var/chattering = FALSE
var/phomeme_type = "sans"
- var/list/phomeme_types = list("sans", "papyrus")
+ var/list/phomeme_types = list(/datum/accent/span/sans, /datum/accent/span/papyrus)
/obj/item/organ/tongue/bone/Initialize()
+ initial_accents += pick(phomeme_types)
. = ..()
- phomeme_type = pick(phomeme_types)
/obj/item/organ/tongue/bone/applyOrganDamage(var/d, var/maximum = maxHealth)
if(d < 0)
@@ -234,19 +180,14 @@
return
/obj/item/organ/tongue/bone/handle_speech(datum/source, list/speech_args)
- if (chattering)
+ if(chattering)
chatter(speech_args[SPEECH_MESSAGE], phomeme_type, source)
- switch(phomeme_type)
- if("sans")
- speech_args[SPEECH_SPANS] |= SPAN_SANS
- if("papyrus")
- speech_args[SPEECH_SPANS] |= SPAN_PAPYRUS
+ ..()
/obj/item/organ/tongue/bone/plasmaman
name = "plasma bone \"tongue\""
desc = "Like animated skeletons, Plasmamen vibrate their teeth in order to produce speech."
icon_state = "tongueplasma"
- modifies_speech = FALSE
/obj/item/organ/tongue/robot
name = "robotic voicebox"
@@ -256,7 +197,7 @@
icon_state = "tonguerobot"
say_mod = "states"
attack_verb = list("beeped", "booped")
- modifies_speech = TRUE
+ initial_accents = list(/datum/accent/span/robot)
taste_sensitivity = 25 // not as good as an organic tongue
maxHealth = 100 //RoboTongue!
var/electronics_magic = TRUE
@@ -264,34 +205,19 @@
/obj/item/organ/tongue/robot/could_speak_language(language)
return ..() || electronics_magic
-/obj/item/organ/tongue/robot/handle_speech(datum/source, list/speech_args)
- speech_args[SPEECH_SPANS] |= SPAN_ROBOT
-
/obj/item/organ/tongue/fluffy
name = "fluffy tongue"
desc = "OwO what's this?"
icon_state = "tonguefluffy"
taste_sensitivity = 10 // extra sensitive and inquisitive uwu
maxHealth = 35 //Sensitive tongue!
- modifies_speech = TRUE
-
-/obj/item/organ/tongue/fluffy/handle_speech(datum/source, list/speech_args)
- var/message = speech_args[SPEECH_MESSAGE]
- if(message[1] != "*")
- message = replacetext(message, "ne", "nye")
- message = replacetext(message, "nu", "nyu")
- message = replacetext(message, "na", "nya")
- message = replacetext(message, "no", "nyo")
- message = replacetext(message, "ove", "uv")
- message = replacetext(message, "l", "w")
- message = replacetext(message, "r", "w")
- message = lowertext(message)
- speech_args[SPEECH_MESSAGE] = message
+ initial_accents = list(/datum/accent/fluffy)
/obj/item/organ/tongue/cybernetic
name = "cybernetic tongue"
desc = "A state of the art robotic tongue that can detect the pH of anything drank."
icon_state = "tonguecybernetic"
+ initial_accents = list(/datum/accent/span/robot)
taste_sensitivity = 10
maxHealth = 60 //It's robotic!
organ_flags = ORGAN_SYNTHETIC
@@ -303,12 +229,32 @@
var/errormessage = list("Runtime in tongue.dm, line 39: Undefined operation \"zapzap ow my tongue\"", "afhsjifksahgjkaslfhashfjsak", "-1.#IND", "Graham's number", "inside you all along", "awaiting at least 1 approving review before merging this taste request")
owner.say("The pH is appropriately [pick(errormessage)].", forced = "EMPed synthetic tongue")
-/obj/item/organ/tongue/cybernetic/handle_speech(datum/source, list/speech_args)
- speech_args[SPEECH_SPANS] |= SPAN_ROBOT
-
/obj/item/organ/tongue/robot/ipc
name = "positronic voicebox"
say_mod = "beeps"
desc = "A voice synthesizer used by IPCs to smoothly interface with organic lifeforms."
electronics_magic = FALSE
organ_flags = ORGAN_SYNTHETIC
+
+/obj/item/organ/tongue/ethereal
+ name = "electric discharger"
+ desc = "A sophisticated ethereal organ, capable of synthesising speech via electrical discharge."
+ icon_state = "electrotongue"
+ say_mod = "crackles"
+ attack_verb = list("shocked", "jolted", "zapped")
+ taste_sensitivity = 101 // Not a tongue, they can't taste shit
+ var/static/list/languages_possible_ethereal = typecacheof(list(
+ /datum/language/common,
+ /datum/language/draconic,
+ /datum/language/codespeak,
+ /datum/language/monkey,
+ /datum/language/narsie,
+ /datum/language/beachbum,
+ /datum/language/aphasia,
+ /datum/language/sylvan,
+ /datum/language/voltaic
+ ))
+
+/obj/item/organ/tongue/ethereal/Initialize(mapload)
+ . = ..()
+ languages_possible = languages_possible_ethereal
diff --git a/code/modules/surgery/repair_puncture.dm b/code/modules/surgery/repair_puncture.dm
new file mode 100644
index 0000000000..12aefefc82
--- /dev/null
+++ b/code/modules/surgery/repair_puncture.dm
@@ -0,0 +1,108 @@
+
+/////BURN FIXING SURGERIES//////
+
+//the step numbers of each of these two, we only currently use the first to switch back and forth due to advancing after finishing steps anyway
+#define REALIGN_INNARDS 1
+#define WELD_VEINS 2
+
+///// Repair puncture wounds
+/datum/surgery/repair_puncture
+ name = "Repair puncture"
+ steps = list(/datum/surgery_step/incise, /datum/surgery_step/repair_innards, /datum/surgery_step/seal_veins, /datum/surgery_step/close) // repeat between steps 2 and 3 until healed
+ target_mobtypes = list(/mob/living/carbon)
+ possible_locs = list(BODY_ZONE_R_ARM,BODY_ZONE_L_ARM,BODY_ZONE_R_LEG,BODY_ZONE_L_LEG,BODY_ZONE_CHEST,BODY_ZONE_HEAD)
+ requires_real_bodypart = TRUE
+ targetable_wound = /datum/wound/pierce
+
+/datum/surgery/repair_puncture/can_start(mob/living/user, mob/living/carbon/target)
+ . = ..()
+ if(.)
+ var/obj/item/bodypart/targeted_bodypart = target.get_bodypart(user.zone_selected)
+ var/datum/wound/burn/pierce_wound = targeted_bodypart.get_wound_type(targetable_wound)
+ return(pierce_wound && pierce_wound.blood_flow > 0)
+
+//SURGERY STEPS
+
+///// realign the blood vessels so we can reweld them
+/datum/surgery_step/repair_innards
+ name = "realign blood vessels"
+ implements = list(TOOL_HEMOSTAT = 100, TOOL_SCALPEL = 85, TOOL_WIRECUTTER = 40)
+ time = 3 SECONDS
+
+/datum/surgery_step/repair_innards/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery)
+ var/datum/wound/pierce/pierce_wound = surgery.operated_wound
+ if(!pierce_wound)
+ user.visible_message("[user] looks for [target]'s [parse_zone(user.zone_selected)].", "You look for [target]'s [parse_zone(user.zone_selected)]...")
+ return
+
+ if(pierce_wound.blood_flow <= 0)
+ to_chat(user, "[target]'s [parse_zone(user.zone_selected)] has no puncture to repair!")
+ surgery.status++
+ return
+
+ display_results(user, target, "You begin to realign the torn blood vessels in [target]'s [parse_zone(user.zone_selected)]...",
+ "[user] begins to realign the torn blood vessels in [target]'s [parse_zone(user.zone_selected)] with [tool].",
+ "[user] begins to realign the torn blood vessels in [target]'s [parse_zone(user.zone_selected)].")
+
+/datum/surgery_step/repair_innards/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results = FALSE)
+ var/datum/wound/pierce/pierce_wound = surgery.operated_wound
+ if(!pierce_wound)
+ to_chat(user, "[target] has no puncture wound there!")
+ return ..()
+
+ display_results(user, target, "You successfully realign some of the blood vessels in [target]'s [parse_zone(target_zone)].",
+ "[user] successfully realigns some of the blood vessels in [target]'s [parse_zone(target_zone)] with [tool]!",
+ "[user] successfully realigns some of the blood vessels in [target]'s [parse_zone(target_zone)]!")
+ log_combat(user, target, "excised infected flesh in", addition="INTENT: [uppertext(user.a_intent)]")
+ surgery.operated_bodypart.receive_damage(brute=3, wound_bonus=CANT_WOUND)
+ pierce_wound.blood_flow -= 0.25
+ return ..()
+
+/datum/surgery_step/repair_innards/failure(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery, var/fail_prob = 0)
+ . = ..()
+ display_results(user, target, "You jerk apart some of the blood vessels in [target]'s [parse_zone(target_zone)].",
+ "[user] jerks apart some of the blood vessels in [target]'s [parse_zone(target_zone)] with [tool]!",
+ "[user] jerk apart some of the blood vessels in [target]'s [parse_zone(target_zone)]!")
+ surgery.operated_bodypart.receive_damage(brute=rand(4,8), sharpness=SHARP_EDGED, wound_bonus = 10)
+
+///// Sealing the vessels back together
+/datum/surgery_step/seal_veins
+ name = "weld veins" // if your doctor says they're going to weld your blood vessels back together, you're either A) on SS13, or B) in grave mortal peril
+ implements = list(TOOL_CAUTERY = 100, /obj/item/gun/energy/laser = 90, TOOL_WELDER = 70, /obj/item = 30)
+ time = 4 SECONDS
+
+/datum/surgery_step/seal_veins/tool_check(mob/user, obj/item/tool)
+ if(implement_type == TOOL_WELDER || implement_type == /obj/item)
+ return tool.get_temperature()
+
+ return TRUE
+
+/datum/surgery_step/seal_veins/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery)
+ var/datum/wound/pierce/pierce_wound = surgery.operated_wound
+ if(!pierce_wound)
+ user.visible_message("[user] looks for [target]'s [parse_zone(user.zone_selected)].", "You look for [target]'s [parse_zone(user.zone_selected)]...")
+ return
+ display_results(user, target, "You begin to meld some of the split blood vessels in [target]'s [parse_zone(user.zone_selected)]...",
+ "[user] begins to meld some of the split blood vessels in [target]'s [parse_zone(user.zone_selected)] with [tool].",
+ "[user] begins to meld some of the split blood vessels in [target]'s [parse_zone(user.zone_selected)].")
+
+/datum/surgery_step/seal_veins/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results = FALSE)
+ var/datum/wound/pierce/pierce_wound = surgery.operated_wound
+ if(!pierce_wound)
+ to_chat(user, "[target] has no puncture there!")
+ return ..()
+
+ display_results(user, target, "You successfully meld some of the split blood vessels in [target]'s [parse_zone(target_zone)] with [tool].",
+ "[user] successfully melds some of the split blood vessels in [target]'s [parse_zone(target_zone)] with [tool]!",
+ "[user] successfully melds some of the split blood vessels in [target]'s [parse_zone(target_zone)]!")
+ log_combat(user, target, "dressed burns in", addition="INTENT: [uppertext(user.a_intent)]")
+ pierce_wound.blood_flow -= 0.5
+ if(pierce_wound.blood_flow > 0)
+ surgery.status = REALIGN_INNARDS
+ to_chat(user, "There still seems to be misaligned blood vessels to finish...")
+ else
+ to_chat(user, "You've repaired all the internal damage in [target]'s [parse_zone(target_zone)]!")
+ return ..()
+
+#undef REALIGN_INNARDS
+#undef WELD_VEINS
diff --git a/code/modules/surgery/tools.dm b/code/modules/surgery/tools.dm
index 150016f570..0f1004221c 100644
--- a/code/modules/surgery/tools.dm
+++ b/code/modules/surgery/tools.dm
@@ -10,6 +10,12 @@
tool_behaviour = TOOL_RETRACTOR
toolspeed = 1
+/obj/item/retractor/attack(mob/living/L, mob/user)
+ if(user.a_intent == INTENT_HELP)
+ to_chat(user, "You refrain from hitting [L] with [src], as you are in help intent.")
+ return
+ return ..()
+
/obj/item/retractor/advanced
name = "mechanical pinches"
desc = "An agglomerate of rods and gears."
@@ -55,6 +61,12 @@
tool_behaviour = TOOL_HEMOSTAT
toolspeed = 1
+/obj/item/hemostat/attack(mob/living/L, mob/user)
+ if(user.a_intent == INTENT_HELP)
+ to_chat(user, "You refrain from hitting [L] with [src], as you are in help intent.")
+ return
+ return ..()
+
/obj/item/hemostat/augment
name = "hemostat"
desc = "Tiny servos power a pair of pincers to stop bleeding."
@@ -79,6 +91,12 @@
tool_behaviour = TOOL_CAUTERY
toolspeed = 1
+/obj/item/cautery/attack(mob/living/L, mob/user)
+ if(user.a_intent == INTENT_HELP)
+ to_chat(user, "You refrain from hitting [L] with [src], as you are in help intent.")
+ return
+ return ..()
+
/obj/item/cautery/augment
name = "cautery"
desc = "A heated element that cauterizes wounds."
@@ -107,6 +125,12 @@
tool_behaviour = TOOL_DRILL
toolspeed = 1
+/obj/item/surgicaldrill/attack(mob/living/L, mob/user)
+ if(user.a_intent == INTENT_HELP)
+ to_chat(user, "You refrain from hitting [L] with [src], as you are in help intent.")
+ return
+ return ..()
+
/obj/item/surgicaldrill/advanced
name = "searing tool"
desc = "It projects a high power laser used for medical application."
@@ -163,7 +187,7 @@
item_flags = SURGICAL_TOOL
attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut")
hitsound = 'sound/weapons/bladeslice.ogg'
- sharpness = IS_SHARP_ACCURATE
+ sharpness = SHARP_POINTY
tool_behaviour = TOOL_SCALPEL
toolspeed = 1
bare_wound_bonus = 20
@@ -172,6 +196,12 @@
. = ..()
AddComponent(/datum/component/butchering, 80 * toolspeed, 100, 0)
+/obj/item/scalpel/attack(mob/living/L, mob/user)
+ if(user.a_intent == INTENT_HELP)
+ to_chat(user, "You refrain from hitting [L] with [src], as you are in help intent.")
+ return
+ return ..()
+
/obj/item/scalpel/advanced
name = "laser scalpel"
desc = "An advanced scalpel which uses laser technology to cut."
@@ -181,7 +211,7 @@
force = 16
toolspeed = 0.7
light_color = LIGHT_COLOR_GREEN
- sharpness = IS_SHARP_ACCURATE
+ sharpness = SHARP_POINTY
/obj/item/scalpel/advanced/Initialize()
. = ..()
@@ -221,7 +251,7 @@
attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut")
toolspeed = 0.5
hitsound = 'sound/weapons/bladeslice.ogg'
- sharpness = IS_SHARP_ACCURATE
+ sharpness = SHARP_POINTY
/obj/item/scalpel/suicide_act(mob/user)
user.visible_message("[user] is slitting [user.p_their()] [pick("wrists", "throat", "stomach")] with [src]! It looks like [user.p_theyre()] trying to commit suicide!")
@@ -245,16 +275,21 @@
throw_range = 5
custom_materials = list(/datum/material/iron=10000, /datum/material/glass=6000)
attack_verb = list("attacked", "slashed", "sawed", "cut")
- sharpness = IS_SHARP
+ sharpness = SHARP_EDGED
tool_behaviour = TOOL_SAW
toolspeed = 1
- wound_bonus = 10
- bare_wound_bonus = 15
+ wound_bonus = 5
+ bare_wound_bonus = 10
/obj/item/circular_saw/Initialize()
. = ..()
AddComponent(/datum/component/butchering, 40 * toolspeed, 100, 5, 'sound/weapons/circsawhit.ogg') //saws are very accurate and fast at butchering
+/obj/item/circular_saw/attack(mob/living/L, mob/user)
+ if(user.a_intent == INTENT_HELP)
+ to_chat(user, "You refrain from hitting [L] with [src], as you are in help intent.")
+ return
+ return ..()
/obj/item/circular_saw/augment
name = "circular saw"
@@ -272,7 +307,7 @@
custom_materials = list(/datum/material/iron=10000, /datum/material/glass=6000)
toolspeed = 0.5
attack_verb = list("attacked", "slashed", "sawed", "cut")
- sharpness = IS_SHARP
+ sharpness = SHARP_EDGED
/obj/item/surgical_drapes
name = "surgical drapes"
@@ -392,3 +427,9 @@
attack_verb = list("corrected", "properly set")
tool_behaviour = TOOL_BONESET
toolspeed = 1
+
+/obj/item/bonesetter/attack(mob/living/L, mob/user)
+ if(user.a_intent == INTENT_HELP)
+ to_chat(user, "You refrain from hitting [L] with [src], as you are in help intent.")
+ return
+ return ..()
diff --git a/code/modules/tgui/external.dm b/code/modules/tgui/external.dm
index 5de54c439c..c4515b8a76 100644
--- a/code/modules/tgui/external.dm
+++ b/code/modules/tgui/external.dm
@@ -1,7 +1,8 @@
/**
- * tgui external
+ * External tgui definitions, such as src_object APIs.
*
- * Contains all external tgui declarations.
+ * Copyright (c) 2020 Aleksej Komarov
+ * SPDX-License-Identifier: MIT
*/
/**
@@ -11,13 +12,9 @@
* If this proc is not implemented properly, the UI will not update correctly.
*
* required user mob The mob who opened/is using the UI.
- * optional ui_key string The ui_key of the UI.
* optional ui datum/tgui The UI to be updated, if it exists.
- * optional force_open bool If the UI should be re-opened instead of updated.
- * optional master_ui datum/tgui The parent UI.
- * optional state datum/ui_state The state used to determine status.
*/
-/datum/proc/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state)
+/datum/proc/ui_interact(mob/user, datum/tgui/ui)
return FALSE // Not implemented.
/**
@@ -37,10 +34,11 @@
* public
*
* Static Data to be sent to the UI.
- * Static data differs from normal data in that it's large data that should be sent infrequently
- * This is implemented optionally for heavy uis that would be sending a lot of redundant data
- * frequently.
- * Gets squished into one object on the frontend side, but the static part is cached.
+ *
+ * Static data differs from normal data in that it's large data that should be
+ * sent infrequently. This is implemented optionally for heavy uis that would
+ * be sending a lot of redundant data frequently. Gets squished into one
+ * object on the frontend side, but the static part is cached.
*
* required user mob The mob interacting with the UI.
*
@@ -52,18 +50,17 @@
/**
* public
*
- * Forces an update on static data. Should be done manually whenever something happens to change static data.
+ * Forces an update on static data. Should be done manually whenever something
+ * happens to change static data.
*
* required user the mob currently interacting with the ui
* optional ui ui to be updated
- * optional ui_key ui key of ui to be updated
*/
-/datum/proc/update_static_data(mob/user, datum/tgui/ui, ui_key = "main")
- ui = SStgui.try_update_ui(user, src, ui_key, ui)
- // If there was no ui to update, there's no static data to update either.
+/datum/proc/update_static_data(mob/user, datum/tgui/ui)
if(!ui)
- return
- ui.push_data(null, ui_static_data(), TRUE)
+ ui = SStgui.get_open_ui(user, src)
+ if(ui)
+ ui.send_full_update()
/**
* public
@@ -85,17 +82,12 @@
* public
*
* Called on an object when a tgui object is being created, allowing you to
- * customise the html
- * For example: inserting a custom stylesheet that you need in the head
+ * push various assets to tgui, for examples spritesheets.
*
- * For this purpose, some tags are available in the html, to be parsed out
- ^ with replacetext
- * (customheadhtml) - Additions to the head tag
- *
- * required html the html base text
+ * return list List of asset datums or file paths.
*/
-/datum/proc/ui_base_html(html)
- return html
+/datum/proc/ui_assets(mob/user)
+ return list()
/**
* private
@@ -107,6 +99,15 @@
/datum/proc/ui_host(mob/user)
return src // Default src.
+/**
+ * private
+ *
+ * The UI's state controller to be used for created uis
+ * This is a proc over a var for memory reasons
+ */
+/datum/proc/ui_state(mob/user)
+ return GLOB.default_state
+
/**
* global
*
@@ -118,9 +119,24 @@
/**
* global
*
- * Used to track UIs for a mob.
+ * Tracks open UIs for a user.
*/
-/mob/var/list/open_uis = list()
+/mob/var/list/tgui_open_uis = list()
+
+/**
+ * global
+ *
+ * Tracks open windows for a user.
+ */
+/client/var/list/tgui_windows = list()
+
+/**
+ * global
+ *
+ * TRUE if cache was reloaded by tgui dev server at least once.
+ */
+/client/var/tgui_cache_reloaded = FALSE
+
/**
* public
*
@@ -137,17 +153,56 @@
*
* required uiref ref The UI that was closed.
*/
-/client/verb/uiclose(ref as text)
+/client/verb/uiclose(window_id as text)
// Name the verb, and hide it from the user panel.
set name = "uiclose"
- set hidden = 1
+ set hidden = TRUE
+ var/mob/user = src && src.mob
+ if(!user)
+ return
+ // Close all tgui datums based on window_id.
+ SStgui.force_close_window(user, window_id)
- // Get the UI based on the ref.
- var/datum/tgui/ui = locate(ref)
-
- // If we found the UI, close it.
- if(istype(ui))
- ui.close()
- // Unset machine just to be sure.
- if(src && src.mob)
- src.mob.unset_machine()
+/**
+ * Middleware for /client/Topic.
+ *
+ * return bool If TRUE, prevents propagation of the topic call.
+ */
+/proc/tgui_Topic(href_list)
+ // Skip non-tgui topics
+ if(!href_list["tgui"])
+ return FALSE
+ var/type = href_list["type"]
+ // Unconditionally collect tgui logs
+ if(type == "log")
+ log_tgui(usr, href_list["message"])
+ // Reload all tgui windows
+ if(type == "cacheReloaded")
+ if(!check_rights(R_ADMIN) || usr.client.tgui_cache_reloaded)
+ return TRUE
+ // Mark as reloaded
+ usr.client.tgui_cache_reloaded = TRUE
+ // Notify windows
+ var/list/windows = usr.client.tgui_windows
+ for(var/window_id in windows)
+ var/datum/tgui_window/window = windows[window_id]
+ if (window.status == TGUI_WINDOW_READY)
+ window.on_message(type, null, href_list)
+ return TRUE
+ // Locate window
+ var/window_id = href_list["window_id"]
+ var/datum/tgui_window/window
+ if(window_id)
+ window = usr.client.tgui_windows[window_id]
+ if(!window)
+ log_tgui(usr, "Error: Couldn't find the window datum, force closing.")
+ SStgui.force_close_window(usr, window_id)
+ return TRUE
+ // Decode payload
+ var/payload
+ if(href_list["payload"])
+ payload = json_decode(href_list["payload"])
+ // Pass message to window
+ if(window)
+ window.on_message(type, payload, href_list)
+ return TRUE
diff --git a/code/modules/tgui/states.dm b/code/modules/tgui/states.dm
index e626b98815..fa88cc1338 100644
--- a/code/modules/tgui/states.dm
+++ b/code/modules/tgui/states.dm
@@ -1,7 +1,9 @@
/**
- * tgui states
+ * Base state and helpers for states. Just does some sanity checks,
+ * implement a proper state for in-depth checks.
*
- * Base state and helpers for states. Just does some sanity checks, implement a state for in-depth checks.
+ * Copyright (c) 2020 Aleksej Komarov
+ * SPDX-License-Identifier: MIT
*/
/**
@@ -26,9 +28,10 @@
. = max(., UI_INTERACTIVE)
// Regular ghosts can always at least view if in range.
- var/clientviewlist = getviewsize(user.client.view)
- if(get_dist(src_object, user) < max(clientviewlist[1],clientviewlist[2]))
- . = max(., UI_UPDATE)
+ if(user.client)
+ var/clientviewlist = getviewsize(user.client.view)
+ if(get_dist(src_object, user) < max(clientviewlist[1], clientviewlist[2]))
+ . = max(., UI_UPDATE)
// Check if the state allows interaction
var/result = state.can_use_topic(src_object, user)
@@ -46,7 +49,8 @@
* return UI_state The state of the UI.
*/
/datum/ui_state/proc/can_use_topic(src_object, mob/user)
- return UI_CLOSE // Don't allow interaction by default.
+ // Don't allow interaction by default.
+ return UI_CLOSE
/**
* public
@@ -56,21 +60,31 @@
* return UI_state The state of the UI.
*/
/mob/proc/shared_ui_interaction(src_object)
- if(!client) // Close UIs if mindless.
+ // Close UIs if mindless.
+ if(!client)
return UI_CLOSE
- else if(stat) // Disable UIs if unconcious.
+ // Disable UIs if unconcious.
+ else if(stat)
return UI_DISABLED
- else if(incapacitated() || lying) // Update UIs if incapicitated but concious.
+ // Update UIs if incapicitated but concious.
+ else if(incapacitated())
return UI_UPDATE
return UI_INTERACTIVE
+/mob/living/shared_ui_interaction(src_object)
+ . = ..()
+ if(!(mobility_flags & MOBILITY_UI) && . == UI_INTERACTIVE)
+ return UI_UPDATE
+
/mob/living/silicon/ai/shared_ui_interaction(src_object)
- if(lacks_power()) // Disable UIs if the AI is unpowered.
+ // Disable UIs if the AI is unpowered.
+ if(lacks_power())
return UI_DISABLED
return ..()
/mob/living/silicon/robot/shared_ui_interaction(src_object)
- if(!cell || cell.charge <= 0 || locked_down) // Disable UIs if the Borg is unpowered or locked.
+ // Disable UIs if the Borg is unpowered or locked.
+ if(!cell || cell.charge <= 0 || locked_down)
return UI_DISABLED
return ..()
@@ -87,7 +101,8 @@
* return UI_state The state of the UI.
*/
/atom/proc/contents_ui_distance(src_object, mob/living/user)
- return user.shared_living_ui_distance(src_object) // Just call this mob's check.
+ // Just call this mob's check.
+ return user.shared_living_ui_distance(src_object)
/**
* public
@@ -99,17 +114,21 @@
* return UI_state The state of the UI.
*/
/mob/living/proc/shared_living_ui_distance(atom/movable/src_object, viewcheck = TRUE)
- if(viewcheck && !(src_object in view(src))) // If the object is obscured, close it.
+ // If the object is obscured, close it.
+ if(viewcheck && !(src_object in view(src)))
return UI_CLOSE
-
var/dist = get_dist(src_object, src)
- if(dist <= 1 || src_object.hasSiliconAccessInArea(src)) // Open and interact if 1-0 tiles away.
+ // Open and interact if 1-0 tiles away.
+ if(dist <= 1)
return UI_INTERACTIVE
- else if(dist <= 2) // View only if 2-3 tiles away.
+ // View only if 2-3 tiles away.
+ else if(dist <= 2)
return UI_UPDATE
- else if(dist <= 5) // Disable if 5 tiles away.
+ // Disable if 5 tiles away.
+ else if(dist <= 5)
return UI_DISABLED
- return UI_CLOSE // Otherwise, we got nothing.
+ // Otherwise, we got nothing.
+ return UI_CLOSE
/mob/living/carbon/human/shared_living_ui_distance(atom/movable/src_object, viewcheck = TRUE)
if(dna.check_mutation(TK) && tkMaxRangeCheck(src, src_object))
diff --git a/code/modules/tgui/states/admin.dm b/code/modules/tgui/states/admin.dm
index 61fc373118..227a294078 100644
--- a/code/modules/tgui/states/admin.dm
+++ b/code/modules/tgui/states/admin.dm
@@ -2,6 +2,9 @@
* tgui state: admin_state
*
* Checks that the user is an admin, end-of-story.
+ *
+ * Copyright (c) 2020 Aleksej Komarov
+ * SPDX-License-Identifier: MIT
*/
GLOBAL_DATUM_INIT(admin_state, /datum/ui_state/admin_state, new)
diff --git a/code/modules/tgui/states/always.dm b/code/modules/tgui/states/always.dm
index a741e2e3d4..210f0896a2 100644
--- a/code/modules/tgui/states/always.dm
+++ b/code/modules/tgui/states/always.dm
@@ -2,6 +2,9 @@
* tgui state: always_state
*
* Always grants the user UI_INTERACTIVE. Period.
+ *
+ * Copyright (c) 2020 Aleksej Komarov
+ * SPDX-License-Identifier: MIT
*/
GLOBAL_DATUM_INIT(always_state, /datum/ui_state/always_state, new)
diff --git a/code/modules/tgui/states/conscious.dm b/code/modules/tgui/states/conscious.dm
index 4e2793d130..670ca7c07e 100644
--- a/code/modules/tgui/states/conscious.dm
+++ b/code/modules/tgui/states/conscious.dm
@@ -2,6 +2,9 @@
* tgui state: conscious_state
*
* Only checks if the user is conscious.
+ *
+ * Copyright (c) 2020 Aleksej Komarov
+ * SPDX-License-Identifier: MIT
*/
GLOBAL_DATUM_INIT(conscious_state, /datum/ui_state/conscious_state, new)
diff --git a/code/modules/tgui/states/contained.dm b/code/modules/tgui/states/contained.dm
index f02424d01e..1eb8edba25 100644
--- a/code/modules/tgui/states/contained.dm
+++ b/code/modules/tgui/states/contained.dm
@@ -2,6 +2,9 @@
* tgui state: contained_state
*
* Checks that the user is inside the src_object.
+ *
+ * Copyright (c) 2020 Aleksej Komarov
+ * SPDX-License-Identifier: MIT
*/
GLOBAL_DATUM_INIT(contained_state, /datum/ui_state/contained_state, new)
diff --git a/code/modules/tgui/states/debug.dm b/code/modules/tgui/states/debug.dm
new file mode 100644
index 0000000000..6c600b38ce
--- /dev/null
+++ b/code/modules/tgui/states/debug.dm
@@ -0,0 +1,6 @@
+GLOBAL_DATUM_INIT(debug_state, /datum/ui_state/debug_state, new)
+
+/datum/ui_state/debug_state/can_use_topic(src_object, mob/user)
+ if(check_rights_for(user.client, R_DEBUG))
+ return UI_INTERACTIVE
+ return UI_CLOSE
diff --git a/code/modules/tgui/states/deep_inventory.dm b/code/modules/tgui/states/deep_inventory.dm
index 43758cbab1..a2b9276a59 100644
--- a/code/modules/tgui/states/deep_inventory.dm
+++ b/code/modules/tgui/states/deep_inventory.dm
@@ -1,7 +1,11 @@
/**
* tgui state: deep_inventory_state
*
- * Checks that the src_object is in the user's deep (backpack, box, toolbox, etc) inventory.
+ * Checks that the src_object is in the user's deep
+ * (backpack, box, toolbox, etc) inventory.
+ *
+ * Copyright (c) 2020 Aleksej Komarov
+ * SPDX-License-Identifier: MIT
*/
GLOBAL_DATUM_INIT(deep_inventory_state, /datum/ui_state/deep_inventory_state, new)
diff --git a/code/modules/tgui/states/default.dm b/code/modules/tgui/states/default.dm
index 6bb159640e..367e57beff 100644
--- a/code/modules/tgui/states/default.dm
+++ b/code/modules/tgui/states/default.dm
@@ -1,7 +1,11 @@
/**
* tgui state: default_state
*
- * Checks a number of things -- mostly physical distance for humans and view for robots.
+ * Checks a number of things -- mostly physical distance for humans
+ * and view for robots.
+ *
+ * Copyright (c) 2020 Aleksej Komarov
+ * SPDX-License-Identifier: MIT
*/
GLOBAL_DATUM_INIT(default_state, /datum/ui_state/default, new)
diff --git a/code/modules/tgui/states/default_contained.dm b/code/modules/tgui/states/default_contained.dm
deleted file mode 100644
index c532e9f5d1..0000000000
--- a/code/modules/tgui/states/default_contained.dm
+++ /dev/null
@@ -1,13 +0,0 @@
-/**
- * tgui state: default_contained
- *
- * Basically default and contained combined, allowing for both
- */
-
-GLOBAL_DATUM_INIT(default_contained_state, /datum/ui_state/default/contained, new)
-
-/datum/ui_state/default/contained/can_use_topic(atom/src_object, mob/user)
- if(src_object.contains(user))
- return UI_INTERACTIVE
- return ..()
-
\ No newline at end of file
diff --git a/code/modules/tgui/states/hands.dm b/code/modules/tgui/states/hands.dm
index d73d1058ea..1c885ed414 100644
--- a/code/modules/tgui/states/hands.dm
+++ b/code/modules/tgui/states/hands.dm
@@ -2,6 +2,9 @@
* tgui state: hands_state
*
* Checks that the src_object is in the user's hands.
+ *
+ * Copyright (c) 2020 Aleksej Komarov
+ * SPDX-License-Identifier: MIT
*/
GLOBAL_DATUM_INIT(hands_state, /datum/ui_state/hands_state, new)
@@ -19,7 +22,7 @@ GLOBAL_DATUM_INIT(hands_state, /datum/ui_state/hands_state, new)
return UI_INTERACTIVE
return UI_CLOSE
-/mob/living/silicon/robot/hands_can_use_topic(obj/src_object)
- if(activated(src_object) || istype(src_object.loc, /obj/item/weapon/gripper))
+/mob/living/silicon/robot/hands_can_use_topic(src_object)
+ if(activated(src_object))
return UI_INTERACTIVE
return UI_CLOSE
diff --git a/code/modules/tgui/states/human_adjacent.dm b/code/modules/tgui/states/human_adjacent.dm
index 7aefe43e44..2ac7c8637b 100644
--- a/code/modules/tgui/states/human_adjacent.dm
+++ b/code/modules/tgui/states/human_adjacent.dm
@@ -3,6 +3,9 @@
*
* In addition to default checks, only allows interaction for a
* human adjacent user.
+ *
+ * Copyright (c) 2020 Aleksej Komarov
+ * SPDX-License-Identifier: MIT
*/
GLOBAL_DATUM_INIT(human_adjacent_state, /datum/ui_state/human_adjacent_state, new)
diff --git a/code/modules/tgui/states/inventory.dm b/code/modules/tgui/states/inventory.dm
index 43fe2cb451..dc5dd0d57e 100644
--- a/code/modules/tgui/states/inventory.dm
+++ b/code/modules/tgui/states/inventory.dm
@@ -1,7 +1,11 @@
/**
* tgui state: inventory_state
*
- * Checks that the src_object is in the user's top-level (hand, ear, pocket, belt, etc) inventory.
+ * Checks that the src_object is in the user's top-level
+ * (hand, ear, pocket, belt, etc) inventory.
+ *
+ * Copyright (c) 2020 Aleksej Komarov
+ * SPDX-License-Identifier: MIT
*/
GLOBAL_DATUM_INIT(inventory_state, /datum/ui_state/inventory_state, new)
diff --git a/code/modules/tgui/states/language_menu.dm b/code/modules/tgui/states/language_menu.dm
index 5c816c8922..6389b05cd5 100644
--- a/code/modules/tgui/states/language_menu.dm
+++ b/code/modules/tgui/states/language_menu.dm
@@ -1,5 +1,8 @@
/**
* tgui state: language_menu_state
+ *
+ * Copyright (c) 2020 Aleksej Komarov
+ * SPDX-License-Identifier: MIT
*/
GLOBAL_DATUM_INIT(language_menu_state, /datum/ui_state/language_menu, new)
diff --git a/code/modules/tgui/states/not_incapacitated.dm b/code/modules/tgui/states/not_incapacitated.dm
index 364b59424d..16dcb7881e 100644
--- a/code/modules/tgui/states/not_incapacitated.dm
+++ b/code/modules/tgui/states/not_incapacitated.dm
@@ -2,6 +2,9 @@
* tgui state: not_incapacitated_state
*
* Checks that the user isn't incapacitated
+ *
+ * Copyright (c) 2020 Aleksej Komarov
+ * SPDX-License-Identifier: MIT
*/
GLOBAL_DATUM_INIT(not_incapacitated_state, /datum/ui_state/not_incapacitated_state, new)
@@ -24,6 +27,10 @@ GLOBAL_DATUM_INIT(not_incapacitated_turf_state, /datum/ui_state/not_incapacitate
/datum/ui_state/not_incapacitated_state/can_use_topic(src_object, mob/user)
if(user.stat)
return UI_CLOSE
- if(user.incapacitated() || user.lying || (turf_check && !isturf(user.loc)))
+ if(user.incapacitated() || (turf_check && !isturf(user.loc)))
return UI_DISABLED
- return UI_INTERACTIVE
\ No newline at end of file
+ if(isliving(user))
+ var/mob/living/L = user
+ if(!(L.mobility_flags & MOBILITY_STAND))
+ return UI_DISABLED
+ return UI_INTERACTIVE
diff --git a/code/modules/tgui/states/notcontained.dm b/code/modules/tgui/states/notcontained.dm
index 642c6ce95f..1d4e6aec19 100644
--- a/code/modules/tgui/states/notcontained.dm
+++ b/code/modules/tgui/states/notcontained.dm
@@ -1,7 +1,11 @@
/**
* tgui state: notcontained_state
*
- * Checks that the user is not inside src_object, and then makes the default checks.
+ * Checks that the user is not inside src_object, and then makes the
+ * default checks.
+ *
+ * Copyright (c) 2020 Aleksej Komarov
+ * SPDX-License-Identifier: MIT
*/
GLOBAL_DATUM_INIT(notcontained_state, /datum/ui_state/notcontained_state, new)
diff --git a/code/modules/tgui/states/observer.dm b/code/modules/tgui/states/observer.dm
index 86ad776b13..d105de1c0c 100644
--- a/code/modules/tgui/states/observer.dm
+++ b/code/modules/tgui/states/observer.dm
@@ -2,6 +2,9 @@
* tgui state: observer_state
*
* Checks that the user is an observer/ghost.
+ *
+ * Copyright (c) 2020 Aleksej Komarov
+ * SPDX-License-Identifier: MIT
*/
GLOBAL_DATUM_INIT(observer_state, /datum/ui_state/observer_state, new)
diff --git a/code/modules/tgui/states/physical.dm b/code/modules/tgui/states/physical.dm
index 88c8a291aa..3073039d14 100644
--- a/code/modules/tgui/states/physical.dm
+++ b/code/modules/tgui/states/physical.dm
@@ -2,6 +2,9 @@
* tgui state: physical_state
*
* Short-circuits the default state to only check physical distance.
+ *
+ * Copyright (c) 2020 Aleksej Komarov
+ * SPDX-License-Identifier: MIT
*/
GLOBAL_DATUM_INIT(physical_state, /datum/ui_state/physical, new)
diff --git a/code/modules/tgui/states/self.dm b/code/modules/tgui/states/self.dm
index b0c9500fbc..4b6e3b9fd9 100644
--- a/code/modules/tgui/states/self.dm
+++ b/code/modules/tgui/states/self.dm
@@ -2,6 +2,9 @@
* tgui state: self_state
*
* Only checks that the user and src_object are the same.
+ *
+ * Copyright (c) 2020 Aleksej Komarov
+ * SPDX-License-Identifier: MIT
*/
GLOBAL_DATUM_INIT(self_state, /datum/ui_state/self_state, new)
diff --git a/code/modules/tgui/states/zlevel.dm b/code/modules/tgui/states/zlevel.dm
index 5e3ccfb7de..64ea2fa1c0 100644
--- a/code/modules/tgui/states/zlevel.dm
+++ b/code/modules/tgui/states/zlevel.dm
@@ -2,6 +2,9 @@
* tgui state: z_state
*
* Only checks that the Z-level of the user and src_object are the same.
+ *
+ * Copyright (c) 2020 Aleksej Komarov
+ * SPDX-License-Identifier: MIT
*/
GLOBAL_DATUM_INIT(z_state, /datum/ui_state/z_state, new)
diff --git a/code/modules/tgui/subsystem.dm b/code/modules/tgui/subsystem.dm
deleted file mode 100644
index cbe94e2d7f..0000000000
--- a/code/modules/tgui/subsystem.dm
+++ /dev/null
@@ -1,247 +0,0 @@
-/**
- * tgui subsystem
- *
- * Contains all tgui state and subsystem code.
- */
-
-/**
- * public
- *
- * Get a open UI given a user, src_object, and ui_key and try to update it with data.
- *
- * required user mob The mob who opened/is using the UI.
- * required src_object datum The object/datum which owns the UI.
- * required ui_key string The ui_key of the UI.
- * optional ui datum/tgui The UI to be updated, if it exists.
- * optional force_open bool If the UI should be re-opened instead of updated.
- *
- * return datum/tgui The found UI.
- */
-/datum/controller/subsystem/tgui/proc/try_update_ui(mob/user, datum/src_object, ui_key, datum/tgui/ui, force_open = FALSE)
- if(isnull(ui)) // No UI was passed, so look for one.
- ui = get_open_ui(user, src_object, ui_key)
-
- if(!isnull(ui))
- var/data = src_object.ui_data(user) // Get data from the src_object.
- if(!force_open) // UI is already open; update it.
- ui.push_data(data)
- else // Re-open it anyways.
- ui.reinitialize(null, data)
- return ui // We found the UI, return it.
- else
- return null // We couldn't find a UI.
-
-/**
- * private
- *
- * Get a open UI given a user, src_object, and ui_key.
- *
- * required user mob The mob who opened/is using the UI.
- * required src_object datum The object/datum which owns the UI.
- * required ui_key string The ui_key of the UI.
- *
- * return datum/tgui The found UI.
- */
-/datum/controller/subsystem/tgui/proc/get_open_ui(mob/user, datum/src_object, ui_key)
- var/src_object_key = "[REF(src_object)]"
- if(isnull(open_uis[src_object_key]) || !istype(open_uis[src_object_key], /list))
- return null // No UIs open.
- else if(isnull(open_uis[src_object_key][ui_key]) || !istype(open_uis[src_object_key][ui_key], /list))
- return null // No UIs open for this object.
-
- for(var/datum/tgui/ui in open_uis[src_object_key][ui_key]) // Find UIs for this object.
- if(ui.user == user) // Make sure we have the right user
- return ui
-
- return null // Couldn't find a UI!
-
-/**
- * private
- *
- * Update all UIs attached to src_object.
- *
- * required src_object datum The object/datum which owns the UIs.
- *
- * return int The number of UIs updated.
- */
-/datum/controller/subsystem/tgui/proc/update_uis(datum/src_object)
- var/src_object_key = "[REF(src_object)]"
- if(isnull(open_uis[src_object_key]) || !istype(open_uis[src_object_key], /list))
- return 0 // Couldn't find any UIs for this object.
-
- var/update_count = 0
- for(var/ui_key in open_uis[src_object_key])
- for(var/datum/tgui/ui in open_uis[src_object_key][ui_key])
- if(ui && ui.src_object && ui.user && ui.src_object.ui_host(ui.user)) // Check the UI is valid.
- ui.process(force = 1) // Update the UI.
- update_count++ // Count each UI we update.
- return update_count
-
-/**
- * private
- *
- * Close all UIs attached to src_object.
- *
- * required src_object datum The object/datum which owns the UIs.
- *
- * return int The number of UIs closed.
- */
-/datum/controller/subsystem/tgui/proc/close_uis(datum/src_object)
- var/src_object_key = "[REF(src_object)]"
- if(isnull(open_uis[src_object_key]) || !istype(open_uis[src_object_key], /list))
- return 0 // Couldn't find any UIs for this object.
-
- var/close_count = 0
- for(var/ui_key in open_uis[src_object_key])
- for(var/datum/tgui/ui in open_uis[src_object_key][ui_key])
- if(ui && ui.src_object && ui.user && ui.src_object.ui_host(ui.user)) // Check the UI is valid.
- ui.close() // Close the UI.
- close_count++ // Count each UI we close.
- return close_count
-
-/**
- * private
- *
- * Close *ALL* UIs
- *
- * return int The number of UIs closed.
- */
-/datum/controller/subsystem/tgui/proc/close_all_uis()
- var/close_count = 0
- for(var/src_object_key in open_uis)
- for(var/ui_key in open_uis[src_object_key])
- for(var/datum/tgui/ui in open_uis[src_object_key][ui_key])
- if(ui && ui.src_object && ui.user && ui.src_object.ui_host(ui.user)) // Check the UI is valid.
- ui.close() // Close the UI.
- close_count++ // Count each UI we close.
- return close_count
-
-/**
- * private
- *
- * Update all UIs belonging to a user.
- *
- * required user mob The mob who opened/is using the UI.
- * optional src_object datum If provided, only update UIs belonging this src_object.
- * optional ui_key string If provided, only update UIs with this UI key.
- *
- * return int The number of UIs updated.
- */
-/datum/controller/subsystem/tgui/proc/update_user_uis(mob/user, datum/src_object = null, ui_key = null)
- if(isnull(user.open_uis) || !istype(user.open_uis, /list) || open_uis.len == 0)
- return 0 // Couldn't find any UIs for this user.
-
- var/update_count = 0
- for(var/datum/tgui/ui in user.open_uis)
- if((isnull(src_object) || !isnull(src_object) && ui.src_object == src_object) && (isnull(ui_key) || !isnull(ui_key) && ui.ui_key == ui_key))
- ui.process(force = 1) // Update the UI.
- update_count++ // Count each UI we upadte.
- return update_count
-
-/**
- * private
- *
- * Close all UIs belonging to a user.
- *
- * required user mob The mob who opened/is using the UI.
- * optional src_object datum If provided, only close UIs belonging this src_object.
- * optional ui_key string If provided, only close UIs with this UI key.
- *
- * return int The number of UIs closed.
- */
-/datum/controller/subsystem/tgui/proc/close_user_uis(mob/user, datum/src_object = null, ui_key = null)
- if(isnull(user.open_uis) || !istype(user.open_uis, /list) || open_uis.len == 0)
- return 0 // Couldn't find any UIs for this user.
-
- var/close_count = 0
- for(var/datum/tgui/ui in user.open_uis)
- if((isnull(src_object) || !isnull(src_object) && ui.src_object == src_object) && (isnull(ui_key) || !isnull(ui_key) && ui.ui_key == ui_key))
- ui.close() // Close the UI.
- close_count++ // Count each UI we close.
- return close_count
-
-/**
- * private
- *
- * Add a UI to the list of open UIs.
- *
- * required ui datum/tgui The UI to be added.
- */
-/datum/controller/subsystem/tgui/proc/on_open(datum/tgui/ui)
- var/src_object_key = "[REF(ui.src_object)]"
- if(isnull(open_uis[src_object_key]) || !istype(open_uis[src_object_key], /list))
- open_uis[src_object_key] = list(ui.ui_key = list()) // Make a list for the ui_key and src_object.
- else if(isnull(open_uis[src_object_key][ui.ui_key]) || !istype(open_uis[src_object_key][ui.ui_key], /list))
- open_uis[src_object_key][ui.ui_key] = list() // Make a list for the ui_key.
-
- // Append the UI to all the lists.
- ui.user.open_uis |= ui
- var/list/uis = open_uis[src_object_key][ui.ui_key]
- uis |= ui
- processing_uis |= ui
-
-/**
- * private
- *
- * Remove a UI from the list of open UIs.
- *
- * required ui datum/tgui The UI to be removed.
- *
- * return bool If the UI was removed or not.
- */
-/datum/controller/subsystem/tgui/proc/on_close(datum/tgui/ui)
- var/src_object_key = "[REF(ui.src_object)]"
- if(isnull(open_uis[src_object_key]) || !istype(open_uis[src_object_key], /list))
- return 0 // It wasn't open.
- else if(isnull(open_uis[src_object_key][ui.ui_key]) || !istype(open_uis[src_object_key][ui.ui_key], /list))
- return 0 // It wasn't open.
-
- processing_uis.Remove(ui) // Remove it from the list of processing UIs.
- if(ui.user) // If the user exists, remove it from them too.
- ui.user.open_uis.Remove(ui)
- var/Ukey = ui.ui_key
- var/list/uis = open_uis[src_object_key][Ukey] // Remove it from the list of open UIs.
- uis.Remove(ui)
- if(!uis.len)
- var/list/uiobj = open_uis[src_object_key]
- uiobj.Remove(Ukey)
- if(!uiobj.len)
- open_uis.Remove(src_object_key)
-
- return 1 // Let the caller know we did it.
-
-/**
- * private
- *
- * Handle client logout, by closing all their UIs.
- *
- * required user mob The mob which logged out.
- *
- * return int The number of UIs closed.
- */
-/datum/controller/subsystem/tgui/proc/on_logout(mob/user)
- return close_user_uis(user)
-
-/**
- * private
- *
- * Handle clients switching mobs, by transferring their UIs.
- *
- * required user source The client's original mob.
- * required user target The client's new mob.
- *
- * return bool If the UIs were transferred.
- */
-/datum/controller/subsystem/tgui/proc/on_transfer(mob/source, mob/target)
- if(!source || isnull(source.open_uis) || !istype(source.open_uis, /list) || open_uis.len == 0)
- return 0 // The old mob had no open UIs.
-
- if(isnull(target.open_uis) || !istype(target.open_uis, /list))
- target.open_uis = list() // Create a list for the new mob if needed.
-
- for(var/datum/tgui/ui in source.open_uis)
- ui.user = target // Inform the UIs of their new owner.
- target.open_uis.Add(ui) // Transfer all the UIs.
-
- source.open_uis.Cut() // Clear the old list.
- return 1 // Let the caller know we did it.
diff --git a/code/modules/tgui/tgui.dm b/code/modules/tgui/tgui.dm
index 7971a940d4..b3b07eb178 100644
--- a/code/modules/tgui/tgui.dm
+++ b/code/modules/tgui/tgui.dm
@@ -1,7 +1,6 @@
/**
- * tgui
- *
- * /tg/station user interface library
+ * Copyright (c) 2020 Aleksej Komarov
+ * SPDX-License-Identifier: MIT
*/
/**
@@ -14,34 +13,26 @@
var/datum/src_object
/// The title of te UI.
var/title
- /// The ui_key of the UI. This allows multiple UIs for one src_object.
- var/ui_key
/// The window_id for browse() and onclose().
- var/window_id
- /// The window width.
- var/width = 0
- /// The window height
- var/height = 0
+ var/datum/tgui_window/window
+ /// Key that is used for remembering the window geometry.
+ var/window_key
+ /// Deprecated: Window size.
+ var/window_size
/// The interface (template) to be used for this UI.
var/interface
/// Update the UI every MC tick.
var/autoupdate = TRUE
/// If the UI has been initialized yet.
var/initialized = FALSE
- /// The data (and datastructure) used to initialize the UI.
- var/list/initial_data
- /// The static data used to initialize the UI.
- var/list/initial_static_data
- /// Holder for the json string, that is sent during the initial update
- var/_initial_update
+ /// Time of opening the window.
+ var/opened_at
+ /// Stops further updates when close() was called.
+ var/closing = FALSE
/// The status/visibility of the UI.
var/status = UI_INTERACTIVE
/// Topic state used to determine status/interactability.
var/datum/ui_state/state = null
- /// The parent UI.
- var/datum/tgui/master_ui
- /// Children of this UI.
- var/list/datum/tgui/children = list()
/**
* public
@@ -50,38 +41,25 @@
*
* required user mob The mob who opened/is using the UI.
* required src_object datum The object or datum which owns the UI.
- * required ui_key string The ui_key of the UI.
* required interface string The interface used to render the UI.
* optional title string The title of the UI.
- * optional width int The window width.
- * optional height int The window height.
- * optional master_ui datum/tgui The parent UI.
- * optional state datum/ui_state The state used to determine status.
+ * optional ui_x int Deprecated: Window width.
+ * optional ui_y int Deprecated: Window height.
*
* return datum/tgui The requested UI.
*/
-/datum/tgui/New(mob/user, datum/src_object, ui_key, interface, title, width = 0, height = 0, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state)
+/datum/tgui/New(mob/user, datum/src_object, interface, title, ui_x, ui_y)
+ log_tgui(user, "new [interface] fancy [user.client.prefs.tgui_fancy]")
src.user = user
src.src_object = src_object
- src.ui_key = ui_key
- // DO NOT replace with \ref here. src_object could potentially be tagged
- src.window_id = "[REF(src_object)]-[ui_key]"
+ src.window_key = "[REF(src_object)]-main"
src.interface = interface
-
if(title)
- src.title = sanitize(title)
- if(width)
- src.width = width
- if(height)
- src.height = height
-
- src.master_ui = master_ui
- if(master_ui)
- master_ui.children += src
- src.state = state
-
- var/datum/asset/assets = get_asset_datum(/datum/asset/group/tgui)
- assets.send(user)
+ src.title = title
+ src.state = src_object.ui_state()
+ // Deprecated
+ if(ui_x && ui_y)
+ src.window_size = list(ui_x, ui_y)
/**
* public
@@ -90,85 +68,59 @@
*/
/datum/tgui/proc/open()
if(!user.client)
- return // Bail if there is no client.
-
- update_status(push = FALSE) // Update the window status.
+ return null
+ if(window)
+ return null
+ process_status()
if(status < UI_UPDATE)
- return // Bail if we're not supposed to open.
-
- // Build window options
- var/window_options = "can_minimize=0;auto_format=0;"
- // If we have a width and height, use them.
- if(width && height)
- window_options += "size=[width]x[height];"
- // Remove titlebar and resize handles for a fancy window
- if(user.client.prefs.tgui_fancy)
- window_options += "titlebar=0;can_resize=0;"
+ return null
+ window = SStgui.request_pooled_window(user)
+ if(!window)
+ return null
+ opened_at = world.time
+ window.acquire_lock(src)
+ if(!window.is_ready())
+ window.initialize(
+ fancy = user.client.prefs.tgui_fancy,
+ inline_assets = list(
+ get_asset_datum(/datum/asset/simple/tgui_common),
+ get_asset_datum(/datum/asset/simple/tgui),
+ ))
else
- window_options += "titlebar=1;can_resize=1;"
-
- // Generate page html
- var/html
- html = SStgui.basehtml
- // Allow the src object to override the html if needed
- html = src_object.ui_base_html(html)
- // Replace template tokens with important UI data
- // NOTE: Intentional \ref usage; tgui datums can't/shouldn't
- // be tagged, so this is an effective unwrap
- html = replacetextEx(html, "\[tgui:ref]", "\ref[src]")
-
- // Open the window.
- user << browse(html, "window=[window_id];[window_options]")
-
- // Instruct the client to signal UI when the window is closed.
- // NOTE: Intentional \ref usage; tgui datums can't/shouldn't
- // be tagged, so this is an effective unwrap
- winset(user, window_id, "on-close=\"uiclose \ref[src]\"")
-
- // Pre-fetch initial state while browser is still loading in
- // another thread
- if(!initial_data) {
- initial_data = src_object.ui_data(user)
- }
- if(!initial_static_data) {
- initial_static_data = src_object.ui_static_data(user)
- }
- _initial_update = url_encode(get_json(initial_data, initial_static_data))
-
+ window.send_message("ping")
+ var/flush_queue = window.send_asset(get_asset_datum(
+ /datum/asset/simple/namespaced/fontawesome))
+ for(var/datum/asset/asset in src_object.ui_assets(user))
+ flush_queue |= window.send_asset(asset)
+ if (flush_queue)
+ user.client.browse_queue_flush()
+ window.send_message("update", get_payload(
+ with_data = TRUE,
+ with_static_data = TRUE))
SStgui.on_open(src)
/**
* public
*
- * Reinitialize the UI.
- * (Possibly with a new interface and/or data).
+ * Close the UI.
*
- * optional template string The name of the new interface.
- * optional data list The new initial data.
+ * optional can_be_suspended bool
*/
-/datum/tgui/proc/reinitialize(interface, list/data, list/static_data)
- if(interface)
- src.interface = interface
- if(data)
- initial_data = data
- if(static_data)
- initial_static_data = static_data
- open()
-
-/**
- * public
- *
- * Close the UI, and all its children.
- */
-/datum/tgui/proc/close()
- user << browse(null, "window=[window_id]") // Close the window.
- src_object.ui_close(user)
- SStgui.on_close(src)
- for(var/datum/tgui/child in children) // Loop through and close all children.
- child.close()
- children.Cut()
+/datum/tgui/proc/close(can_be_suspended = TRUE)
+ if(closing)
+ return
+ closing = TRUE
+ // If we don't have window_id, open proc did not have the opportunity
+ // to finish, therefore it's safe to skip this whole block.
+ if(window)
+ // Windows you want to keep are usually blue screens of death
+ // and we want to keep them around, to allow user to read
+ // the error message properly.
+ window.release_lock()
+ window.close(can_be_suspended)
+ src_object.ui_close(user)
+ SStgui.on_close(src)
state = null
- master_ui = null
qdel(src)
/**
@@ -176,187 +128,179 @@
*
* Enable/disable auto-updating of the UI.
*
- * required state bool Enable/disable auto-updating.
+ * required value bool Enable/disable auto-updating.
*/
-/datum/tgui/proc/set_autoupdate(state = TRUE)
- autoupdate = state
+/datum/tgui/proc/set_autoupdate(autoupdate)
+ src.autoupdate = autoupdate
+
+/**
+ * public
+ *
+ * Replace current ui.state with a new one.
+ *
+ * required state datum/ui_state/state Next state
+ */
+/datum/tgui/proc/set_state(datum/ui_state/state)
+ src.state = state
+
+/**
+ * public
+ *
+ * Makes an asset available to use in tgui.
+ *
+ * required asset datum/asset
+ *
+ * return bool - true if an asset was actually sent
+ */
+/datum/tgui/proc/send_asset(datum/asset/asset)
+ if(!window)
+ CRASH("send_asset() can only be called after open().")
+ return window.send_asset(asset)
+
+/**
+ * public
+ *
+ * Send a full update to the client (includes static data).
+ *
+ * optional custom_data list Custom data to send instead of ui_data.
+ * optional force bool Send an update even if UI is not interactive.
+ */
+/datum/tgui/proc/send_full_update(custom_data, force)
+ if(!user.client || !initialized || closing)
+ return
+ var/should_update_data = force || status >= UI_UPDATE
+ window.send_message("update", get_payload(
+ custom_data,
+ with_data = should_update_data,
+ with_static_data = TRUE))
+
+/**
+ * public
+ *
+ * Send a partial update to the client (excludes static data).
+ *
+ * optional custom_data list Custom data to send instead of ui_data.
+ * optional force bool Send an update even if UI is not interactive.
+ */
+/datum/tgui/proc/send_update(custom_data, force)
+ if(!user.client || !initialized || closing)
+ return
+ var/should_update_data = force || status >= UI_UPDATE
+ window.send_message("update", get_payload(
+ custom_data,
+ with_data = should_update_data))
/**
* private
*
* Package the data to send to the UI, as JSON.
- * This includes the UI data and config_data.
*
- * return string The packaged JSON.
+ * return list
*/
-/datum/tgui/proc/get_json(list/data, list/static_data)
+/datum/tgui/proc/get_payload(custom_data, with_data, with_static_data)
var/list/json_data = list()
-
json_data["config"] = list(
"title" = title,
"status" = status,
"interface" = interface,
- "fancy" = user.client.prefs.tgui_fancy,
- "locked" = user.client.prefs.tgui_lock,
- "observer" = isobserver(user),
- "window" = window_id,
- // NOTE: Intentional \ref usage; tgui datums can't/shouldn't
- // be tagged, so this is an effective unwrap
- "ref" = "\ref[src]"
+ "window" = list(
+ "key" = window_key,
+ "size" = window_size,
+ "fancy" = user.client.prefs.tgui_fancy,
+ "locked" = user.client.prefs.tgui_lock,
+ ),
+ "client" = list(
+ "ckey" = user.client.ckey,
+ "address" = user.client.address,
+ "computer_id" = user.client.computer_id,
+ ),
+ "user" = list(
+ "name" = "[user]",
+ "observer" = isobserver(user),
+ ),
)
-
- if(!isnull(data))
+ var/data = custom_data || with_data && src_object.ui_data(user)
+ if(data)
json_data["data"] = data
- if(!isnull(static_data))
+ var/static_data = with_static_data && src_object.ui_static_data(user)
+ if(static_data)
json_data["static_data"] = static_data
-
- // Send shared states
if(src_object.tgui_shared_states)
json_data["shared"] = src_object.tgui_shared_states
-
- // Generate the JSON.
- var/json = json_encode(json_data)
- // Strip #255/improper.
- json = replacetext(json, "\proper", "")
- json = replacetext(json, "\improper", "")
- return json
+ return json_data
/**
* private
*
- * Handle clicks from the UI.
- * Call the src_object's ui_act() if status is UI_INTERACTIVE.
- * If the src_object's ui_act() returns 1, update all UIs attacked to it.
- */
-/datum/tgui/Topic(href, href_list)
- if(user != usr)
- return // Something is not right here.
-
- var/action = href_list["action"]
- var/params = href_list; params -= "action"
-
- switch(action)
- if("tgui:initialize")
- user << output(_initial_update, "[window_id].browser:update")
- initialized = TRUE
- if("tgui:setSharedState")
- // Update the window state.
- update_status(push = FALSE)
- // Bail if UI is not interactive or usr calling Topic
- // is not the UI user.
- if(status != UI_INTERACTIVE)
- return
- var/key = params["key"]
- var/value = params["value"]
- if(!src_object.tgui_shared_states)
- src_object.tgui_shared_states = list()
- src_object.tgui_shared_states[key] = value
- SStgui.update_uis(src_object)
- if("tgui:setFancy")
- var/value = text2num(params["value"])
- user.client.prefs.tgui_fancy = value
- if("tgui:log")
- // Force window to show frills on fatal errors
- if(params["fatal"])
- winset(user, window_id, "titlebar=1;can-resize=1;size=600x600")
- log_message(params["log"])
- if("tgui:link")
- user << link(params["url"])
- else
- // Update the window state.
- update_status(push = FALSE)
- // Call ui_act() on the src_object.
- if(src_object.ui_act(action, params, src, state))
- // Update if the object requested it.
- SStgui.update_uis(src_object)
-
-/**
- * private
- *
- * Update the UI.
- * Only updates the data if update is true, otherwise only updates the status.
- *
- * optional force bool If the UI should be forced to update.
+ * Run an update cycle for this UI. Called internally by SStgui
+ * every second or so.
*/
/datum/tgui/process(force = FALSE)
+ if(closing)
+ return
var/datum/host = src_object.ui_host(user)
- if(!src_object || !host || !user) // If the object or user died (or something else), abort.
+ // If the object or user died (or something else), abort.
+ if(!src_object || !host || !user || !window)
+ close(can_be_suspended = FALSE)
+ return
+ // Validate ping
+ if(!initialized && world.time - opened_at > TGUI_PING_TIMEOUT)
+ log_tgui(user, \
+ "Error: Zombie window detected, killing it with fire.\n" \
+ + "window_id: [window.id]\n" \
+ + "opened_at: [opened_at]\n" \
+ + "world.time: [world.time]")
+ close(can_be_suspended = FALSE)
+ return
+ // Update through a normal call to ui_interact
+ if(status != UI_DISABLED && (autoupdate || force))
+ src_object.ui_interact(user, src)
+ return
+ // Update status only
+ var/needs_update = process_status()
+ if(status <= UI_CLOSE)
close()
return
-
- if(status && (force || autoupdate))
- update() // Update the UI if the status and update settings allow it.
- else
- update_status(push = TRUE) // Otherwise only update status.
+ if(needs_update)
+ window.send_message("update", get_payload())
/**
* private
*
- * Push data to an already open UI.
- *
- * required data list The data to send.
- * optional force bool If the update should be sent regardless of state.
+ * Updates the status, and returns TRUE if status has changed.
*/
-/datum/tgui/proc/push_data(data, static_data, force = FALSE)
- // Update the window state.
- update_status(push = FALSE)
- // Cannot update UI if it is not set up yet.
- if(!initialized)
- return
- // Cannot update UI, we have no visibility.
- if(status <= UI_DISABLED && !force)
- return
- // Send the new JSON to the update() Javascript function.
- user << output(
- url_encode(get_json(data, static_data)),
- "[window_id].browser:update")
+/datum/tgui/proc/process_status()
+ var/prev_status = status
+ status = src_object.ui_status(user, state)
+ return prev_status != status
/**
* private
*
- * Updates the UI by interacting with the src_object again, which will hopefully
- * call try_ui_update on it.
- *
- * optional force_open bool If force_open should be passed to ui_interact.
+ * Callback for handling incoming tgui messages.
*/
-/datum/tgui/proc/update(force_open = FALSE)
- src_object.ui_interact(user, ui_key, src, force_open, master_ui, state)
-
-/**
- * private
- *
- * Update the status/visibility of the UI for its user.
- *
- * optional push bool Push an update to the UI (an update is always sent for UI_DISABLED).
- */
-/datum/tgui/proc/update_status(push = FALSE)
- var/status = src_object.ui_status(user, state)
- if(master_ui)
- status = min(status, master_ui.status)
- set_status(status, push)
- if(status == UI_CLOSE)
- close()
-
-/**
- * private
- *
- * Set the status/visibility of the UI.
- *
- * required status int The status to set (UI_CLOSE/UI_DISABLED/UI_UPDATE/UI_INTERACTIVE).
- * optional push bool Push an update to the UI (an update is always sent for UI_DISABLED).
- */
-/datum/tgui/proc/set_status(status, push = FALSE)
- // Only update if status has changed.
- if(src.status != status)
- if(src.status == UI_DISABLED)
- src.status = status
- if(push)
- update()
- else
- src.status = status
- // Update if the UI just because disabled, or a push is requested.
- if(status == UI_DISABLED || push)
- push_data(null, force = TRUE)
-
-/datum/tgui/proc/log_message(message)
- log_tgui("[user] ([user.ckey]) using \"[title]\":\n[message]")
+/datum/tgui/proc/on_message(type, list/payload, list/href_list)
+ // Pass act type messages to ui_act
+ if(type && copytext(type, 1, 5) == "act/")
+ process_status()
+ if(src_object.ui_act(copytext(type, 5), payload, src, state))
+ SStgui.update_uis(src_object)
+ return FALSE
+ switch(type)
+ if("ready")
+ initialized = TRUE
+ if("pingReply")
+ initialized = TRUE
+ if("suspend")
+ close(can_be_suspended = TRUE)
+ if("close")
+ close(can_be_suspended = FALSE)
+ if("log")
+ if(href_list["fatal"])
+ close(can_be_suspended = FALSE)
+ if("setSharedState")
+ if(status != UI_INTERACTIVE)
+ return
+ LAZYINITLIST(src_object.tgui_shared_states)
+ src_object.tgui_shared_states[href_list["key"]] = href_list["value"]
+ SStgui.update_uis(src_object)
diff --git a/code/modules/tgui/tgui_window.dm b/code/modules/tgui/tgui_window.dm
new file mode 100644
index 0000000000..b511fe4057
--- /dev/null
+++ b/code/modules/tgui/tgui_window.dm
@@ -0,0 +1,316 @@
+/**
+ * Copyright (c) 2020 Aleksej Komarov
+ * SPDX-License-Identifier: MIT
+ */
+
+/datum/tgui_window
+ var/id
+ var/client/client
+ var/pooled
+ var/pool_index
+ var/is_browser = FALSE
+ var/status = TGUI_WINDOW_CLOSED
+ var/locked = FALSE
+ var/datum/tgui/locked_by
+ var/datum/subscriber_object
+ var/subscriber_delegate
+ var/fatally_errored = FALSE
+ var/message_queue
+ var/sent_assets = list()
+ // Vars passed to initialize proc (and saved for later)
+ var/inline_assets
+ var/fancy
+
+/**
+ * public
+ *
+ * Create a new tgui window.
+ *
+ * required client /client
+ * required id string A unique window identifier.
+ */
+/datum/tgui_window/New(client/client, id, pooled = FALSE)
+ src.id = id
+ src.client = client
+ src.client.tgui_windows[id] = src
+ src.pooled = pooled
+ if(pooled)
+ src.pool_index = TGUI_WINDOW_INDEX(id)
+
+/**
+ * public
+ *
+ * Initializes the window with a fresh page. Puts window into the "loading"
+ * state. You can begin sending messages right after initializing. Messages
+ * will be put into the queue until the window finishes loading.
+ *
+ * optional inline_assets list List of assets to inline into the html.
+ * optional inline_html string Custom HTML to inject.
+ * optional fancy bool If TRUE, will hide the window titlebar.
+ */
+/datum/tgui_window/proc/initialize(
+ inline_assets = list(),
+ inline_html = "",
+ fancy = FALSE)
+ log_tgui(client, "[id]/initialize")
+ if(!client)
+ return
+ src.inline_assets = inline_assets
+ src.fancy = fancy
+ status = TGUI_WINDOW_LOADING
+ fatally_errored = FALSE
+ // Build window options
+ var/options = "file=[id].html;can_minimize=0;auto_format=0;"
+ // Remove titlebar and resize handles for a fancy window
+ if(fancy)
+ options += "titlebar=0;can_resize=0;"
+ else
+ options += "titlebar=1;can_resize=1;"
+ // Generate page html
+ var/html = SStgui.basehtml
+ html = replacetextEx(html, "\[tgui:windowId]", id)
+ // Process inline assets
+ var/inline_styles = ""
+ var/inline_scripts = ""
+ for(var/datum/asset/asset in inline_assets)
+ var/mappings = asset.get_url_mappings()
+ for(var/name in mappings)
+ var/url = mappings[name]
+ // Not urlencoding since asset strings are considered safe
+ if(copytext(name, -4) == ".css")
+ inline_styles += "\n"
+ else if(copytext(name, -3) == ".js")
+ inline_scripts += "\n"
+ asset.send(client)
+ html = replacetextEx(html, "\n", inline_styles)
+ html = replacetextEx(html, "\n", inline_scripts)
+ // Inject custom HTML
+ html = replacetextEx(html, "\n", inline_html)
+ // Open the window
+ client << browse(html, "window=[id];[options]")
+ // Instruct the client to signal UI when the window is closed.
+ winset(client, id, "on-close=\"uiclose [id]\"")
+ // Detect whether the control is a browser
+ is_browser = winexists(client, id) == "BROWSER"
+
+/**
+ * public
+ *
+ * Checks if the window is ready to receive data.
+ *
+ * return bool
+ */
+/datum/tgui_window/proc/is_ready()
+ return status == TGUI_WINDOW_READY
+
+/**
+ * public
+ *
+ * Checks if the window can be sanely suspended.
+ *
+ * return bool
+ */
+/datum/tgui_window/proc/can_be_suspended()
+ return !fatally_errored \
+ && pooled \
+ && pool_index > 0 \
+ && pool_index <= TGUI_WINDOW_SOFT_LIMIT \
+ && status == TGUI_WINDOW_READY
+
+/**
+ * public
+ *
+ * Acquire the window lock. Pool will not be able to provide this window
+ * to other UIs for the duration of the lock.
+ *
+ * Can be given an optional tgui datum, which will be automatically
+ * subscribed to incoming messages via the on_message proc.
+ *
+ * optional ui /datum/tgui
+ */
+/datum/tgui_window/proc/acquire_lock(datum/tgui/ui)
+ locked = TRUE
+ locked_by = ui
+
+/**
+ * public
+ *
+ * Release the window lock.
+ */
+/datum/tgui_window/proc/release_lock()
+ // Clean up assets sent by tgui datum which requested the lock
+ if(locked)
+ sent_assets = list()
+ locked = FALSE
+ locked_by = null
+
+/**
+ * public
+ *
+ * Subscribes the datum to consume window messages on a specified proc.
+ *
+ * Note, that this supports only one subscriber, because code for that
+ * is simpler and therefore faster. If necessary, this can be rewritten
+ * to support multiple subscribers.
+ */
+/datum/tgui_window/proc/subscribe(datum/object, delegate)
+ subscriber_object = object
+ subscriber_delegate = delegate
+
+/**
+ * public
+ *
+ * Unsubscribes the datum. Do not forget to call this when cleaning up.
+ */
+/datum/tgui_window/proc/unsubscribe(datum/object)
+ subscriber_object = null
+ subscriber_delegate = null
+
+/**
+ * public
+ *
+ * Close the UI.
+ *
+ * optional can_be_suspended bool
+ */
+/datum/tgui_window/proc/close(can_be_suspended = TRUE)
+ if(!client)
+ return
+ if(can_be_suspended && can_be_suspended())
+ log_tgui(client, "[id]/close: suspending")
+ status = TGUI_WINDOW_READY
+ send_message("suspend")
+ return
+ log_tgui(client, "[id]/close")
+ release_lock()
+ status = TGUI_WINDOW_CLOSED
+ message_queue = null
+ // Do not close the window to give user some time
+ // to read the error message.
+ if(!fatally_errored)
+ client << browse(null, "window=[id]")
+
+/**
+ * public
+ *
+ * Sends a message to tgui window.
+ *
+ * required type string Message type
+ * required payload list Message payload
+ * optional force bool Send regardless of the ready status.
+ */
+/datum/tgui_window/proc/send_message(type, payload, force)
+ if(!client)
+ return
+ var/message = TGUI_CREATE_MESSAGE(type, payload)
+ // Place into queue if window is still loading
+ if(!force && status != TGUI_WINDOW_READY)
+ if(!message_queue)
+ message_queue = list()
+ message_queue += list(message)
+ return
+ client << output(message, is_browser \
+ ? "[id]:update" \
+ : "[id].browser:update")
+
+/**
+ * public
+ *
+ * Sends a raw payload to tgui window.
+ *
+ * required message string JSON+urlencoded blob to send.
+ * optional force bool Send regardless of the ready status.
+ */
+/datum/tgui_window/proc/send_raw_message(message, force)
+ if(!client)
+ return
+ // Place into queue if window is still loading
+ if(!force && status != TGUI_WINDOW_READY)
+ if(!message_queue)
+ message_queue = list()
+ message_queue += list(message)
+ return
+ client << output(message, is_browser \
+ ? "[id]:update" \
+ : "[id].browser:update")
+
+/**
+ * public
+ *
+ * Makes an asset available to use in tgui.
+ *
+ * required asset datum/asset
+ *
+ * return bool - TRUE if any assets had to be sent to the client
+ */
+/datum/tgui_window/proc/send_asset(datum/asset/asset)
+ if(!client || !asset)
+ return
+ sent_assets |= list(asset)
+ . = asset.send(client)
+ if(istype(asset, /datum/asset/spritesheet))
+ var/datum/asset/spritesheet/spritesheet = asset
+ send_message("asset/stylesheet", spritesheet.css_filename())
+ send_message("asset/mappings", asset.get_url_mappings())
+
+/**
+ * private
+ *
+ * Sends queued messages if the queue wasn't empty.
+ */
+/datum/tgui_window/proc/flush_message_queue()
+ if(!client || !message_queue)
+ return
+ for(var/message in message_queue)
+ client << output(message, is_browser \
+ ? "[id]:update" \
+ : "[id].browser:update")
+ message_queue = null
+
+/**
+ * private
+ *
+ * Callback for handling incoming tgui messages.
+ */
+/datum/tgui_window/proc/on_message(type, payload, href_list)
+ // Status can be READY if user has refreshed the window.
+ if(type == "ready" && status == TGUI_WINDOW_READY)
+ // Resend the assets
+ for(var/asset in sent_assets)
+ send_asset(asset)
+ // Mark this window as fatally errored which prevents it from
+ // being suspended.
+ if(type == "log" && href_list["fatal"])
+ fatally_errored = TRUE
+ // Mark window as ready since we received this message from somewhere
+ if(status != TGUI_WINDOW_READY)
+ status = TGUI_WINDOW_READY
+ flush_message_queue()
+ // Pass message to UI that requested the lock
+ if(locked && locked_by)
+ var/prevent_default = locked_by.on_message(type, payload, href_list)
+ if(prevent_default)
+ return
+ // Pass message to the subscriber
+ else if(subscriber_object)
+ var/prevent_default = call(
+ subscriber_object,
+ subscriber_delegate)(type, payload, href_list)
+ if(prevent_default)
+ return
+ // If not locked, handle these message types
+ switch(type)
+ if("ping")
+ send_message("pingReply", payload)
+ if("suspend")
+ close(can_be_suspended = TRUE)
+ if("close")
+ close(can_be_suspended = FALSE)
+ if("openLink")
+ client << link(href_list["url"])
+ if("cacheReloaded")
+ // Reinitialize
+ initialize(inline_assets = inline_assets, fancy = fancy)
+ // Resend the assets
+ for(var/asset in sent_assets)
+ send_asset(asset)
diff --git a/code/modules/tgui_panel/audio.dm b/code/modules/tgui_panel/audio.dm
new file mode 100644
index 0000000000..e62c4b5bc1
--- /dev/null
+++ b/code/modules/tgui_panel/audio.dm
@@ -0,0 +1,42 @@
+/**
+ * Copyright (c) 2020 Aleksej Komarov
+ * SPDX-License-Identifier: MIT
+ */
+
+/// Admin music volume, from 0 to 1.
+/client/var/admin_music_volume = 1
+
+/**
+ * public
+ *
+ * Sends music data to the browser.
+ *
+ * Optional settings:
+ * - pitch: the playback rate
+ * - start: the start time of the sound
+ * - end: when the musics stops playing
+ *
+ * required url string Must be an https URL.
+ * optional extra_data list Optional settings.
+ */
+/datum/tgui_panel/proc/play_music(url, extra_data)
+ if(!is_ready())
+ return
+ if(!findtext(url, GLOB.is_http_protocol))
+ return
+ var/list/payload = list()
+ if(length(extra_data) > 0)
+ for(var/key in extra_data)
+ payload[key] = extra_data[key]
+ payload["url"] = url
+ window.send_message("audio/playMusic", payload)
+
+/**
+ * public
+ *
+ * Stops playing music through the browser.
+ */
+/datum/tgui_panel/proc/stop_music()
+ if(!is_ready())
+ return
+ window.send_message("audio/stopMusic")
diff --git a/code/modules/tgui_panel/external.dm b/code/modules/tgui_panel/external.dm
new file mode 100644
index 0000000000..57c89dc194
--- /dev/null
+++ b/code/modules/tgui_panel/external.dm
@@ -0,0 +1,53 @@
+/**
+ * Copyright (c) 2020 Aleksej Komarov
+ * SPDX-License-Identifier: MIT
+ */
+
+/client/var/datum/tgui_panel/tgui_panel
+
+/**
+ * tgui panel / chat troubleshooting verb
+ */
+/client/verb/fix_chat()
+ set name = "Fix chat"
+ set category = "OOC"
+ var/action
+ log_tgui(src, "tgui_panel: Started fix_chat.")
+ // Not initialized
+ if(!tgui_panel || !istype(tgui_panel))
+ log_tgui(src, "tgui_panel: datum is missing")
+ action = alert(src, "tgui panel was not initialized!\nSet it up again?", "", "OK", "Cancel")
+ if(action != "OK")
+ return
+ tgui_panel = new(src)
+ tgui_panel.initialize()
+ action = alert(src, "Wait a bit and tell me if it's fixed", "", "Fixed", "Nope")
+ if(action == "Fixed")
+ log_tgui(src, "tgui_panel: Fixed by calling 'new' + 'initialize'")
+ return
+ // Not ready
+ if(!tgui_panel?.is_ready())
+ log_tgui(src, "tgui_panel: not ready")
+ action = alert(src, "tgui panel looks like it's waiting for something.\nSend it a ping?", "", "OK", "Cancel")
+ if(action != "OK")
+ return
+ tgui_panel.window.send_message("ping", force = TRUE)
+ action = alert(src, "Wait a bit and tell me if it's fixed", "", "Fixed", "Nope")
+ if(action == "Fixed")
+ log_tgui(src, "tgui_panel: Fixed by sending a ping")
+ return
+ // Catch all solution
+ action = alert(src, "Looks like tgui panel was already setup, but we can always try again.\nSet it up again?", "", "OK", "Cancel")
+ if(action != "OK")
+ return
+ tgui_panel.initialize(force = TRUE)
+ action = alert(src, "Wait a bit and tell me if it's fixed", "", "Fixed", "Nope")
+ if(action == "Fixed")
+ log_tgui(src, "tgui_panel: Fixed by calling 'initialize'")
+ return
+ // Failed to fix
+ action = alert(src, "Welp, I'm all out of ideas. Try closing BYOND and reconnecting.\nWe could also disable tgui_panel and re-enable the old UI", "", "Thanks anyways", "Switch to old UI")
+ if (action == "Switch to old UI")
+ winset(src, "output", "on-show=&is-disabled=0&is-visible=1")
+ winset(src, "browseroutput", "is-disabled=1;is-visible=0")
+ log_tgui(src, "tgui_panel: Failed to fix.")
diff --git a/code/modules/tgui_panel/telemetry.dm b/code/modules/tgui_panel/telemetry.dm
new file mode 100644
index 0000000000..79087d8500
--- /dev/null
+++ b/code/modules/tgui_panel/telemetry.dm
@@ -0,0 +1,80 @@
+/**
+ * Copyright (c) 2020 Aleksej Komarov
+ * SPDX-License-Identifier: MIT
+ */
+
+/**
+ * Maximum number of connection records allowed to analyze.
+ * Should match the value set in the browser.
+ */
+#define TGUI_TELEMETRY_MAX_CONNECTIONS 5
+
+/**
+ * Maximum time allocated for sending a telemetry packet.
+ */
+#define TGUI_TELEMETRY_RESPONSE_WINDOW 30 SECONDS
+
+/// Time of telemetry request
+/datum/tgui_panel/var/telemetry_requested_at
+/// Time of telemetry analysis completion
+/datum/tgui_panel/var/telemetry_analyzed_at
+/// List of previous client connections
+/datum/tgui_panel/var/list/telemetry_connections
+
+/**
+ * private
+ *
+ * Requests some telemetry from the client.
+ */
+/datum/tgui_panel/proc/request_telemetry()
+ telemetry_requested_at = world.time
+ telemetry_analyzed_at = null
+ window.send_message("telemetry/request", list(
+ "limits" = list(
+ "connections" = TGUI_TELEMETRY_MAX_CONNECTIONS,
+ ),
+ ))
+
+/**
+ * private
+ *
+ * Analyzes a telemetry packet.
+ *
+ * Is currently only useful for detecting ban evasion attempts.
+ */
+/datum/tgui_panel/proc/analyze_telemetry(payload)
+ if(world.time > telemetry_requested_at + TGUI_TELEMETRY_RESPONSE_WINDOW)
+ message_admins("[key_name(client)] sent telemetry outside of the allocated time window.")
+ return
+ if(telemetry_analyzed_at)
+ message_admins("[key_name(client)] sent telemetry more than once.")
+ return
+ telemetry_analyzed_at = world.time
+ if(!payload)
+ return
+ telemetry_connections = payload["connections"]
+ var/len = length(telemetry_connections)
+ if(len == 0)
+ return
+ if(len > TGUI_TELEMETRY_MAX_CONNECTIONS)
+ message_admins("[key_name(client)] was kicked for sending a huge telemetry payload")
+ qdel(client)
+ return
+ var/list/found
+ for(var/i in 1 to len)
+ if(QDELETED(client))
+ // He got cleaned up before we were done
+ return
+ var/list/row = telemetry_connections[i]
+ // Check for a malformed history object
+ if (!row || row.len < 3 || (!row["ckey"] || !row["address"] || !row["computer_id"]))
+ return
+ if (world.IsBanned(row["ckey"], row["address"], row["computer_id"], real_bans_only = TRUE))
+ found = row
+ break
+ CHECK_TICK
+ // This fucker has a history of playing on a banned account.
+ if(found)
+ var/msg = "[key_name(client)] has a banned account in connection history! (Matched: [found["ckey"]], [found["address"]], [found["computer_id"]])"
+ message_admins(msg)
+ log_admin_private(msg)
diff --git a/code/modules/tgui_panel/tgui_panel.dm b/code/modules/tgui_panel/tgui_panel.dm
new file mode 100644
index 0000000000..b983484046
--- /dev/null
+++ b/code/modules/tgui_panel/tgui_panel.dm
@@ -0,0 +1,95 @@
+/**
+ * Copyright (c) 2020 Aleksej Komarov
+ * SPDX-License-Identifier: MIT
+ */
+
+/**
+ * tgui_panel datum
+ * Hosts tgchat and other nice features.
+ */
+/datum/tgui_panel
+ var/client/client
+ var/datum/tgui_window/window
+ var/broken = FALSE
+ var/initialized_at
+
+/datum/tgui_panel/New(client/client)
+ src.client = client
+ window = new(client, "browseroutput")
+ window.subscribe(src, .proc/on_message)
+
+/datum/tgui_panel/Del()
+ window.unsubscribe(src)
+ window.close()
+ return ..()
+
+/**
+ * public
+ *
+ * TRUE if panel is initialized and ready to receive messages.
+ */
+/datum/tgui_panel/proc/is_ready()
+ return !broken && window.is_ready()
+
+/**
+ * public
+ *
+ * Initializes tgui panel.
+ */
+/datum/tgui_panel/proc/initialize(force = FALSE)
+ initialized_at = world.time
+ // Perform a clean initialization
+ window.initialize(inline_assets = list(
+ get_asset_datum(/datum/asset/simple/tgui_common),
+ get_asset_datum(/datum/asset/simple/tgui_panel),
+ ))
+ window.send_asset(get_asset_datum(/datum/asset/simple/namespaced/fontawesome))
+ window.send_asset(get_asset_datum(/datum/asset/spritesheet/chat))
+ request_telemetry()
+ addtimer(CALLBACK(src, .proc/on_initialize_timed_out), 2 SECONDS)
+
+/**
+ * private
+ *
+ * Called when initialization has timed out.
+ */
+/datum/tgui_panel/proc/on_initialize_timed_out()
+ // Currently does nothing but sending a message to old chat.
+ SEND_TEXT(client, "Failed to load fancy chat, reverting to old chat. Certain features won't work.")
+
+/**
+ * private
+ *
+ * Callback for handling incoming tgui messages.
+ */
+/datum/tgui_panel/proc/on_message(type, payload)
+ if(type == "ready")
+ broken = FALSE
+ window.send_message("update", list(
+ "config" = list(
+ "client" = list(
+ "ckey" = client.ckey,
+ "address" = client.address,
+ "computer_id" = client.computer_id,
+ ),
+ "window" = list(
+ "fancy" = FALSE,
+ "locked" = FALSE,
+ ),
+ ),
+ ))
+ return TRUE
+ if(type == "audio/setAdminMusicVolume")
+ client.admin_music_volume = payload["volume"]
+ return TRUE
+ if(type == "telemetry")
+ analyze_telemetry(payload)
+ return TRUE
+
+/**
+ * public
+ *
+ * Sends a round restart notification.
+ */
+/datum/tgui_panel/proc/send_roundrestart()
+ window.send_message("roundrestart")
diff --git a/code/modules/tgui_panel/to_chat.dm b/code/modules/tgui_panel/to_chat.dm
new file mode 100644
index 0000000000..aad27d4872
--- /dev/null
+++ b/code/modules/tgui_panel/to_chat.dm
@@ -0,0 +1,71 @@
+/**
+ * Copyright (c) 2020 Aleksej Komarov
+ * SPDX-License-Identifier: MIT
+ */
+
+/**
+ * global
+ *
+ * Circumvents the message queue and sends the message
+ * to the recipient (target) as soon as possible.
+ */
+/proc/to_chat_immediate(
+ target,
+ text,
+ handle_whitespace = TRUE,
+ trailing_newline = TRUE,
+ confidential = FALSE)
+ if(!target || !text)
+ return
+ if(target == world)
+ target = GLOB.clients
+ var/flags = handle_whitespace \
+ | trailing_newline << 1 \
+ | confidential << 2
+ var/message = TGUI_CREATE_MESSAGE("chat/message", list(
+ "text" = text,
+ "flags" = flags,
+ ))
+ if(islist(target))
+ for(var/_target in target)
+ var/client/client = CLIENT_FROM_VAR(_target)
+ if(client)
+ // Send to tgchat
+ client.tgui_panel?.window.send_raw_message(message)
+ // Send to old chat
+ SEND_TEXT(client, text)
+ return
+ var/client/client = CLIENT_FROM_VAR(target)
+ if(client)
+ // Send to tgchat
+ client.tgui_panel?.window.send_raw_message(message)
+ // Send to old chat
+ SEND_TEXT(client, text)
+
+/**
+ * global
+ *
+ * Sends the message to the recipient (target).
+ */
+/proc/to_chat(
+ target,
+ text,
+ handle_whitespace = TRUE,
+ trailing_newline = TRUE,
+ confidential = FALSE)
+ if(Master.current_runlevel == RUNLEVEL_INIT || !SSchat?.initialized)
+ to_chat_immediate(
+ target,
+ text,
+ handle_whitespace,
+ trailing_newline,
+ confidential)
+ return
+ if(!target || !text)
+ return
+ if(target == world)
+ target = GLOB.clients
+ var/flags = handle_whitespace \
+ | trailing_newline << 1 \
+ | confidential << 2
+ SSchat.queue(target, text, flags)
diff --git a/code/modules/unit_tests/_unit_tests.dm b/code/modules/unit_tests/_unit_tests.dm
index c739d426c2..e758a43589 100644
--- a/code/modules/unit_tests/_unit_tests.dm
+++ b/code/modules/unit_tests/_unit_tests.dm
@@ -3,6 +3,7 @@
#ifdef UNIT_TESTS
#include "anchored_mobs.dm"
+#include "character_saving.dm"
#include "component_tests.dm"
#include "reagent_id_typos.dm"
#include "reagent_recipe_collisions.dm"
diff --git a/code/modules/unit_tests/character_saving.dm b/code/modules/unit_tests/character_saving.dm
new file mode 100644
index 0000000000..bdcb0f0276
--- /dev/null
+++ b/code/modules/unit_tests/character_saving.dm
@@ -0,0 +1,14 @@
+/datum/unit_test/character_saving/Run()
+ try
+ var/datum/preferences/P = new
+ P.load_path("test")
+ P.features["flavor_text"] = "Foo"
+ P.features["ooc_notes"] = "Bar"
+ P.save_character()
+ P.load_character()
+ if(P.features["flavor_text"] != "Foo")
+ Fail("Flavor text is failing to save.")
+ if(P.features["ooc_notes"] != "Bar")
+ Fail("OOC text is failing to save.")
+ catch(var/exception/e)
+ Fail("Failed to save and load character due to exception [e.file]:[e.line], [e.name]")
diff --git a/code/modules/unit_tests/reactions.dm b/code/modules/unit_tests/reactions.dm
new file mode 100644
index 0000000000..66d9b49099
--- /dev/null
+++ b/code/modules/unit_tests/reactions.dm
@@ -0,0 +1,6 @@
+/datum/unit_test/reactions/Run()
+ for(var/datum/gas_reaction/G in SSair.gas_reactions)
+ var/test_info = G.test()
+ if(!test_info["success"])
+ var/message = test_info["message"]
+ Fail("Gas reaction [G.name] is failing its unit test with the following message: [message]")
diff --git a/code/modules/uplink/uplink_items/uplink_ammo.dm b/code/modules/uplink/uplink_items/uplink_ammo.dm
index ce74773f8d..853f6111b2 100644
--- a/code/modules/uplink/uplink_items/uplink_ammo.dm
+++ b/code/modules/uplink/uplink_items/uplink_ammo.dm
@@ -291,10 +291,17 @@
/datum/uplink_item/ammo/bolt_action
name = "Surplus Rifle Clip"
desc = "A stripper clip used to quickly load bolt action rifles. Contains 5 rounds."
- item = /obj/item/ammo_box/a762
+ item = /obj/item/ammo_box/a762
cost = 1
include_modes = list(/datum/game_mode/nuclear)
+/datum/uplink_item/ammo/bolt_action_bulk
+ name = "Surplus Rifle Clip Box"
+ desc = "An ammo box we found in a warehouse, holding 7 clips of 5 rounds for bolt-action rifles. Yes, the cheap ones."
+ item = /obj/item/storage/toolbox/ammo
+ cost = 4
+ include_modes = list(/datum/game_mode/nuclear)
+
/datum/uplink_item/ammo/dark_gygax/bag
name = "Dark Gygax Ammo Bag"
desc = "A duffel bag containing ammo for three full reloads of the incendiary carbine and flash bang launcher that are equipped on a standard Dark Gygax exosuit."
diff --git a/code/modules/uplink/uplink_items/uplink_bundles.dm b/code/modules/uplink/uplink_items/uplink_bundles.dm
index fbeaee8939..1b7909a50d 100644
--- a/code/modules/uplink/uplink_items/uplink_bundles.dm
+++ b/code/modules/uplink/uplink_items/uplink_bundles.dm
@@ -173,8 +173,7 @@
/datum/uplink_item/bundles_TC/reroll/purchase(mob/user, datum/component/uplink/U)
var/datum/antagonist/traitor/T = user?.mind?.has_antag_datum(/datum/antagonist/traitor)
if(istype(T))
- var/new_traitor_kind = get_random_traitor_kind(list(T.traitor_kind.type))
- T.set_traitor_kind(new_traitor_kind)
+ T.set_traitor_kind(/datum/traitor_class/human/subterfuge)
else
to_chat(user,"Invalid user for contract renegotiation.")
diff --git a/code/modules/uplink/uplink_items/uplink_clothing.dm b/code/modules/uplink/uplink_items/uplink_clothing.dm
index 014e0452b5..745eddcc07 100644
--- a/code/modules/uplink/uplink_items/uplink_clothing.dm
+++ b/code/modules/uplink/uplink_items/uplink_clothing.dm
@@ -90,6 +90,7 @@
desc = "Through bluespace magic stolen from an organisation that hoards technology, these boots simply allow you to slip through the atoms that make up anything, but only while walking, for safety reasons. As well as this, they unfortunately cause minor breath loss as the majority of atoms in your lungs are sucked out into any solid object you walk through."
item = /obj/item/clothing/shoes/wallwalkers
cost = 6
+ exclude_modes = list(/datum/game_mode/nuclear, /datum/game_mode/nuclear/clown_ops)
/datum/uplink_item/device_tools/guerillagloves
name = "Guerilla Gloves"
@@ -97,3 +98,9 @@
item = /obj/item/clothing/gloves/tackler/combat/insulated
include_modes = list(/datum/game_mode/nuclear, /datum/game_mode/nuclear/clown_ops)
cost = 2
+
+/datum/uplink_item/device_tools/syndicate_eyepatch
+ name = "Mechanical Eyepatch"
+ desc = "An eyepatch that connects itself to your eye socket, enhancing your shooting to an impossible degree, allowing your bullets to ricochet far more often than usual."
+ item = /obj/item/clothing/glasses/eyepatch/syndicate
+ cost = 8
diff --git a/code/modules/uplink/uplink_items/uplink_dangerous.dm b/code/modules/uplink/uplink_items/uplink_dangerous.dm
index e068094776..898b716775 100644
--- a/code/modules/uplink/uplink_items/uplink_dangerous.dm
+++ b/code/modules/uplink/uplink_items/uplink_dangerous.dm
@@ -20,6 +20,7 @@
desc = "A sleek box containing a brutally simple Syndicate revolver that fires .357 Magnum rounds and has 7 chambers, and an extra speedloader."
item = /obj/item/storage/box/syndie_kit/revolver
cost = 13
+ player_minimum = 15
surplus = 50
exclude_modes = list(/datum/game_mode/nuclear/clown_ops, /datum/game_mode/traitor/internal_affairs)
@@ -177,6 +178,7 @@
organic host as a home base and source of fuel. Holoparasites come in various types and share damage with their host."
item = /obj/item/storage/box/syndie_kit/guardian
cost = 15
+ limited_stock = 1 // you can only have one holopara apparently?
refundable = TRUE
cant_discount = TRUE
surplus = 0
@@ -185,6 +187,17 @@
restricted = TRUE
refund_path = /obj/item/guardiancreator/tech/choose/traitor
+/datum/uplink_item/dangerous/nukieguardian // just like the normal holoparasites but without the support or deffensive stands because nukies shouldnt turtle
+ name = "Holoparasites"
+ desc = "Though capable of near sorcerous feats via use of hardlight holograms and nanomachines, they require an \
+ organic host as a home base and source of fuel. Holoparasites come in various types and share damage with their host."
+ item = /obj/item/storage/box/syndie_kit/nukieguardian
+ cost = 15
+ refundable = TRUE
+ surplus = 50
+ refund_path = /obj/item/guardiancreator/tech/choose/nukie
+ include_modes = list(/datum/game_mode/nuclear)
+
/datum/uplink_item/dangerous/machinegun
name = "L6 Squad Automatic Weapon"
desc = "A fully-loaded Aussec Armoury belt-fed machine gun. \
diff --git a/code/modules/uplink/uplink_items/uplink_devices.dm b/code/modules/uplink/uplink_items/uplink_devices.dm
index 5149259bb8..5f5eb91a04 100644
--- a/code/modules/uplink/uplink_items/uplink_devices.dm
+++ b/code/modules/uplink/uplink_items/uplink_devices.dm
@@ -147,6 +147,12 @@
item = /obj/item/aiModule/syndicate
cost = 9
+/datum/uplink_item/device_tools/damaged_module
+ name = "Damaged AI Law Upload Module"
+ desc = "This AI law upload module has been laying around our warehouse for god knows how long. We do not know why you would ever use this."
+ item = /obj/item/aiModule/core/full/damaged
+ cost = 5
+
/datum/uplink_item/device_tools/headsetupgrade
name = "Headset Upgrader"
desc = "A device that can be used to make one headset immune to flashbangs."
@@ -200,9 +206,7 @@
this primer of questionable worth and value is rumored to increase your rifle-bolt-working and/or shotgun \
racking fivefold. Then again, the techniques here only work on bolt-actions and pump-actions..."
item = /obj/item/book/granter/trait/rifleman
- cost = 3
- restricted_roles = list("Operative") // i want it to be surplusable but i also want it to be mostly nukie only, please advise
- surplus = 90
+ cost = 3 // fuck it available for everyone
/datum/uplink_item/device_tools/stimpack
name = "Stimpack"
diff --git a/code/modules/uplink/uplink_items/uplink_explosives.dm b/code/modules/uplink/uplink_items/uplink_explosives.dm
index 6cc1c9fe05..f44966fb3b 100644
--- a/code/modules/uplink/uplink_items/uplink_explosives.dm
+++ b/code/modules/uplink/uplink_items/uplink_explosives.dm
@@ -80,6 +80,7 @@
item = /obj/item/cartridge/virus/syndicate
cost = 5
restricted = TRUE
+ limited_stock = 1
/datum/uplink_item/explosives/emp
name = "EMP Grenades and Implanter Kit"
diff --git a/code/modules/uplink/uplink_items/uplink_implants.dm b/code/modules/uplink/uplink_items/uplink_implants.dm
index 02b8b1e01d..bb4e0c7960 100644
--- a/code/modules/uplink/uplink_items/uplink_implants.dm
+++ b/code/modules/uplink/uplink_items/uplink_implants.dm
@@ -29,6 +29,13 @@
item = /obj/item/storage/box/syndie_kit/imp_freedom
cost = 5
+/datum/uplink_item/implants/warp
+ name = "Warp Implant"
+ desc = "An implant injected into the body and later activated at the user's will. It will inject eigenstasium which saves the user's location and teleports them there after five seconds. Lasts only fifteen times."
+ item = /obj/item/storage/box/syndie_kit/imp_warp
+ cost = 6
+ exclude_modes = list(/datum/game_mode/nuclear, /datum/game_mode/nuclear/clown_ops)
+
/datum/uplink_item/implants/hijack
name = "Hijack Implant"
desc = "An implant that will let you hack into the APCs on station, allowing you to control them at will and the machinery within those rooms."
diff --git a/code/modules/uplink/uplink_items/uplink_stealthdevices.dm b/code/modules/uplink/uplink_items/uplink_stealthdevices.dm
index f1c27c640b..28d02cf79b 100644
--- a/code/modules/uplink/uplink_items/uplink_stealthdevices.dm
+++ b/code/modules/uplink/uplink_items/uplink_stealthdevices.dm
@@ -112,13 +112,13 @@
name = "Radio Jammer"
desc = "This device will disrupt any nearby outgoing radio communication when activated. Does not affect binary chat."
item = /obj/item/jammer
- cost = 5
+ cost = 2
/datum/uplink_item/stealthy_tools/smugglersatchel
name = "Smuggler's Satchel"
desc = "This satchel is thin enough to be hidden in the gap between plating and tiling; great for stashing \
your stolen goods. Comes with a crowbar and a floor tile inside. Properly hidden satchels have been \
- known to survive intact even beyond the current shift. "
+ known to survive intact even beyond the current shift, but this is just a myth. "
item = /obj/item/storage/backpack/satchel/flat
- cost = 2
+ cost = 1
surplus = 30
diff --git a/code/modules/vehicles/_vehicle.dm b/code/modules/vehicles/_vehicle.dm
index ac7fa879f4..12e9f365d0 100644
--- a/code/modules/vehicles/_vehicle.dm
+++ b/code/modules/vehicles/_vehicle.dm
@@ -18,6 +18,7 @@
var/canmove = TRUE
var/emulate_door_bumps = TRUE //when bumping a door try to make occupants bump them to open them.
var/default_driver_move = TRUE //handle driver movement instead of letting something else do it like riding datums.
+ var/enclosed = FALSE // is the rider protected from bullets? assume no
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)
@@ -166,3 +167,9 @@
if(trailer && .)
var/dir_to_move = get_dir(trailer.loc, newloc)
step(trailer, dir_to_move)
+
+/obj/vehicle/bullet_act(obj/item/projectile/Proj) //wrapper
+ if (!enclosed && length(occupants) && !Proj.force_hit && (Proj.def_zone == BODY_ZONE_HEAD || Proj.def_zone == BODY_ZONE_CHEST)) //allows bullets to hit drivers
+ occupants[1].bullet_act(Proj) // driver dinkage
+ return BULLET_ACT_HIT
+ . = ..()
diff --git a/code/modules/vehicles/sealed.dm b/code/modules/vehicles/sealed.dm
index edaab8b982..28f6b1cca8 100644
--- a/code/modules/vehicles/sealed.dm
+++ b/code/modules/vehicles/sealed.dm
@@ -1,4 +1,5 @@
/obj/vehicle/sealed
+ enclosed = TRUE // you're in a sealed vehicle dont get dinked idiot
var/enter_delay = 20
flags_1 = BLOCK_FACE_ATOM_1
diff --git a/code/modules/vending/_vending.dm b/code/modules/vending/_vending.dm
index a7b9b6a298..a115300085 100644
--- a/code/modules/vending/_vending.dm
+++ b/code/modules/vending/_vending.dm
@@ -501,10 +501,10 @@ GLOBAL_LIST_EMPTY(vending_products)
C.bleed(150)
var/obj/item/bodypart/l_leg/l = C.get_bodypart(BODY_ZONE_L_LEG)
if(l)
- l.receive_damage(brute=200)
+ l.receive_damage(brute=200, updating_health=TRUE)
var/obj/item/bodypart/r_leg/r = C.get_bodypart(BODY_ZONE_R_LEG)
if(r)
- r.receive_damage(brute=200)
+ r.receive_damage(brute=200, updating_health=TRUE)
if(l || r)
C.visible_message("[C]'s legs shatter with a sickening crunch!", \
"Your legs shatter with a sickening crunch!")
@@ -530,8 +530,7 @@ GLOBAL_LIST_EMPTY(vending_products)
for(var/i in C.bodyparts)
var/obj/item/bodypart/squish_part = i
if(squish_part.is_organic_limb())
- //var/type_wound = pick(WOUND_LIST_BONE)
- var/type_wound = pick(list(/datum/wound/brute/bone/critical, /datum/wound/brute/bone/severe, /datum/wound/brute/bone/critical, /datum/wound/brute/bone/severe, /datum/wound/brute/bone/moderate))
+ var/type_wound = pick(list(/datum/wound/blunt/critical, /datum/wound/blunt/severe, /datum/wound/blunt/moderate))
squish_part.force_wound_upwards(type_wound)
else
squish_part.receive_damage(brute=30)
@@ -577,7 +576,7 @@ GLOBAL_LIST_EMPTY(vending_products)
"You right [src].")
unbuckle_all_mobs(TRUE)
-
+ anchored = FALSE //so you can push it back into position
tilted = FALSE
layer = initial(layer)
@@ -673,21 +672,21 @@ GLOBAL_LIST_EMPTY(vending_products)
return
return ..()
-/obj/machinery/vending/ui_base_html(html)
- var/datum/asset/spritesheet/assets = get_asset_datum(/datum/asset/spritesheet/vending)
- . = replacetext(html, "", assets.css_tag())
+/obj/machinery/vending/ui_assets(mob/user)
+ return list(
+ get_asset_datum(/datum/asset/spritesheet/vending),
+ )
-/obj/machinery/vending/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state)
- ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+/obj/machinery/vending/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
- var/datum/asset/assets = get_asset_datum(/datum/asset/spritesheet/vending)
- assets.send(user)
- ui = new(user, src, ui_key, "Vending", ui_key, 450, 600, master_ui, state)
+ ui = new(user, src, "Vending")
ui.open()
/obj/machinery/vending/ui_static_data(mob/user)
. = list()
.["onstation"] = onstation
+ .["department"] = payment_department
.["product_records"] = list()
for (var/datum/data/vending_product/R in product_records)
var/list/data = list(
@@ -714,7 +713,7 @@ GLOBAL_LIST_EMPTY(vending_products)
var/list/data = list(
path = replacetext(replacetext("[R.product_path]", "/obj/item/", ""), "/", "-"),
name = R.name,
- price = R.custom_price || default_price,
+ price = R.custom_premium_price || extra_price, //may cause breakage. please note
max_amount = R.max_amount,
ref = REF(R),
premium = TRUE
@@ -723,28 +722,24 @@ GLOBAL_LIST_EMPTY(vending_products)
/obj/machinery/vending/ui_data(mob/user)
. = list()
- var/obj/item/card/id/C = user.get_idcard(TRUE)
- .["cost_mult"] = 1
- .["cost_text"] = ""
- if(C && C.registered_account)
- .["user"] = list()
- .["user"]["name"] = C.registered_account.account_holder
- .["user"]["cash"] = C.registered_account.account_balance
- if(C.registered_account.account_job)
- .["user"]["job"] = C.registered_account.account_job.title
- else
- .["user"]["job"] = "No Job"
- var/cost_mult = get_best_discount(C)
- if(cost_mult != 1)
- .["cost_mult"] = cost_mult
- if(cost_mult < 1)
- .["cost_text"] = " ([(1 - cost_mult) * 100]% OFF)"
+ var/mob/living/carbon/human/H
+ var/obj/item/card/id/C
+ if(ishuman(user))
+ H = user
+ C = H.get_idcard(TRUE)
+ if(C?.registered_account)
+ .["user"] = list()
+ .["user"]["name"] = C.registered_account.account_holder
+ .["user"]["cash"] = C.registered_account.account_balance
+ if(C.registered_account.account_job)
+ .["user"]["job"] = C.registered_account.account_job.title
+ .["user"]["department"] = C.registered_account.account_job.paycheck_department
else
- .["cost_text"] = " ([(cost_mult - 1) * 100]% EXTRA)"
+ .["user"]["job"] = "No Job"
+ .["user"]["department"] = "No Department"
.["stock"] = list()
for (var/datum/data/vending_product/R in product_records + coin_records + hidden_records)
.["stock"][R.name] = R.amount
- .
.["extended_inventory"] = extended_inventory
/obj/machinery/vending/ui_act(action, params)
@@ -767,7 +762,9 @@ GLOBAL_LIST_EMPTY(vending_products)
if(!R || !istype(R) || !R.product_path)
vend_ready = TRUE
return
- var/price_to_use = R.custom_price || default_price
+ var/price_to_use = default_price
+ if(R.custom_price)
+ price_to_use = R.custom_price
if(R in hidden_records)
if(!extended_inventory)
vend_ready = TRUE
@@ -781,8 +778,10 @@ GLOBAL_LIST_EMPTY(vending_products)
flick(icon_deny,src)
vend_ready = TRUE
return
- if(onstation && price_to_use >= 0)
- var/obj/item/card/id/C = usr.get_idcard(TRUE)
+ if(onstation && ishuman(usr))
+ var/mob/living/carbon/human/H = usr
+ var/obj/item/card/id/C = H.get_idcard(TRUE)
+
if(!C)
say("No card found.")
flick(icon_deny,src)
@@ -793,11 +792,20 @@ GLOBAL_LIST_EMPTY(vending_products)
flick(icon_deny,src)
vend_ready = TRUE
return
+ // else if(age_restrictions && R.age_restricted && (!C.registered_age || C.registered_age < AGE_MINOR))
+ // say("You are not of legal age to purchase [R.name].")
+ // if(!(usr in GLOB.narcd_underages))
+ // Radio.set_frequency(FREQ_SECURITY)
+ // Radio.talk_into(src, "SECURITY ALERT: Underaged crewmember [H] recorded attempting to purchase [R.name] in [get_area(src)]. Please watch for substance abuse.", FREQ_SECURITY)
+ // GLOB.narcd_underages += H
+ // flick(icon_deny,src)
+ // vend_ready = TRUE
+ // return
var/datum/bank_account/account = C.registered_account
- if(coin_records.Find(R))
- price_to_use = R.custom_premium_price || extra_price
- else if(!hidden_records.Find(R))
- price_to_use = round(price_to_use * get_best_discount(C))
+ if(account.account_job && account.account_job.paycheck_department == payment_department)
+ price_to_use = 0
+ if(coin_records.Find(R) || hidden_records.Find(R))
+ price_to_use = R.custom_premium_price ? R.custom_premium_price : extra_price
if(price_to_use && !account.adjust_money(-price_to_use))
say("You do not possess the funds to purchase [R.name].")
flick(icon_deny,src)
@@ -806,6 +814,8 @@ GLOBAL_LIST_EMPTY(vending_products)
var/datum/bank_account/D = SSeconomy.get_dep_account(payment_department)
if(D)
D.adjust_money(price_to_use)
+ SSblackbox.record_feedback("amount", "vending_spent", price_to_use)
+ //log_econ("[price_to_use] credits were inserted into [src] by [D.account_holder] to buy [R].")
if(last_shopper != usr || purchase_message_cooldown < world.time)
say("Thank you for shopping with [src]!")
purchase_message_cooldown = world.time + 5 SECONDS
diff --git a/code/modules/vending/coffee.dm b/code/modules/vending/coffee.dm
index ab64756868..fd555526c6 100644
--- a/code/modules/vending/coffee.dm
+++ b/code/modules/vending/coffee.dm
@@ -1,16 +1,20 @@
/obj/machinery/vending/coffee
name = "\improper Solar's Best Hot Drinks"
desc = "A vending machine which dispenses hot drinks."
- product_ads = "Have a drink!;Drink up!;It's good for you!;Would you like a hot joe?;I'd kill for some coffee!;The best beans in the galaxy.;Only the finest brew for you.;Mmmm. Nothing like a coffee.;I like coffee, don't you?;Coffee helps you work!;Try some tea.;We hope you like the best!;Try our new chocolate!;Admin conspiracies"
+ product_ads = "Just what you need!;Have a drink!;Drink up!;It's good for you!;Would you like a hot joe?;I'd kill for some coffee!;The best beans in the galaxy.;Only the finest brew for you.;Mmmm. Nothing like a coffee.;I like coffee, don't you?;Coffee helps you work!;Try some tea.;We hope you like the best!;Try our new chocolate!;Admin conspiracies"
icon_state = "coffee"
icon_vend = "coffee-vend"
products = list(/obj/item/reagent_containers/food/drinks/coffee = 25,
/obj/item/reagent_containers/food/drinks/mug/tea = 25,
+ /obj/item/reagent_containers/food/drinks/mug/tea/red = 10,
+ /obj/item/reagent_containers/food/drinks/mug/tea/green = 10,
/obj/item/reagent_containers/food/drinks/mug/coco = 25)
- contraband = list(/obj/item/reagent_containers/food/drinks/ice = 12)
+ contraband = list(/obj/item/reagent_containers/food/drinks/ice = 12,
+ /obj/item/reagent_containers/food/drinks/mug/tea/mush = 3,)
premium = list(/obj/item/reagent_containers/food/condiment/milk = 2,
/obj/item/reagent_containers/food/drinks/bottle/cream = 2,
- /obj/item/reagent_containers/food/condiment/sugar = 1)
+ /obj/item/reagent_containers/food/condiment/sugar = 1,
+ /obj/item/reagent_containers/food/drinks/mug/tea/forest = 3,)
refill_canister = /obj/item/vending_refill/coffee
default_price = PRICE_REALLY_CHEAP
diff --git a/code/modules/vending/kinkmate.dm b/code/modules/vending/kinkmate.dm
index df8a4e8a96..e522583772 100644
--- a/code/modules/vending/kinkmate.dm
+++ b/code/modules/vending/kinkmate.dm
@@ -26,7 +26,12 @@
/obj/item/clothing/under/shorts/polychromic/pantsu = 3,
/obj/item/clothing/under/misc/poly_bottomless = 3,
/obj/item/clothing/under/misc/poly_tanktop = 3,
- /obj/item/clothing/under/misc/poly_tanktop/female = 3
+ /obj/item/clothing/under/misc/poly_tanktop/female = 3,
+ /obj/item/autosurgeon/penis = 3,
+ /obj/item/autosurgeon/testicles = 3,
+ /obj/item/storage/pill_bottle/penis_enlargement = 5,
+ /obj/item/storage/pill_bottle/breast_enlargement = 5,
+ /obj/item/storage/daki = 4
)
contraband = list(
/obj/item/clothing/neck/petcollar/locked = 2,
@@ -36,13 +41,11 @@
/obj/item/clothing/under/misc/keyholesweater = 2,
/obj/item/clothing/under/misc/stripper/mankini = 2,
/obj/item/clothing/under/costume/jabroni = 2,
- /obj/item/dildo/flared/huge = 3,
- /obj/item/reagent_containers/glass/bottle/crocin = 5,
- /obj/item/reagent_containers/glass/bottle/camphor = 5
+ /obj/item/clothing/gloves/evening/black = 2,
+ /obj/item/dildo/flared/huge = 3
)
premium = list(
/obj/item/clothing/accessory/skullcodpiece/fake = 3,
- /obj/item/reagent_containers/glass/bottle/hexacrocin = 10,
/obj/item/clothing/under/pants/chaps = 5
)
refill_canister = /obj/item/vending_refill/kink
diff --git a/code/modules/vending/medical.dm b/code/modules/vending/medical.dm
index 34de3b490c..795d35adc4 100644
--- a/code/modules/vending/medical.dm
+++ b/code/modules/vending/medical.dm
@@ -46,7 +46,8 @@
/obj/item/wrench/medical = 1,
/obj/item/storage/belt/medolier/full = 2,
/obj/item/gun/syringe/dart = 2,
- /obj/item/storage/briefcase/medical = 2)
+ /obj/item/storage/briefcase/medical = 2,
+ /obj/item/plunger/reinforced = 2)
armor = list("melee" = 100, "bullet" = 100, "laser" = 100, "energy" = 100, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 50)
resistance_flags = FIRE_PROOF
diff --git a/code/modules/vending/snack.dm b/code/modules/vending/snack.dm
index 7aef2b627c..ff8fd46676 100644
--- a/code/modules/vending/snack.dm
+++ b/code/modules/vending/snack.dm
@@ -12,7 +12,8 @@
/obj/item/reagent_containers/food/snacks/no_raisin = 5,
/obj/item/reagent_containers/food/snacks/spacetwinkie = 5,
/obj/item/reagent_containers/food/snacks/cheesiehonkers = 5,
- /obj/item/reagent_containers/food/snacks/cornchips = 5)
+ /obj/item/reagent_containers/food/snacks/cornchips = 5,
+ /obj/item/reagent_containers/food/snacks/energybar = 6)
contraband = list(
/obj/item/reagent_containers/food/snacks/cracker = 10,
/obj/item/reagent_containers/food/snacks/honeybar = 5,
diff --git a/code/modules/zombie/items.dm b/code/modules/zombie/items.dm
index cf61e4b210..2cb3a83257 100644
--- a/code/modules/zombie/items.dm
+++ b/code/modules/zombie/items.dm
@@ -12,10 +12,10 @@
var/icon_right = "bloodhand_right"
hitsound = 'sound/hallucinations/growl1.ogg'
force = 18
- sharpness = IS_SHARP_ACCURATE //it's a claw, they're sharp.
+ sharpness = SHARP_POINTY //it's a claw, they're sharp.
damtype = "brute"
total_mass = TOTAL_MASS_HAND_REPLACEMENT
- sharpness = IS_SHARP
+ sharpness = SHARP_EDGED
wound_bonus = -30
bare_wound_bonus = 15
@@ -81,3 +81,14 @@
user.updatehealth()
user.adjustOrganLoss(ORGAN_SLOT_BRAIN, -hp_gained) // Zom Bee gibbers "BRAAAAISNSs!1!"
user.adjust_nutrition(hp_gained, NUTRITION_LEVEL_FULL)
+
+/obj/item/paper/guides/antag/romerol_instructions
+ info = "How to do necromancy with chemicals: \
+
\
+
Use a dropper or syringe (provided) to inject the Romerol (provided) into a target (not provided)
\
+
Wait for said target to die, or speed the process up by doing it yourself
\
+
Run away from the target, as they will be hostile when rising back up
\
+
Optionally: Inject chemical into foods and drinks to further spread possible infection
\
+
???
\
+
Complete assigned objectives amidst the chaos
\
+
"
\ No newline at end of file
diff --git a/config/config.txt b/config/config.txt
index 46f9a0cdc4..a550e6bf8a 100644
--- a/config/config.txt
+++ b/config/config.txt
@@ -4,6 +4,8 @@ $include game_options.txt
$include dbconfig.txt
$include comms.txt
$include antag_rep.txt
+$include resources.txt
+# Cit-specific imports
$include donator_groupings.txt
$include dynamic_config.txt
$include plushies/defines.txt
@@ -431,6 +433,8 @@ AUTOADMIN_RANK Game Master
#CLIENT_WARN_MESSAGE Byond released 511 as the stable release. You can set the framerate your client runs at, which makes the game feel very different and cool. Shortly after its release we will end up using 511 client features and you will be forced to update.
CLIENT_ERROR_VERSION 511
CLIENT_ERROR_MESSAGE Your version of byond is not supported. Please upgrade.
+## The minimum build needed for joining the server, if using 512, a good minimum build would be 1421 as that disables the Middle Mouse Button exploit.
+CLIENT_ERROR_BUILD 1421
## TOPIC RATE LIMITING
## This allows you to limit how many topic calls (clicking on an interface window) the client can do in any given game second and/or game minute.
diff --git a/config/game_options.txt b/config/game_options.txt
index 405ec0405a..a5b0d0b8c4 100644
--- a/config/game_options.txt
+++ b/config/game_options.txt
@@ -116,6 +116,7 @@ CONTINUOUS CHANGELING
CONTINUOUS WIZARD
#CONTINUOUS MONKEY
CONTINUOUS BLOODSUCKER
+CONTINUOUS HERESY
##Note: do not toggle continuous off for these modes, as they have no antagonists and would thus end immediately!
@@ -445,6 +446,7 @@ ROUNDSTART_RACES plasmaman
#ROUNDSTART_RACES shadow
ROUNDSTART_RACES felinid
ROUNDSTART_RACES dwarf
+ROUNDSTART_RACES ethereal
## Races that are better than humans in some ways, but worse in others
#ROUNDSTART_RACES jelly
diff --git a/config/policy.json b/config/policy.json
new file mode 100644
index 0000000000..9e26dfeeb6
--- /dev/null
+++ b/config/policy.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/config/resources.txt b/config/resources.txt
new file mode 100644
index 0000000000..909bef0809
--- /dev/null
+++ b/config/resources.txt
@@ -0,0 +1,38 @@
+# External resources
+# Set this to the location of a .zip with the server's .rsc inside of it.
+# If you set this mutiple times, the server will rotate between the links.
+# To use this, the compile option PRELOAD_RSC must be set to 0 to keep byond from preloading resources
+
+EXTERNAL_RSC_URLS http://tgstation13.download/byond/tgstationv2.zip
+
+
+########################
+# Browser Asset Config #
+########################
+# Browser assets are any file included in interfaces. css, images, javascript, etc.
+# This handles configuring how we get these to the player so interfaces can access them.
+
+# Asset Transport
+# The normal way of getting assets to clients is to use the internal byond system. This can be slow and delay the opening of interface windows. It also doesn't allow the internal IE windows byond uses to cache anything.
+# You can instead have the server save them to a website via a folder within the game server that the web server can read. This could be a simple webserver or something backed by a CDN.
+# Valid values: simple, webroot. Simple is the default.
+#ASSET_TRANSPORT webroot
+
+
+# Simple asset transport configurable values.
+
+# Uncomment this to have the server passively send all browser assets to each client in the background. (instead of waiting for them to be needed)
+# This should be uncommented in production and commented in development
+#ASSET_SIMPLE_PRELOAD
+
+
+# Webroot asset transport configurable values.
+
+# Local folder to save assets to.
+# Assets will be saved in the format of asset.MD5HASH.EXT or in namespaces/hash/ as ASSET_FILE_NAME or asset.MD5HASH.EXT
+#ASSET_CDN_WEBROOT data/asset-store/
+
+# URL the folder from above can be accessed from.
+# for best results the webserver powering this should return a long cache validity time, as all assets sent via this transport use hash based urls
+# if you want to test this locally, you simpily run the `localhost-asset-webroot-server.py` python3 script to host assets stored in `data/asset-store/` via http://localhost:58715/
+#ASSET_CDN_URL http://localhost:58715/
diff --git a/config/spaceRuinBlacklist.txt b/config/spaceRuinBlacklist.txt
index 90682f5bad..969e4135f6 100644
--- a/config/spaceRuinBlacklist.txt
+++ b/config/spaceRuinBlacklist.txt
@@ -52,3 +52,4 @@
#_maps/RandomRuins/SpaceRuins/arcade.dmm
#_maps/RandomRuins/SpaceRuins/spacehermit.dmm
#_maps/RandomRuins/SpaceRuins/advancedlab.dmm
+#_maps/RandomRuins/SpaceRuins/spacediner.dmm
diff --git a/html/panels.css b/html/admin/panels.css
similarity index 97%
rename from html/panels.css
rename to html/admin/panels.css
index 1f969c690e..22373ee0ca 100644
--- a/html/panels.css
+++ b/html/admin/panels.css
@@ -1,10 +1,10 @@
-body {padding:0px;margin:0px;}
-#top {position:fixed;top:5px;left:10%;width:80%;text-align:center;background-color:#fff;border:2px solid #ccc;}
-#main {position:relative;top:10px;left:3%;width:96%;text-align:center;z-index:0;}
-#searchable {table-layout:fixed;width:100%;text-align:center;"#f4f4f4";}
-tr.norm {background-color:#f4f4f4;}
-tr.title {background-color:#ccc;}
-tr.alt {background-color:#e7e7e7;}
-.small {font-size:80%;}
-a {text-decoration:none;}
-a:hover {color:#d3d;}
+body {padding:0px;margin:0px;}
+#top {position:fixed;top:5px;left:10%;width:80%;text-align:center;background-color:#fff;border:2px solid #ccc;}
+#main {position:relative;top:10px;left:3%;width:96%;text-align:center;z-index:0;}
+#searchable {table-layout:fixed;width:100%;text-align:center;"#f4f4f4";}
+tr.norm {background-color:#f4f4f4;}
+tr.title {background-color:#ccc;}
+tr.alt {background-color:#e7e7e7;}
+.small {font-size:80%;}
+a {text-decoration:none;}
+a:hover {color:#d3d;}
diff --git a/html/search.js b/html/admin/search.js
similarity index 100%
rename from html/search.js
rename to html/admin/search.js
diff --git a/html/changelog.html b/html/changelog.html
index b72721fcdd..550e911e4a 100644
--- a/html/changelog.html
+++ b/html/changelog.html
@@ -50,6 +50,923 @@
-->
+
17 September 2020
+
DeltaFire15 updated:
+
+
Failing the plushmium reaction can now create peculiar plushies, depending on reaction volume.
+
The mood-buff from petting a plushie now works properly again.
+
Fixed wacky necropolis loot chest behavior
+
+
EmeraldSundisk updated:
+
+
Adds the Research Director's office to Omega Station
+
Adds 2 new solar arrays (and control rooms)
+
Adds some action figures that weren't there previously
+
The CMO's office now has a light switch
+
Slight readjustments to impacted areas
+
Readjusts the toxins air supply line to (ideally) be easier to service
+
Department camera consoles should now be able to actually check appropriate cameras
+
Xenobiology can now be locked down (by the Research Director)
+
+
MrJWhit updated:
+
+
Adds a brain damage line
+
+
Putnam3145 updated:
+
+
Your balls finally feel full, again.
+
+
timothyteakettle updated:
+
+
due to changes in policy, and several lawsuits, Nanotrasen has been forced to allow disabled people to sign up
+
+
+
16 September 2020
+
timothyteakettle updated:
+
+
fixed an icon path
+
+
+
12 September 2020
+
BlueWildrose updated:
+
+
The Polychromic winter coat's hoodie will now polychrome, alongside any other new polychromic items with toggleable headwear.
+
Minesweeper will no longer blow up the player's ears when they select "Play on the same board"
+
Slimepeople will find warm donk pockets among other toxin healing items even more repulsive, as they are anti-toxic.
+
Slimepeople are now neutral to gross foods.
+
+
DeltaFire15 updated:
+
+
AIs can no longer qdel() the gravity generator
+
+
Putnam3145 updated:
+
+
Added some unit tests for reactions.
+
replaced handle_changeling and handle_bloodsucker with signal registration
+
+
Sonic121x updated:
+
+
Fixed pill button on chemical press
+
+
TheObserver-sys updated:
+
+
Brass now has a proper datum. Aspiring Forgetenders rejoice!
+
+
Trilbyspaceclone updated:
+
+
Race based drinks will no longer stay inside your blood for ever.
+
+
Tupinambis updated:
+
+
Redid Cogstation atmos pipes to make it less cluttered.
+
Removed a few doors from the main hallway to mitigate chokepoint issues
+
All belt hell conveyers are now on by default, so that belt hell actually works.
+
IDs for poddoors and belts and the like. Everything is now properly hooked and should work as expected (except for the pressure triggered mass drivers)
+
addresses most if not all roundstart active turfs.
+
Issue where wires were connected to the SMES improperly, and SMES were not properly precharged, resulting in power failure earlier than intended.
+
various rogue turfs and wirings.
+
security office APC being hooked to maintenance for some reason.
+
TEG is now directly wired to the SMES.
+
adds a subtype of mass drivers that is triggered by things being on it. TODO: Make these mass drivers trigger poddoors, to make belt hell fully functional.
+
+
lolman360 updated:
+
+
glaives now work again
+
+
zeroisthebiggay updated:
+
+
Revolver is now poplocked down to fifteen people.
+
+
+
11 September 2020
+
Putnam3145 updated:
+
+
Superconducting turfs now can't go below 0 celsius.
+
+
+
09 September 2020
+
Putnam3145 updated:
+
+
Made superconductivity work for the first time literally ever.
+
+
timothyteakettle updated:
+
+
accents work better
+
+
+
08 September 2020
+
Ghommie updated:
+
+
fixed names of the Electrical Toolbox goodie pack and green croptop christmas suit.
+
Fixed turf visuals for real. Original PR by AnturK on tgstation.
+
+
KeRSedChaplain updated:
+
+
added borg_deathsound.ogg and android_scream.ogg
+
+
silicons updated:
+
+
meteor waves now have a static 5 minute timer.
+
+
+
07 September 2020
+
DeltaFire15 updated:
+
+
fixed a typo causing a span not to show.
+
+
Putnam for debugging ammo loading! Thx!! updated:
+
+
Removed spell blade, diamond picaxe, and fire wand from lava land loot tables
+
Each mini boss now has its own type of crate.
+
Each spike has its own type of crate that it pulls its now smaller loot table from
+
Moved god eye from spike loot table to hard spawn collosses fight
+
Moved holoparasight from spike loot table to bubble gum
+
Moved magic meat hook from spike loot table to drake
+
2 more crates to Arena Shuttle as well as 4-4-4 of each new type of chest
+
Replaced the diamond pick loot with a better one
+
Replaced the cursted katana with a non cursted verson that deals half the damage and has less block!
+
Three new potions, blue heals the mind like a mama potion, Green heals the soul aka the organs! Lastly Red heals the body, by 100 damage of each main types. Best not to waste them!
+
Four more "wands" Spellbooks! These fun little guys shoot out its own pages to do the affect, one will set the target on fire like a bullet, one will harm them a bit, one will heal the target a small bit - How nice! Last one will give them a few statis affects like a taser bolt but without as much power or tasing affect
+
+
+
06 September 2020
+
Putnam3145 updated:
+
+
Dynamic no longer pushes events.
+
Made spontaneous brain trauma a good deal less annoying.
+
+
lolman360, NecromancerAnne updated:
+
+
The ancient art of blacksmithing, now in pixels.
+
cool swords and shit (thanks anne)
+
+
raspy-on-osu updated:
+
+
thermomachine examine text
+
+
+
05 September 2020
+
Bhijn updated:
+
+
Readded the old method of temperature notifications in the form of a new pair of shivering/sweating notifications.
+
+
Putnam3145 updated:
+
+
Hilbert hotel flavor text for one particular snowflake hotel changed.
+
admins can now actually reduce threat level in dynamic
+
Made owo.ogg smaller.
+
Character saving unit test is now more verbose on failure.
+
Added an extools proc hook alternative to rust_g logging.
+
+
raspy-on-osu updated:
+
+
supermatter shard examine text
+
protolathe item categories
+
+
+
04 September 2020
+
timothyteakettle updated:
+
+
ipcs can speak
+
+
+
03 September 2020
+
Ghommie updated:
+
+
Jaunters should now work with magic mirror chasming.
+
The photocopier can now print more than one copy at a time.
+
Alkali perspiration infos don't crash the Pandemic UI anymore.
+
Windoors can be actually tinted now.
+
+
+
02 September 2020
+
Putnam3145 updated:
+
+
Added a unit test for character saving.
+
Plastitanium rapier no longer silently sleeps with no chance at counterplay when used by pacifists.
+
Fusion scan is now actually useful.
+
+
Tupinambis updated:
+
+
moved the dakis, genital growth pills, and genital autosurgeons out of the maintenance loot table and into kinkmates.
+
+
raspy-on-osu updated:
+
+
pyroclastic anomaly client spam
+
+
timothyteakettle updated:
+
+
you can hide your ckey now from the roundend report
+
+
+
01 September 2020
+
BlueWildrose updated:
+
+
fixed slimes starting off hungry
+
+
+
31 August 2020
+
Arturlang updated:
+
+
Slimes can now damage structures, don't leave them unfed!
+
+
Chiirno updated:
+
+
Moves pill_bottles/dice to box/dice on CogStation.
+
+
Couls, ported by NecromancerAnne updated:
+
+
cleans up mech backstabbing code
+
+
DeltaFire15 updated:
+
+
teleport-to-ark ability of the eminence, commented out
+
teleport-to-obelisk ability for the eminence
+
+
Detective-Google updated:
+
+
plasmamen have no more slowdown
+
object reskins now use very nice and cool radials
+
+
EmeraldSundisk updated:
+
+
Adds a pool to MetaStation
+
Slight readjustments to the surrounding area
+
Fixes a handful of external airlocks
+
+
ForrestWick updated:
+
+
removes wall walking boots from nukie uplink
+
+
Ghommie updated:
+
+
e-gun overlays and some floor decals should have been fixed.
+
+
LetterN updated:
+
+
tgchat
+
+
Lynxless updated:
+
+
Changed anatomic panacea into a direct buff, instead of a chem injection
+
Changed the values of anatomic panacea
+
Added a new icon for panacea's buff alert
+
+
Putnam3145 updated:
+
+
Pref for genital/vore examine text
+
Fixed a couple events having ghost roles eligible.
+
Buffed slaughter demon: gets stronger as it eats people
+
Nerfed slaughter demon: no longer permanently round-removes all who are eaten by it, instead releasing their now-heartless bodies
+
Dynamic storytellers now calculate property weights properly.
+
+
Sonic121x updated:
+
+
Fix the four type of new tea that will stuck inside your vein.
+
drinking glass sprite for those tea.
+
+
kappa-sama updated:
+
+
miners can no longer acquire funny antag item
+
+
lolman360 updated:
+
+
shuttle engine/heater sprites now face the right way
+
+
raspy-on-osu updated:
+
+
TEG power output
+
tesla movement priorities
+
tesla counterplay
+
tesla containment check (containment variable now usable)
+
+
silicons updated:
+
+
brooms now sweep objects on MOVABLE_PRE_MOVE rather than MOVABLE_MOVED
+
firedoors no longer automatically open on touch when there's no pressure differences.
+
+
timothyteakettle updated:
+
+
buzz, buzz2 and ping are now all unrestricted emotes and can be used by anyone
+
the drake credit and pickle credit sprites have been removed
+
tongue speech handling is now done by accent datums
+
+
zeroisthebiggay updated:
+
+
waffleco
+
+
+
30 August 2020
+
raspy-on-osu updated:
+
+
new explosion echoes
+
explosion echo range
+
5 new explosion related sounds
+
+
+
28 August 2020
+
EmeraldSundisk updated:
+
+
Adds more paper to the library
+
The law office now has a desk window
+
Expands most of CogStation's exterior airlocks. Slightly adjusts surrounding areas to accommodate this.
+
Updates some of CogStation's paperwork
+
The rat in the morgue turned themselves into a possum. Funniest shit I've ever seen.
+
Adjusts some area designations so cameras should receive power properly
+
Cleans up an errant decal
+
+
Hatterhat updated:
+
+
Traitor holoparasites can now only be bought once, because apparently you can only have one active holopara.
+
PDA bombs can now only be bought once per uplink.
+
+
lolman360 updated:
+
+
atmos = radiation = chemistry.
+
+
shellspeed1 updated:
+
+
Adds slow mode for iv drips
+
+
timothyteakettle updated:
+
+
an ancient game over a thousand years old has re-emerged among crewmembers - rock paper scissors
+
customization features appear in alphabetical order where necessary
+
bokken do two more stamina damage now
+
you can now choose a body sprite as an anthromorph or anthromorphic insect, and can choose from aquatic/avian and apid respectively (and obviously back to the defaults too)
+
+
+
27 August 2020
+
silicons updated:
+
+
eyebeam lighting can only have 128 maximum HSV saturation now.
+
no more shotgun stripper clips in boxes.
+
goliath tentacles now do 20 damage to mechs at 25% ap
+
+
timothyteakettle updated:
+
+
changing your character's gender won't randomize its hairstyle and facial hairstyle now
+
+
+
26 August 2020
+
ancientpower updated:
+
+
Ghosts can read newscasters by clicking on them.
+
+
silicons updated:
+
+
hierophant vortex blasts now have 50% armor penetration vs mecha
+
ventcrawling now kicks off every attached/buckled mob, even for non humans.
+
+
+
25 August 2020
+
Hatterhat updated:
+
+
Insidious combat gloves have been replaced by insidious guerilla gloves. They're generally the same, except now you can tackle with them.
+
+
Literallynotpickles updated:
+
+
You can now equip handheld crew monitors on all medical-related winter coats.
+
+
Putnam3145 updated:
+
+
vore now ejects occupants on death
+
+
raspy-on-osu updated:
+
+
Thermoelectric Generator power output
+
+
timothyteakettle updated:
+
+
I.P.Cs now short their circuits when expressing emotion, causing sparks to appear around them.
+
+
+
24 August 2020
+
MrJWhit updated:
+
+
Fixes areas on expanded airlocks
+
+
silicons updated:
+
+
wormhole jaunters work
+
wormhole jaunters no longer get interference from bags of holding
+
airlocks now only shock on pulse/wirecutters instead of on tgui panel open.
+
+
timothyteakettle updated:
+
+
three new items are in the loadout for all donators
+
+
zeroisthebiggay updated:
+
+
contraband black evening gloves in kinkvend
+
+
+
23 August 2020
+
DeltaFire15 updated:
+
+
silicons and clockies can now access APCs properly
+
+
EmeraldSundisk updated:
+
+
Medbay now has a smartfridge for organ storage
+
Slight enhancements to the station's electrical wiring layout
+
Very small library renovation
+
Exterior airlocks have been given proper air systems for safety's sake
+
+
Ghommie updated:
+
+
Stops shielded hardsuits from slowly turning the wearer into a big glowing ball of stacked energy shield overlays.
+
the shielding overlay is merely visual as result. Aim your clicks.
+
+
Ludox235 updated:
+
+
no more 10 pop xenos (25pop now)
+
+
MrJWhit updated:
+
+
Increases the majority of airlocks by 1 tile.
+
Minor adjustments to the TEG engine.
+
+
Putnam3145 updated:
+
+
Simplemobs no longer count in dynamic.
+
"Story" storyteller no longer starts at a ludicrously low threat, always.
+
Blob threat now scales with coverage.
+
One person with their pref on no longer overpowers 40 people who might not even know there is one.
+
Negative-weight rulesets are no longer put into the list.
+
+
kiwedespars updated:
+
+
removed durathread from armwraps recipe.
+
+
lolman360 updated:
+
+
breath mask balaclava
+
+
timothyteakettle updated:
+
+
lizards are now a recommended species for mam snouts
+
+
zeroisthebiggay updated:
+
+
new sprites for the temporal katana
+
suiciding with the temporal katana omae wa mou shinderius you into the shadow realm
+
twilight isnt earrape
+
+
+
22 August 2020
+
Time-Green (copypasta'd by lolman360) updated:
+
+
plumbing
+
automatic hydro trays
+
+
+
21 August 2020
+
LetterN updated:
+
+
Updates and adds some of the tips
+
+
Putnam3145 updated:
+
+
added reftracking as a compile flag
+
+
SmArtKar updated:
+
+
RSD limitation is now 500 tiles
+
Fixed broken RSD sprites
+
Removed that shuttle limit
+
+
timothyteakettle updated:
+
+
two snouts can once again be chosen in customization
+
lizard snouts work again
+
+
+
20 August 2020
+
DeltaFire15 updated:
+
+
The cooking oil damage formula is no longer scuffed.
+
Changed the clockie help-link to lead to our own wiki.
+
+
Fikou updated:
+
+
admins can now do html in ahelps properly
+
+
Hatterhat updated:
+
+
Pirate threats are now announced as "business propositions", and their arrivals are now also announced properly.
+
+
tiramisuapimancer updated:
+
+
Ethereal hair is now their body color instead of accidentally white
+
+
+
18 August 2020
+
DeltaFire15 updated:
+
+
kindle cast time: 15ds -> 25ds
+
Moved the Belligerent Scripture to where it should be in the code
+
+
Detective-Google updated:
+
+
glass floors
+
uncrowbarrable plasma floors tweak:disco inferno's plasma floors can no longer be crowbarred.
+
ghost cafe has funky fresh art
+
you can actually remove glass floors now
+
get_equipped_items is hopefully less gross
+
plasma cutters are no longer gay
+
+
Hatterhat updated:
+
+
Slaughter demons (and laughter demons, being a subtype) are MOB_SIZE_LARGE, with one of the more immediate effects being able to mark them with a crusher and backstab them.
+
The funny blyat men have stumbled upon another surplus of Mosin-Nagants and are starting to pack them into crates again.
+
Vehicle riders can now, by default, get shot in the face and/or chest.
+
Adminspawn only .357 DumDum rounds! Because sometimes the other guy just really needs to hurt.
+
Bluespace beakers now have a chemical window through the side that shows chemical overlays.
+
Plant DNA manipulators now let you chuck things over them. Or they WOULD, if LETPASSTHROW worked half a damn.
+
+
LetterN updated:
+
+
uplink implant states
+
tweaks how role assigning works
+
+
MrJWhit updated:
+
+
Gives ashwalkers nightvision
+
Makes tesla blast people, not the environment, to save the server.
+
+
TheObserver-sys updated:
+
+
moves Garlic sprites from growing.dmi to growing_vegetable.dmi
+
Removes the unused Electric Lime mutation, it just takes up space with no actual function nor sprites.
+
Gives Catnip growing sprites
+
Removes redundant images in growing.dmi
+
+
kiwedespars updated:
+
+
10 force to a fucking rubber cock.
+
+
lolman360 updated:
+
+
shotgun stripper clip nerf. ammoboxes can now accept a load_delay that happens when they attack a magazine, internal or external.
+
+
ported from tg updated:
+
+
bronze airlocks and windows can now be built
+
i also tweaked bronze flooring to be cheaper.
+
+
silicons updated:
+
+
stamina draining projectiles without stamina for their primary damage type now has their stamina damage taken into account for shield blocking, rather than the block being done for free for that.
+
+
timothyteakettle updated:
+
+
snowflake code tidyup
+
snowflake code for mutant bodypart selection has been rewritten to be ~14x shorter
+
meat type and horns can now be selected by any species
+
+
+
17 August 2020
+
DeltaFire15 updated:
+
+
Cogscarabs are no longer always Pogscarabs
+
+
Strazyplus updated:
+
+
Added drakeborgs
+
Added drakeplushies
+
added drakeborg sprites
+
added drakeplushie sprites
+
changed some code - added drakeplushies to backpack loadout Removed duplicate voresleeper belly sprites from engdrake & jantidrake. [CC BY-NC-SA 3.0](https://creativecommons.org/licenses/by-nc-sa/3.0/)
+
Added CC BY-NC-SA 3.0 license details to icon/mob/cyborg moved drakeborg.dmi to icon/mob/cyborg
+
+
+
16 August 2020
+
kiwedespars updated:
+
+
nerfed hypereut chaplain weapon.
+
50% rng blockchance -> 0%
+
parry made much worse because it's an actual weapon and a roundstart one at that.
+
+
zeroisthebiggay updated:
+
+
tips
+
+
+
15 August 2020
+
LetterN updated:
+
+
missing anomaly core icons
+
wrong state. blame the tg vertion i copied
+
+
silicons updated:
+
+
the 8 rotation limit from clockwork chairs has been removed. please don't abuse this.
The temporal katana is now slightly more worthy of the 2 spell point cost, with a smaller, antimagic respecting timestop, less force, and no random blockchance. Society has progressed past the need for blockchance.
+
+
LetterN updated:
+
+
Mafia Component
+
Fixed missing icons and handtele
+
+
Putnam3145 updated:
+
+
a whole lot of jank regarding funny part sprite display.
+
+
Toriate updated:
+
+
Opossums have migrated into the maintenance tunnels! Seek them out at your own peril!
+
+
ancientpower updated:
+
+
Doors added to the west side of box medbay to make things a bit more manageable.
+
+
kappa-sama updated:
+
+
smuggler satchel cost 2->1
+
radio jammer cost 5->2
+
smuggler satchel uplink description now implies that persistence is disabled
+
+
lolman360 updated:
+
+
renameable necklace (accessory, attaches to suit) and ring (glove slot.)
+
custom rename is now 2048 characters? i think it's characters.
+
+
silicons updated:
+
+
You can now use anything as an emoji by doing :/obj/item/path/to/item:. This works for any /atom or subtype.
+
+
timothyteakettle updated:
+
+
syndicate agents now have access to mechanical aim enhancers which allow them to aim bullets to bounce off walls
+
ricochets work properly now for the bullets that support them
+
+
zeroisthebiggay updated:
+
+
hair and some sechuds
+
ce hardsuit radproofing
+
+
+
11 August 2020
+
Hatterhat updated:
+
+
PDA uplinks can now steal from pens. Properly. Just make sure to have a pen in your PDA, first.
+
+
kappa-sama updated:
+
+
tracer no longer gives you full stamheals per use
+
+
zeroisthebiggay updated:
+
+
volaju two
+
+
+
10 August 2020
+
Hatterhat updated:
+
+
Parry counterattack text now shows up.
+
Sterilized gauze is now better at stopping bleeding, and applies slightly faster. Very slightly faster.
+
Ointment and sutures now hold more in a stack (12 and 15, respectively).
+
Sterilized gauze can now be made by just pouring 10u sterilizine onto standard medical gauze, instead of having to craft it. Why you had to craft it, I will honestly never know.
+
Proto-kinetic glaives are more expensive, stagger/cooldown on failed parries increased slightly, perfect parries required for counterattack.
+
New item: Temporal Katana. 2 points for wizards, timestops upon successful parry, bokken quickparry stats (100 force on melee counter!).
+
Also you can *smirk. This has no mechanical effect, other than being smug.
+
+
KeRSedChaplain updated:
+
+
Added a guide for romerol usage
+
made infectious zombies not enter softcrit and take no stamina damage
+
+
LetterN updated:
+
+
clocktheme color
+
Ports TGUI-4
+
+
Lynxless updated:
+
+
Ports TG #51879
+
+
Owai-Seek updated:
+
+
Meatballs now spawn raw from food processors.
+
+
Putnam3145 updated:
+
+
Ethereals
+
(Hexa)crocin
+
(Hexa)camphor
+
Tweaked wording for marking tickets IC issue.
+
Rerolling your traitor goals will ONLY give you "proper" objectives.
+
+
Seris02 updated:
+
+
borgs being able to select and use a module when it's too damaged
+
+
Sishen1542 updated:
+
+
gave chairs active block/parry in exchange for removal of block_chance
+
replaces box whiteship tbaton with truncheon
+
+
kappa-sama updated:
+
+
made the Dirty Magazines crate cost 4000 instead of 12000 credits
+
MODS I SPILLED MU JUICE HEJPPHRLP HELPJ JLEP HELP
+
+
silicons updated:
+
+
player made areas are no longer valid for malf hacking
+
default space levels is 4 again.
+
rats now swarm instead of stacking on one spot.
+
getting hit by an explosion will now barely hard knockdown, but will leave you somewhat winded.
+
+
timothyteakettle updated:
+
+
speech verbs copy through dna copying now
+
+
+
09 August 2020
+
Hatterhat updated:
+
+
Proto-kinetic glaives (not crushers) can parry now.
+
+
MrJWhit updated:
+
+
Adds a second shutter on the top of the hop line
+
+
silicons updated:
+
+
immovable rods no longer drop down chasms
+
fun removal: squeaking objects now have an 1 second cooldown between squeaks, and will have a 33% chance of interrupting any other squeaking object when Cross()ing, meaning no more ear-fuck conveyor belts.
+
+
+
08 August 2020
+
DeltaFire15 updated:
+
+
Roundstart cultists now start with a replica fabricator - no brass though, make your own.
+
Kindle cast time: 10 > 15, mute after stun end: 2 > 5, slur after mute end: 3 > 5
+
The ratvarian spear no longer adds negative vitality under very specific circumstances.
+
The Ratvarian Spear can parry now! Short parries with low leeway, but low cooldown.
+
The brass claw, a implant-based weapon which gains combo on consecutive hits against the same target.
+
The sigil of rites, a sigil used to perform various rites with a cost of power and materials
+
The Rite of Advancement: Used to add a organ or cyberimplant to a clockie without need for surgery.
+
The Rite of Woundmending: Used to heal all wounds on another cultist, causing toxins damage in return.
+
The Rite of the Claw: Used to summon a brass claw implant. Maximum of 4 uses per round.
+
+
Hatterhat updated:
+
+
You can now buy a toolbox's worth of Mosin-Nagant ammo for a fairly discounted price.
+
Revolvers from the dedicated kit now have reskinning capabilities.
+
You can now actually buy the riflery primer, which lets you pump shotguns and work the Mosin's bolt faster.
+
Bulldog slug magazines now have a unique sprite.
+
+
Ludox235 updated:
+
+
Removed an abductee objective that told you to remove all oxygen.
+
Added a new abductee objective to replace the removed one.
+
+
Sishen1542 updated:
+
+
🅱ï¸oneless
+
squishy slime emotes
+
+
timothyteakettle updated:
+
+
heparin makes you bleed half as much now
+
cuts make you bleed 25% less now
+
more items in the loadout and loadout has subcategories now for easier searching
+
+
+
07 August 2020
+
dapnee updated:
+
+
fixed active tufs on some space ruins, murderdome VR, and a few on pubby, changed cargo autolathe to techfab, messed with pipe room leading to monastery.
+
+
lolman360 updated:
+
+
vendors are now unanchored when tipped. it just fell over it's not bolted to the ground anymore.
+
podpeople no fat when sunbathing.
+
+
silicons updated:
+
+
explosions only recurse one level into storage before dropping 1 level per storage layer.
+
volumetric storage is now minimum 16 pixels per item because 8 was ridiculous
+
shieldbash balanace --> balance
+
attempting to send too long of an emote will now reflect it back to you instead of cutting it off and discarding the overflow.
+
holoparasites can now play music
+
lethal blood now causes damaging bleeding instead of outright gibbing
+
+
+
06 August 2020
+
Auris456852 updated:
+
+
Added B.O.O.P. Remote Control cartridges to the PTech.
+
+
Hatterhat updated:
+
+
Proto-kinetic glaives! Essentially a proto-kinetic crusher with a different blade, handguard, and goliath hide grip. Expensive, but elegant.
+
Door charges no longer knock people out.
+
+
Ludox235 updated:
+
+
You can now buy damaged AI upload modules in the traitor's uplink.
+
+
Seris02 updated:
+
+
fixed ghost chilis
+
+
Trilbyspaceclone updated:
+
+
4 New blends of tea have been shipped to the station, and how to make them has been leaked!
+
+
b1tt3r1n0 updated:
+
+
Added the warp implant
+
+
dapnee updated:
+
+
added a hallway to telecoms for engineers to get there on meta
+
+
kappa-sama updated:
+
+
dildo circuit assemblies
+
+
lolman360 updated:
+
+
The Tendril-Mother on Lavaland has remembered how to make ashwalkers who know how to speak Draconic again.
+
+
timothyteakettle updated:
+
+
nanotrasen has decided to fire all disabled members of the security division and confiscate certain sentimental items from doctors
+
the custom tongue preference now passes through cloning so you spawn with your selected tongue
+
several changes to travelling traders so they look better and spawn slightly less often
+
+
zeroisthebiggay updated:
+
+
nukies can buy holoparasites
+
+
+
04 August 2020
+
Seris02 updated:
+
+
lizard spines
+
+
timothyteakettle updated:
+
+
due to further advancements in medical technology, you can now have holes poked into your body for fun and enjoyment
+
+
zeroisthebiggay updated:
+
+
prefs for headpat wagging
+
+
+
03 August 2020
+
KeRSedChaplain updated:
+
+
fixed clockwork guardians being able to reflect ranged weapons
+
+
Linzolle updated:
+
+
uv penlight no longer invisible
+
+
dapnee updated:
+
+
active turfs on box and xenohive, maintenance bar APC not being stringed correctly, turned a monitor to face a direction that makes sense, changed tag of camera in gravgen being misnamed
+
+
silicons updated:
+
+
shoves have been buffed to apply a status effect rather than a 0.85 movespeed modifier, meaning repeatedly shoving someone now renews the debuff
+
shoves now stagger for 3.5 seconds.
+
war operatives now actually time 20 minutes since roundstart to depart instead of 15.
+
explosive stand bombs can now be examined from any distance
+
explosive stand bombs are now a component.
+
+
02 August 2020
Auris456852 updated:
@@ -473,760 +1390,6 @@
martial arts twenty minpop
records
-
-
17 July 2020
-
ShizCalev, Fikou updated:
-
-
Added some sanity checking for varedit values.
-
Fixed an exploit involving coins and mints that could crash the server.
-
Fixed an exploit that would allow you to destroy round-critical / indestructible items with folders.
-
Swarmers can no longer cut power lines by deconstructing catwalks underneath them.
-
Fixed a scenario that allowed infinite resource generation via ore machines.
-
you can no longer inject html in ahelps
-
you cant either, jannies
-
-
timothyteakettle updated:
-
-
fixes a small pickle related issue
-
recent culinary and scientific advancements have brought forth new pickle related technologies
-
-
-
16 July 2020
-
DeltaFire15 updated:
-
-
Fixes a zeolite runtime caused by a missing check.
-
-
Sneakyrat6 updated:
-
-
Fixes being able to meta people real name with OOC Notes
-
-
timothyteakettle updated:
-
-
travelling traders from another dimension can now visit the station in search of something specific, and reward you for giving it to them
-
small error with pet carrier logic fixed and also making sure simple mobs are catered for properly inside bluespace jars
-
fixes coin related issue
-
-
-
15 July 2020
-
Sonic121x updated:
-
-
Paramedic jumpsuit
-
-
-
14 July 2020
-
silicons updated:
-
-
chemical reactions now are sorted by priority first and temperature second.
-
sec and medical records have been added to character setup.
-
circuit reagent heaters are now sanitized for temperature from 2.7 to 1000.
-
-
timothyteakettle updated:
-
-
ports a money bag exploit
-
-
-
13 July 2020
-
Linzolle updated:
-
-
you can no longer vore and digest people regardless of vore preferences
-
-
Owai-Seek updated:
-
-
Trashbags can now hold most shoes, and organs.
-
You can no longer nest nuke disks or hold brains in the trash.
-
-
-
12 July 2020
-
DeltaFire15 updated:
-
-
Sentinels compromise now heals augmented bodyparts.
-
-
EmeraldSundisk updated:
-
-
Adds turnstiles to CogStation's security wing
-
Readds robotics to the Corpse Disposal Network
-
Readds chemistry's ability to send items directly to the experimentation lab
-
Visual renovation and slight adjustments to CogStation's robotics lab
-
Slight visual adjustments elsewhere (the library)
-
CogStation's mail and disposal pipes are once again complete
-
CogStation's robotics lab now has spawners, lights, and other room essentials
-
-
HeroWithYay updated:
-
-
Added Telecrystal Dust
-
Telecrystals can be sold at cargo
-
-
LetterN updated:
-
-
Added d[thing] emojis
-
bye xss
-
-
MrJWhit updated:
-
-
Removes northern tunnel to the monastery on Pubby
-
-
Yakumo Chen updated:
-
-
Adds a wedding crate to cargo full of wedding attire.
-
-
kappa-sama updated:
-
-
wisdom cow is half as common and is wise enough to lag the server 66% less
-
-
silicons updated:
-
-
policy configuration added, plus support hooks for assisting enforcement of clone memory disorder. logging: added logging of revival by defib, cloning, strangereagent, and revival surgery
-
You can now "audibly emote" by having ! at the start of a sentence.
-
-
timothyteakettle updated:
-
-
due to recent biological advancements, you can now make eye contact with people.
-
-
zeroisthebiggay updated:
-
-
a singular stray pixel
-
-
-
11 July 2020
-
Putnam3145 updated:
-
-
Gas mixtures now live entirely in a DLL.
-
-
-
10 July 2020
-
Chiirno updated:
-
-
Gave jellypeople a unique brain object /obj/item/organ/brain/jelly
-
added an icon for jellypeople brains.
-
-
EmeraldSundisk updated:
-
-
Adds a pool to PubbyStation
-
Slight adjustments to the surrounding area as to fit said pool
-
-
Sneakyrat6 updated:
-
-
Fixes hair falling out of hoodies.
-
-
TheObserver-sys updated:
-
-
Actually adds the juice reagent to make laugh peas donuts.
-
-
-
09 July 2020
-
timothyteakettle updated:
-
-
bluespace tray added, allowing twice as many items as the regular tray, printable at the service lathe, researched through science
-
bluespace jar added, a kind of pet carrier that allows human sized mobs inside, and smashes when thrown, researched and printed through science
-
-
-
08 July 2020
-
DeltaFire15 updated:
-
-
The kill-once objective now works properly.
-
-
EmeraldSundisk updated:
-
-
CogStation now has an apothecary
-
Removes an outdated note on sleepers
-
Readjusts CogStation's chemistry lab
-
Slight area designation adjustments for Robotics
-
The arrivals plaque should be readable now
-
-
Owai-Seek updated:
-
-
Margarine, Chili Cheese Fries.
-
Egg Wraps are now categorized under egg foods.
-
Tuna Sandwich crafting/sprite is now visible.
-
Icons for chicken, cooked chicken, steak, grilled carp, corndogs
-
Icons for chili cheese fries, margarine, BLT sandwich
Fixed the slowdown formula for small character sprites; you guys don't use custom sprite sizes so just ignore these changes.
-
Fixed the "Move it to the threshold" button; it now does what it says.
-
Reworded some text to be clearer.
-
-
-
05 July 2020
-
Ghommie updated:
-
-
You can now actually gain wiring experience from using cable coils.
-
Opening the View Skill Panel shouldn't trigger messages about insufficient admin priviledges anymore.
-
-
Yakumo Chen, kappa-sama updated:
-
-
Removes improvised handguns
-
removed handsaws, improvised gun barrels (you can use atmos pipes again)
-
Guncrafting is less time and resource intensive
-
Item names in guncrafting are user-friendly.
-
-
kappa-sama updated:
-
-
cloth string to replace durathread string
-
durathread string
-
All bows and arrows have had crafting times significantly reduced, coming out at up to 6 times faster crafting speeds. Improvised bows no longer require durathread; instead, they use cloth materials.
-
-
silicons updated:
-
-
active blocking now has a toggle keybind
-
auto bunker override verb has been added
-
shields take 2.5 stam instead of 3.5 stam per second to maintain block
-
Cybernetic implant shields will auto-extend and be used to block if the user has no item to block with
-
-
timothyteakettle updated:
-
-
cooking oil is now far less lethal, requiring a higher volume of the reagent to deal more damage
-
-
-
04 July 2020
-
Sonic121x updated:
-
-
crushed Soldry sodacan
-
digitigrade version of chief medical officer's turtleneck and captain's female formal outfit.
-
-
silicons updated:
-
-
blood_DNA["color"] is now a single variable instead of a list
-
-
-
03 July 2020
-
Arturlang updated:
-
-
You can now toggle hardsuit helmets from the strip menu
-
-
Ghommie updated:
-
-
fixed custom speech/tongue stuff.
-
Lowered shaft miners' paycheck, they have other ways to make cash.
-
You can't (un)equip garments on/from obscured inventory slots anymore.
-
The stamina cost multiplier for swinging melee weapons against mobs has been brought back to 1 from 0.8
-
The stamina cost for throwing mobs now scales with their mob size variable.
-
-
LetterN updated:
-
-
Ported some tags from tgui-3.0 to Vending.js
-
vending icons
-
r&d icons
-
chem master icons
-
-
Onule updated:
-
-
titanium wall man good
-
-
Sonic121x updated:
-
-
Bringback the ChemMaster pill type button.
-
Fix Technode icon.
-
-
bunny232 updated:
-
-
Witchhunter hat no longer obscures mask ears ,eyes, face and mouth
-
-
timothyteakettle updated:
-
-
bloodpacks initialise correctly now
-
-
-
02 July 2020
-
Ghommie updated:
-
-
Fixing a few issues with twohanded items.
-
Unum decks now work correctly.
-
Abductor walls are once again buildable with alien alloy.
-
-
Trilbyspaceclone updated:
-
-
Makes pride and envy ruin a bit smaller!
-
Pride now has rings, lipstick wigs and silver walls/door making a nice and polished look then cyan blue walls.
-
more trash and better dagger placement on food ruin
-
Snowboim now has snowballs and toy gifts for the two skeles daw!
-
Beach boim now has carp light branding beer, as well as soap!
-
Greed ruin now uses nice slick walls and carpet!
-
Founten ruin looks a lot better with its carpets and well maintained fluff things, but walls suffered and no longer can salvage ruined metal...
-
Alien nest has a bit more glowy floors of resin looking a bit more lived in by the drones. As well as the "door" now being see through resin rather then the thicker stuff that you cant see through
-
Pizza party has a few more gifts, some candy and snap pops yay!
-
Sloth ruin is about 15~ tiles shorter, has and has more fruit for a bowl. How lazy!
-
-
silicons updated:
-
-
bohbombing is a thing now
-
-
-
30 June 2020
-
Fikou updated:
-
-
spray cans, airlock painters, and decal painters added to engineering/service/autolathe (where applicable)
-
-
Ghommie updated:
-
-
Fixed a gap on the male insect anthro torso sprite when facing south.
-
Fixed mecha ID access not being removable.
-
Fixed a peeve with the hypno trance status effect not sanitizing some heard hypnosis inputs (i.e. custom say messages like say"honks*clownem ipsum dolor")
-
fixed an issue about using stacks with only 1 amount left.
-
Fixed a peeve on attack messages against carbons/humans.
-
Fixed missing hypnochair board.
-
Fixed material walls and tiles. My bad on that port.
-
-
Ghommie (inspired by MrDoomBringer's work on tgstation) updated:
-
-
New check skills UI.
-
-
Ghommie (porting PRs by XTDM, coiax, MrDoomBringer) updated:
-
-
Random Events now have a follow link for ghosts!
-
Adds the Spontaneous Brain Trauma to the event pool. Sometimes your brain just goes a little wrong.
-
Sometimes a low level cloning pod will make errors in replicating your brain, leaving you with a mild brain trauma.
-
When a person is cloned, any mental traumas are cloned as well.
-
The wizard federation announces that the Curse of Madness is out of beta and is now available for purchase for 4 points. It causes long-lasting brain traumas to all inhabitants of a target space station.
-
The wizard federation declines responsibility for any self-harm caused by curses cast while inside the targeted station.
-
Due to the extensive testing of the Curse of Madness some unique new trauma types have appeared across Nanotrasen-controlled space.
-
Curse of Madness can now be triggered by a wizard's Summon Events, at the same chance as Summon Guns or Summon Magic.
-
When an admin triggers Curse of Madness manually, they can specify their own dark truth to horrify the station with.
-
-
nightred updated:
-
-
Created two_handed component
-
Updated all existing two handed items to use the new component
-
-
silicons updated:
-
-
typing indicators no longer generates duplicate message boxes.
-
config errors now have line numbers.
-
outgoing mentorpms are now blue instead of green for the sender.
-
*squish
-
-
timothyteakettle updated:
-
-
you can now select your tongue and speech verb in the character customization menu!
-
skeleton is now split into two more types, greater and lesser
-
non-carbon blood is now not white
-
fixed a bunch of grammar/spelling mistakes
-
-
-
29 June 2020
-
b1tt3r1n0 updated:
-
-
Made teratomas from sdgf less powergame
-
-
timothyteakettle updated:
-
-
slimes no longer have white blood by default
-
-
-
28 June 2020
-
Detective-Google updated:
-
-
cog is less the suck
-
piggybacking is no longer absolutely inferior
-
-
Ghommie updated:
-
-
Fixing windows interaction with spraycans.
-
Fixing kinetic accelerator guns not working well with gun circuitries.
-
Fixing Zoomba borgs lights overlays.
-
Fixing the "absorb another ling" and "absorb the most dna" objectives rolling when no other changeling is around.
-
Clarified a pet peeve about the spread infestation ability.
-
BEPIS nodes won't show up anymore in the expert mode ui of the r&d console anymore (good thing they weren't researchable).
-
Hopefully fixing sound loop edge cases.
-
Fixing pAI radios being permanently disabled by EMPs at times.
-
Windoors can now be obscured with spraycans just like windows.
-
-
Ghommie porting PRs by Qustinnus/Floyd, Willow, cacogen, nemvar, Ghilker and EOBGames (Inept) updated:
-
-
Fixes a material duplication bug.
-
unique combinations of custom_materials lists are now shared between objects
-
meat material. yes.
-
materials can now be used to build walls/floors. meat house
-
edible component now does not try to attack if you eat something with it
-
Texture support for mat datums with thanks to 4DPlanner!
-
you no longer hit yourself with organs when eating
-
A whole bunch of materials are now datumised! Check out bronze, runed metal, sand, sandstone, snow, paper, cardboard, bone and bamboo. Oh, and pizza. Yes, pizza.
-
Buffs material floor tiles' throwforces from 1 to 10 (same as iron) to better showcase the effect of different materials (e.g. meat vs. titanium)
-
Radioactive items no longer output a single . when examined at a distance
-
-
MrJWhit updated:
-
-
Removed air alarm in Snow Snaxi in Tcomms Sat
-
Removed trash bins in genetics and mining
-
Gives cargo techs a cargolathe
-
-
Putnam3145 updated:
-
-
lost my mind just a couple of times
-
-
b1tt3r1n0 updated:
-
-
pouches, again, and and material pouches.
-
-
timothyteakettle updated:
-
-
support for custom blood colours implemented, slimes blood colour now equivalent to their body colour
-
-
-
27 June 2020
-
Detective-Google updated:
-
-
Lying down is better
-
-
timothyteakettle updated:
-
-
felinids now nya when tabled
-
-
-
26 June 2020
-
Ghommie updated:
-
-
Snore spam.
-
Hostile mobs shouldn't hit their original spawner structures or thoses of the same faction.
-
-
silicons updated:
-
-
soap cleans blood again
-
-
-
25 June 2020
-
Anonymous updated:
-
-
Added kepi and orvilike kepi. Available through loadout.
-
-
Detective Google updated:
-
-
Medigygax
-
-
Detective-Google updated:
-
-
malf AIs can no longer yeet the station while shunted
-
SMESes can now properly use self charging cells
-
ghosts now show up when the round ends
-
away missions
-
-
Funce updated:
-
-
Mentor SQL queries are now deleted properly.
-
-
Linzolle updated:
-
-
analyze function on chem master is no longer broken
-
organs now decay inside dead bodies again
-
-
dapnee updated:
-
-
wataur bottle item
-
wataur bottle and overlay
-
-
-
24 June 2020
-
DeltaFire15 updated:
-
-
Choosing a random item in your uplink will no longer sometimes reroll your contract.
-
Syndicate crate event cannot fire as a random event anymore.
-
-
Detective-Google updated:
-
-
singulos no longer succ infinite rods out of the ice
-
one of the directions for the diag hudpatch was blu instead of orang
-
-
timothyteakettle updated:
-
-
bonfires/grills no longer produce infinite quantities of food
-
slime's alter form ability now updates their hair colour when changing their body colour
-
-
-
22 June 2020
-
Ghommie (porting PRs by zxaber, Ryll-Ryll, AnturK) updated:
-
-
Certain small items purchased through cargo now get grouped into a single box. They also are immune to the 10% private account fee.
-
Added single-order options for several existing products in the Cargo Catalog.
-
Medkit listings are now single-pack items, and considered small items that get grouped into single boxes. Price for medkits is as close to unchanged as is reasonable.
-
You can now beat on vending machines to try and knock loose free stuff! You can also almost kill yourself doing it, so it's your call if your life is worth ten bucks.
-
Cigarette packets now have coupons on the back for small cargo items! Smoking DOES pay!
-
Some single/small items in cargo have been rebranded as goodies, come in lockboxes rather than crates, and can only be purchased with private accounts.
-
-
kevinz000 updated:
-
-
Life() is split into BiologicalLife() and PhysicalLife. A component signal has been added that can prevent either from ticking.
-
-
shellspeed1 updated:
-
-
Adds IV bags.
-
-
-
21 June 2020
-
kevinz000 updated:
-
-
calculations for punch hit chance has been drastically buffed in favor of the attacker.
-
-
-
20 June 2020
-
LetterN updated:
-
-
Asset cache from tg
-
Made the map viewer not look bad
-
Admin matrix right-bracket
-
-
bunny232 updated:
-
-
Removed unsavory things from the vent clog event
-
-
-
19 June 2020
-
Bhijn updated:
-
-
Atmos can no longer become completely bricked
-
-
Funce updated:
-
-
Square root circuit should now actually work.
-
-
SmArtKar updated:
-
-
Fixed my runtimes
-
-
TheSpaghetti updated:
-
-
more insectoid insects
-
-
kevinz000 updated:
-
-
bay/polaris style say_emphasis has been added. You can now |italicize| _underline_ and +bold+ your messages.
-
-
timothyteakettle updated:
-
-
Adds the brain trauma event, where one player gets a random brain trauma!
-
Adds the wisdom cow event, where the wisdom cow appears on the station!
-
Adds the fake virus event, where people get fake virus symptoms.
-
Adds the stray cargo pod event, where a cargo pod crashes into the station.
-
Adds the fugitives event, where fugitives are loose on the station, and it's the hunters jobs to capture them.
-
-
-
18 June 2020
-
Detective-Google updated:
-
-
cog is now less the suck
-
couple little derpy bits
-
malf disk and illegal tech disk moved from ashwalker base (guaranteed) to tendrils (chance based)
-
-
SmArtKar updated:
-
-
Ported shuttles from beestation
-
-
timothyteakettle updated:
-
-
embeds got reworked, sticky tape was added, more bullets that ricochet also added
-
medbots can now be tipped over
-
added more medbot sounds
-
-
-
17 June 2020
-
SmArtKar updated:
-
-
New ID icons
-
Sutures and Meshes
-
-
Trilbyspaceclone updated:
-
-
Gin export takes gin now
-
-
-
16 June 2020
-
Ghommie updated:
-
-
You can't blink nor use LOOC/AOOC as a petrified statue anymore.
-
-
Trilbyspaceclone updated:
-
-
BEPIS decal painter has been moved to venders, replacing it being the flashdark
-
-
kevinz000 updated:
-
-
projectile ricochets now use a less hilariously terrible way of being handled and should be easier to w
-
projectile runtime/ricocheting
-
Lobotomy no longer has a 50% chance of giving you a nigh-unremovable trauma, but does 50 brain damage even on success. On failure, it will give you a lobotomy-class trauma.
-
spinesnapping from tackling now only gives a lobotomy class trauma instead of magic.
-
-
timothyteakettle updated:
-
-
Food carts now function as intended, allowing the pouring and mixing of drinks.
-
slimes can now change their color using alter form
-
-
-
15 June 2020
-
Anturk, kevinz000 updated:
-
-
VV now properly allows access to datums in associative lists
-
SDQL2 printout has been upgraded for the 5th time.
Improvised Energy Gun. Fires 5 shots of 10 burn damage each. Can be upgraded with a lens made from glassworking and T4 parts for a minor buff.
-
Ammo + gun part loot spawners for mappers.
-
New sprites for Improvised Rifle, the ability to sling the rifle and a sprite for that.
-
New sprites for the Improvised Shotgun and its sling sprite.
-
Improvised shotguns are now two-handed only.
-
Improvised shotguns now only have a much less harsh 0.9* modifier, keeping them two hits to crit with slugs and buckshot. It can no longer be dual-wielded but can still be sawn off for w_class medium (can fit in backpacks).
-
Missing handsaw icons added in.
-
Crafting table cleaned up into sections.
-
-
Ghommie updated:
-
-
changed the weak attack message prefix from "inefficiently" to "limply", "feebly" and "saplessly" and lowered the threshold.
-
Fixing old beserker hardsuits having the wrong helmet type.
-
-
The0bserver and Stewydeadmike updated:
-
-
Hey, there's a bit of dust in this recipe book! Recipes using peas? How many recipes does this book even have?
-
Adds 6 new food items, and 1 new consumable reagent using all forms of the recently discovered peas. Ask your local botanist and chef for them today!
-
-
YakumoChen updated:
-
-
Adds polychrome options to loadout
-
Adds risque polychrome options to Kinkmate vendors
-
-
kevinz000 updated:
-
-
emissive blockers can no longer be radioactive.
-
Ghosts can now scan air inside most objects that contain air by clicking on them.
-
Directional blocking has been added, keybound to G. This will reduce a portion of incoming damage if done with eligible items at the cost of stamina damage incurred to the user based on damage blocked as well as while active, as well as in most cases preventing the user from doing any attacks while this is active.
-
Parrying has been added, keybound to F. Timing-based counterattacks, effect heavily dependent on the item, WIP.
-
Disks are now smaller.
-
-
-
12 June 2020
-
EmeraldSundisk updated:
-
-
The Detective's Office has been commandeered in order to serve a Head of Security. Detectives will find their new office within starboard maintenance.
-
Adds the Head of Security's standard equipment to their new office.
-
Slight adjustment/expansion to the main security department
-
Additional maintenance work
-
-
timothyteakettle updated:
-
-
Crabs, cockroaches, slimes and crabs are now small enough to fit in pet carriers
-
-
-
11 June 2020
-
Ghommie updated:
-
-
Balanced vending machine prices to be generally more affordable, a minority has been priced up though.
-
-
-
10 June 2020
-
DeltaFire15 updated:
-
-
Golems / simillar now inherit the neutered antag datum if the creator is neutered.
-
-
Ghommie updated:
-
-
Fixing missing pill type buttons from the chem master UI.
-
-
Naksu updated:
-
-
Lighting corner updates are ever so slightly faster.
-
-
Putnam for helping me code the contamination clearing on people updated:
-
-
Lab made Zeolites have been remade anew and more affective now that they refined the best possable way to mix and make a supper Zeolite capable of clearing contamination form not only people but items!
-
-
Trilbyspaceclone updated:
-
-
Tank Dispender has been moved into toxin storage from toxins
-
-
kevinz000 updated:
-
-
traitor classes can now be poplocked. hijack/glorious death are now locked to 25/20 respectively.
-
-
timothyteakettle updated:
-
-
adds a new fermichem, used for creating sentient plushies!
-
Mice can now breed using cheese wedges
-
Royal cheese can be crafted to convert a mouse into king rat
-
-
-
09 June 2020
-
Anonymous updated:
-
-
Added Orville-inspired clothing as a worthy alternative to Trek stuff.
-
Adds chaplain role allowance to the TMP Service Uniform loadout.
-
Adds paramedic in every medsci mentioned uniform loadout.
-
-
DeltaFire15 updated:
-
-
Offstation AIs can once again only interact with their z-level
-
-
Ghommie updated:
-
-
Fixing IC material containers interaction with stacks, for real.
-
-
Trilbyspaceclone updated:
-
-
Gasses like BZ and Masiam seem to just sell for less in cargo, markets seem to change it seems
-
-
kevinz000 updated:
-
-
plantpeople should stop dying in the halls now
-
Modifier-independent hotkey bindings have been added.
-
-
-
08 June 2020
-
DeltaFire15 updated:
-
-
Delinging now properly removes their special role
-
Keycard auth devices now require two seperate IDs with sufficient access to auth.
-
-
Linzolle updated:
-
-
shotguns no longer delete chambered shells while firing
-
-
kevinz000 updated:
-
-
test
-
-
shellspeed1 updated:
-
-
Internal tanks are now printable at the engineering lathe.
-
-
timothyteakettle updated:
-
-
newly created areas using blueprints now maintain the previous areas noteleport value
-
kudzu seeds now actually spawn vines
-
GoonStation 13 Development Team
diff --git a/html/changelogs/.all_changelog.yml b/html/changelogs/.all_changelog.yml
index a3a5724548..d4197fd1aa 100644
--- a/html/changelogs/.all_changelog.yml
+++ b/html/changelogs/.all_changelog.yml
@@ -26696,3 +26696,651 @@ DO NOT EDIT THIS FILE BY HAND! AUTOMATICALLY GENERATED BY ss13_genchangelog.py.
- tweak: combat mode now has weaker buffs in terms of damage dealt and took for
being or not being in the mode
- tweak: damage debuff for laying down has been decreased from 0.5x to 0.7x
+2020-08-03:
+ KeRSedChaplain:
+ - bugfix: fixed clockwork guardians being able to reflect ranged weapons
+ Linzolle:
+ - bugfix: uv penlight no longer invisible
+ dapnee:
+ - bugfix: active turfs on box and xenohive, maintenance bar APC not being stringed
+ correctly, turned a monitor to face a direction that makes sense, changed tag
+ of camera in gravgen being misnamed
+ silicons:
+ - rscadd: shoves have been buffed to apply a status effect rather than a 0.85 movespeed
+ modifier, meaning repeatedly shoving someone now renews the debuff
+ - balance: shoves now stagger for 3.5 seconds.
+ - tweak: war operatives now actually time 20 minutes since roundstart to depart
+ instead of 15.
+ - balance: explosive stand bombs can now be examined from any distance
+ - code_imp: explosive stand bombs are now a component.
+2020-08-04:
+ Seris02:
+ - bugfix: lizard spines
+ timothyteakettle:
+ - rscadd: due to further advancements in medical technology, you can now have holes
+ poked into your body for fun and enjoyment
+ zeroisthebiggay:
+ - rscadd: prefs for headpat wagging
+2020-08-06:
+ Auris456852:
+ - rscadd: Added B.O.O.P. Remote Control cartridges to the PTech.
+ Hatterhat:
+ - rscadd: Proto-kinetic glaives! Essentially a proto-kinetic crusher with a different
+ blade, handguard, and goliath hide grip. Expensive, but elegant.
+ - balance: Door charges no longer knock people out.
+ Ludox235:
+ - rscadd: You can now buy damaged AI upload modules in the traitor's uplink.
+ Seris02:
+ - bugfix: fixed ghost chilis
+ Trilbyspaceclone:
+ - rscadd: 4 New blends of tea have been shipped to the station, and how to make
+ them has been leaked!
+ b1tt3r1n0:
+ - rscadd: Added the warp implant
+ dapnee:
+ - tweak: added a hallway to telecoms for engineers to get there on meta
+ kappa-sama:
+ - rscdel: dildo circuit assemblies
+ lolman360:
+ - bugfix: The Tendril-Mother on Lavaland has remembered how to make ashwalkers who
+ know how to speak Draconic again.
+ timothyteakettle:
+ - tweak: nanotrasen has decided to fire all disabled members of the security division
+ and confiscate certain sentimental items from doctors
+ - tweak: the custom tongue preference now passes through cloning so you spawn with
+ your selected tongue
+ - tweak: several changes to travelling traders so they look better and spawn slightly
+ less often
+ zeroisthebiggay:
+ - rscadd: nukies can buy holoparasites
+2020-08-07:
+ dapnee:
+ - bugfix: fixed active tufs on some space ruins, murderdome VR, and a few on pubby,
+ changed cargo autolathe to techfab, messed with pipe room leading to monastery.
+ lolman360:
+ - bugfix: vendors are now unanchored when tipped. it just fell over it's not bolted
+ to the ground anymore.
+ - bugfix: podpeople no fat when sunbathing.
+ silicons:
+ - balance: explosions only recurse one level into storage before dropping 1 level
+ per storage layer.
+ - tweak: volumetric storage is now minimum 16 pixels per item because 8 was ridiculous
+ - spellcheck: shieldbash balanace --> balance
+ - rscadd: attempting to send too long of an emote will now reflect it back to you
+ instead of cutting it off and discarding the overflow.
+ - rscadd: holoparasites can now play music
+ - balance: lethal blood now causes damaging bleeding instead of outright gibbing
+2020-08-08:
+ DeltaFire15:
+ - balance: Roundstart cultists now start with a replica fabricator - no brass though,
+ make your own.
+ - balance: 'Kindle cast time: 10 > 15, mute after stun end: 2 > 5, slur after mute
+ end: 3 > 5'
+ - bugfix: The ratvarian spear no longer adds negative vitality under very specific
+ circumstances.
+ - balance: The Ratvarian Spear can parry now! Short parries with low leeway, but
+ low cooldown.
+ - rscadd: The brass claw, a implant-based weapon which gains combo on consecutive
+ hits against the same target.
+ - rscadd: The sigil of rites, a sigil used to perform various rites with a cost
+ of power and materials
+ - rscadd: 'The Rite of Advancement: Used to add a organ or cyberimplant to a clockie
+ without need for surgery.'
+ - rscadd: 'The Rite of Woundmending: Used to heal all wounds on another cultist,
+ causing toxins damage in return.'
+ - rscadd: 'The Rite of the Claw: Used to summon a brass claw implant. Maximum of
+ 4 uses per round.'
+ Hatterhat:
+ - rscadd: You can now buy a toolbox's worth of Mosin-Nagant ammo for a fairly discounted
+ price.
+ - rscadd: Revolvers from the dedicated kit now have reskinning capabilities.
+ - bugfix: You can now actually buy the riflery primer, which lets you pump shotguns
+ and work the Mosin's bolt faster.
+ - imageadd: Bulldog slug magazines now have a unique sprite.
+ Ludox235:
+ - rscdel: Removed an abductee objective that told you to remove all oxygen.
+ - rscadd: Added a new abductee objective to replace the removed one.
+ Sishen1542:
+ - tweak: "\U0001F171\uFE0Foneless"
+ - rscadd: squishy slime emotes
+ timothyteakettle:
+ - tweak: heparin makes you bleed half as much now
+ - tweak: cuts make you bleed 25% less now
+ - rscadd: more items in the loadout and loadout has subcategories now for easier
+ searching
+2020-08-09:
+ Hatterhat:
+ - rscadd: Proto-kinetic glaives (not crushers) can parry now.
+ MrJWhit:
+ - rscadd: Adds a second shutter on the top of the hop line
+ silicons:
+ - bugfix: immovable rods no longer drop down chasms
+ - rscdel: 'fun removal: squeaking objects now have an 1 second cooldown between
+ squeaks, and will have a 33% chance of interrupting any other squeaking object
+ when Cross()ing, meaning no more ear-fuck conveyor belts.'
+2020-08-10:
+ Hatterhat:
+ - bugfix: Parry counterattack text now shows up.
+ - balance: Sterilized gauze is now better at stopping bleeding, and applies slightly
+ faster. Very slightly faster.
+ - balance: Ointment and sutures now hold more in a stack (12 and 15, respectively).
+ - tweak: Sterilized gauze can now be made by just pouring 10u sterilizine onto standard
+ medical gauze, instead of having to craft it. Why you had to craft it, I will
+ honestly never know.
+ - balance: Proto-kinetic glaives are more expensive, stagger/cooldown on failed
+ parries increased slightly, perfect parries required for counterattack.
+ - rscadd: 'New item: Temporal Katana. 2 points for wizards, timestops upon successful
+ parry, bokken quickparry stats (100 force on melee counter!).'
+ - rscadd: Also you can *smirk. This has no mechanical effect, other than being smug.
+ KeRSedChaplain:
+ - rscadd: Added a guide for romerol usage
+ - balance: made infectious zombies not enter softcrit and take no stamina damage
+ LetterN:
+ - tweak: clocktheme color
+ - code_imp: Ports TGUI-4
+ Lynxless:
+ - rscadd: 'Ports TG #51879'
+ Owai-Seek:
+ - tweak: Meatballs now spawn raw from food processors.
+ Putnam3145:
+ - rscadd: Ethereals
+ - rscdel: (Hexa)crocin
+ - rscdel: (Hexa)camphor
+ - tweak: Tweaked wording for marking tickets IC issue.
+ - tweak: Rerolling your traitor goals will ONLY give you "proper" objectives.
+ Seris02:
+ - bugfix: borgs being able to select and use a module when it's too damaged
+ Sishen1542:
+ - balance: gave chairs active block/parry in exchange for removal of block_chance
+ - tweak: replaces box whiteship tbaton with truncheon
+ kappa-sama:
+ - balance: made the Dirty Magazines crate cost 4000 instead of 12000 credits
+ - rscadd: MODS I SPILLED MU JUICE HEJPPHRLP HELPJ JLEP HELP
+ silicons:
+ - balance: player made areas are no longer valid for malf hacking
+ - tweak: default space levels is 4 again.
+ - tweak: rats now swarm instead of stacking on one spot.
+ - balance: getting hit by an explosion will now barely hard knockdown, but will
+ leave you somewhat winded.
+ timothyteakettle:
+ - tweak: speech verbs copy through dna copying now
+2020-08-11:
+ Hatterhat:
+ - bugfix: PDA uplinks can now steal from pens. Properly. Just make sure to have
+ a pen in your PDA, first.
+ kappa-sama:
+ - tweak: tracer no longer gives you full stamheals per use
+ zeroisthebiggay:
+ - rscadd: volaju two
+2020-08-12:
+ DeltaFire15:
+ - balance: 'hellgun single-pack classification: goodies -> armory'
+ Detective-Google:
+ - bugfix: hallway table hallway table
+ Hatterhat:
+ - balance: The temporal katana is now slightly more worthy of the 2 spell point
+ cost, with a smaller, antimagic respecting timestop, less force, and no random
+ blockchance. Society has progressed past the need for blockchance.
+ LetterN:
+ - rscadd: Mafia Component
+ - bugfix: Fixed missing icons and handtele
+ Putnam3145:
+ - bugfix: a whole lot of jank regarding funny part sprite display.
+ Toriate:
+ - rscadd: Opossums have migrated into the maintenance tunnels! Seek them out at
+ your own peril!
+ ancientpower:
+ - tweak: Doors added to the west side of box medbay to make things a bit more manageable.
+ kappa-sama:
+ - tweak: smuggler satchel cost 2->1
+ - tweak: radio jammer cost 5->2
+ - bugfix: smuggler satchel uplink description now implies that persistence is disabled
+ lolman360:
+ - rscadd: renameable necklace (accessory, attaches to suit) and ring (glove slot.)
+ - tweak: custom rename is now 2048 characters? i think it's characters.
+ silicons:
+ - rscadd: You can now use anything as an emoji by doing :/obj/item/path/to/item:.
+ This works for any /atom or subtype.
+ timothyteakettle:
+ - rscadd: syndicate agents now have access to mechanical aim enhancers which allow
+ them to aim bullets to bounce off walls
+ - bugfix: ricochets work properly now for the bullets that support them
+ zeroisthebiggay:
+ - imageadd: hair and some sechuds
+ - balance: ce hardsuit radproofing
+2020-08-13:
+ LetterN:
+ - rscdel: Removes fermisleepers and reverts them to tg ones
+2020-08-14:
+ silicons:
+ - bugfix: abductors can buy things
+2020-08-15:
+ LetterN:
+ - bugfix: missing anomaly core icons
+ - bugfix: wrong state. blame the tg vertion i copied
+ silicons:
+ - tweak: the 8 rotation limit from clockwork chairs has been removed. please don't
+ abuse this.
+ - tweak: ethereals can now wear underwear
+2020-08-16:
+ kiwedespars:
+ - balance: nerfed hypereut chaplain weapon.
+ - balance: 50% rng blockchance -> 0%
+ - balance: parry made much worse because it's an actual weapon and a roundstart
+ one at that.
+ zeroisthebiggay:
+ - rscadd: tips
+ - rscdel: tips
+2020-08-17:
+ DeltaFire15:
+ - bugfix: Cogscarabs are no longer always Pogscarabs
+ Strazyplus:
+ - rscadd: Added drakeborgs
+ - rscadd: Added drakeplushies
+ - imageadd: added drakeborg sprites
+ - imageadd: added drakeplushie sprites
+ - code_imp: changed some code - added drakeplushies to backpack loadout Removed
+ duplicate voresleeper belly sprites from engdrake & jantidrake. [CC BY-NC-SA
+ 3.0](https://creativecommons.org/licenses/by-nc-sa/3.0/)
+ - rscadd: Added CC BY-NC-SA 3.0 license details to icon/mob/cyborg moved drakeborg.dmi
+ to icon/mob/cyborg
+2020-08-18:
+ DeltaFire15:
+ - balance: 'kindle cast time: 15ds -> 25ds'
+ - tweak: Moved the Belligerent Scripture to where it should be in the code
+ Detective-Google:
+ - rscadd: glass floors
+ - rscadd: uncrowbarrable plasma floors tweak:disco inferno's plasma floors can no
+ longer be crowbarred.
+ - rscadd: ghost cafe has funky fresh art
+ - bugfix: you can actually remove glass floors now
+ - code_imp: get_equipped_items is hopefully less gross
+ - balance: plasma cutters are no longer gay
+ Hatterhat:
+ - balance: Slaughter demons (and laughter demons, being a subtype) are MOB_SIZE_LARGE,
+ with one of the more immediate effects being able to mark them with a crusher
+ and backstab them.
+ - balance: The funny blyat men have stumbled upon another surplus of Mosin-Nagants
+ and are starting to pack them into crates again.
+ - balance: Vehicle riders can now, by default, get shot in the face and/or chest.
+ - rscadd: Adminspawn only .357 DumDum rounds! Because sometimes the other guy just
+ really needs to hurt.
+ - rscadd: Bluespace beakers now have a chemical window through the side that shows
+ chemical overlays.
+ - rscadd: Plant DNA manipulators now let you chuck things over them. Or they WOULD,
+ if LETPASSTHROW worked half a damn.
+ LetterN:
+ - bugfix: uplink implant states
+ - tweak: tweaks how role assigning works
+ MrJWhit:
+ - rscadd: Gives ashwalkers nightvision
+ - balance: Makes tesla blast people, not the environment, to save the server.
+ TheObserver-sys:
+ - bugfix: moves Garlic sprites from growing.dmi to growing_vegetable.dmi
+ - rscdel: Removes the unused Electric Lime mutation, it just takes up space with
+ no actual function nor sprites.
+ - imageadd: Gives Catnip growing sprites
+ - imagedel: Removes redundant images in growing.dmi
+ kiwedespars:
+ - rscadd: 10 force to a fucking rubber cock.
+ lolman360:
+ - balance: shotgun stripper clip nerf. ammoboxes can now accept a load_delay that
+ happens when they attack a magazine, internal or external.
+ ported from tg:
+ - rscadd: bronze airlocks and windows can now be built
+ - balance: i also tweaked bronze flooring to be cheaper.
+ silicons:
+ - tweak: stamina draining projectiles without stamina for their primary damage type
+ now has their stamina damage taken into account for shield blocking, rather
+ than the block being done for free for that.
+ timothyteakettle:
+ - refactor: snowflake code tidyup
+ - refactor: snowflake code for mutant bodypart selection has been rewritten to be
+ ~14x shorter
+ - tweak: meat type and horns can now be selected by any species
+2020-08-20:
+ DeltaFire15:
+ - bugfix: The cooking oil damage formula is no longer scuffed.
+ - tweak: Changed the clockie help-link to lead to our own wiki.
+ Fikou:
+ - admin: admins can now do html in ahelps properly
+ Hatterhat:
+ - balance: Pirate threats are now announced as "business propositions", and their
+ arrivals are now also announced properly.
+ tiramisuapimancer:
+ - bugfix: Ethereal hair is now their body color instead of accidentally white
+2020-08-21:
+ LetterN:
+ - rscadd: Updates and adds some of the tips
+ Putnam3145:
+ - admin: added reftracking as a compile flag
+ SmArtKar:
+ - tweak: RSD limitation is now 500 tiles
+ - bugfix: Fixed broken RSD sprites
+ - config: Removed that shuttle limit
+ timothyteakettle:
+ - bugfix: two snouts can once again be chosen in customization
+ - bugfix: lizard snouts work again
+2020-08-22:
+ Time-Green (copypasta'd by lolman360):
+ - rscadd: plumbing
+ - rscadd: automatic hydro trays
+2020-08-23:
+ DeltaFire15:
+ - bugfix: silicons and clockies can now access APCs properly
+ EmeraldSundisk:
+ - rscadd: Medbay now has a smartfridge for organ storage
+ - rscadd: Slight enhancements to the station's electrical wiring layout
+ - rscadd: Very small library renovation
+ - bugfix: Exterior airlocks have been given proper air systems for safety's sake
+ Ghommie:
+ - bugfix: Stops shielded hardsuits from slowly turning the wearer into a big glowing
+ ball of stacked energy shield overlays.
+ - tweak: the shielding overlay is merely visual as result. Aim your clicks.
+ Ludox235:
+ - tweak: no more 10 pop xenos (25pop now)
+ MrJWhit:
+ - tweak: Increases the majority of airlocks by 1 tile.
+ - tweak: Minor adjustments to the TEG engine.
+ Putnam3145:
+ - balance: Simplemobs no longer count in dynamic.
+ - balance: '"Story" storyteller no longer starts at a ludicrously low threat, always.'
+ - balance: Blob threat now scales with coverage.
+ - tweak: One person with their pref on no longer overpowers 40 people who might
+ not even know there is one.
+ - bugfix: Negative-weight rulesets are no longer put into the list.
+ kiwedespars:
+ - tweak: removed durathread from armwraps recipe.
+ lolman360:
+ - rscadd: breath mask balaclava
+ timothyteakettle:
+ - bugfix: lizards are now a recommended species for mam snouts
+ zeroisthebiggay:
+ - rscadd: new sprites for the temporal katana
+ - rscadd: suiciding with the temporal katana omae wa mou shinderius you into the
+ shadow realm
+ - soundadd: twilight isnt earrape
+2020-08-24:
+ MrJWhit:
+ - bugfix: Fixes areas on expanded airlocks
+ silicons:
+ - bugfix: wormhole jaunters work
+ - tweak: wormhole jaunters no longer get interference from bags of holding
+ - bugfix: airlocks now only shock on pulse/wirecutters instead of on tgui panel
+ open.
+ timothyteakettle:
+ - rscadd: three new items are in the loadout for all donators
+ zeroisthebiggay:
+ - rscadd: contraband black evening gloves in kinkvend
+2020-08-25:
+ Hatterhat:
+ - rscadd: Insidious combat gloves have been replaced by insidious guerilla gloves.
+ They're generally the same, except now you can tackle with them.
+ Literallynotpickles:
+ - tweak: You can now equip handheld crew monitors on all medical-related winter
+ coats.
+ Putnam3145:
+ - tweak: vore now ejects occupants on death
+ raspy-on-osu:
+ - tweak: Thermoelectric Generator power output
+ timothyteakettle:
+ - tweak: I.P.Cs now short their circuits when expressing emotion, causing sparks
+ to appear around them.
+2020-08-26:
+ ancientpower:
+ - tweak: Ghosts can read newscasters by clicking on them.
+ silicons:
+ - balance: hierophant vortex blasts now have 50% armor penetration vs mecha
+ - balance: ventcrawling now kicks off every attached/buckled mob, even for non humans.
+2020-08-27:
+ silicons:
+ - tweak: eyebeam lighting can only have 128 maximum HSV saturation now.
+ - balance: no more shotgun stripper clips in boxes.
+ - balance: goliath tentacles now do 20 damage to mechs at 25% ap
+ timothyteakettle:
+ - tweak: changing your character's gender won't randomize its hairstyle and facial
+ hairstyle now
+2020-08-28:
+ EmeraldSundisk:
+ - rscadd: Adds more paper to the library
+ - rscadd: The law office now has a desk window
+ - tweak: Expands most of CogStation's exterior airlocks. Slightly adjusts surrounding
+ areas to accommodate this.
+ - tweak: Updates some of CogStation's paperwork
+ - tweak: The rat in the morgue turned themselves into a possum. Funniest shit I've
+ ever seen.
+ - bugfix: Adjusts some area designations so cameras should receive power properly
+ - bugfix: Cleans up an errant decal
+ Hatterhat:
+ - tweak: Traitor holoparasites can now only be bought once, because apparently you
+ can only have one active holopara.
+ - balance: PDA bombs can now only be bought once per uplink.
+ lolman360:
+ - rscadd: atmos = radiation = chemistry.
+ shellspeed1:
+ - rscadd: Adds slow mode for iv drips
+ timothyteakettle:
+ - rscadd: an ancient game over a thousand years old has re-emerged among crewmembers
+ - rock paper scissors
+ - tweak: customization features appear in alphabetical order where necessary
+ - tweak: bokken do two more stamina damage now
+ - rscadd: you can now choose a body sprite as an anthromorph or anthromorphic insect,
+ and can choose from aquatic/avian and apid respectively (and obviously back
+ to the defaults too)
+2020-08-30:
+ raspy-on-osu:
+ - rscadd: new explosion echoes
+ - tweak: explosion echo range
+ - soundadd: 5 new explosion related sounds
+2020-08-31:
+ Arturlang:
+ - tweak: Slimes can now damage structures, don't leave them unfed!
+ Chiirno:
+ - bugfix: Moves pill_bottles/dice to box/dice on CogStation.
+ Couls, ported by NecromancerAnne:
+ - code_imp: cleans up mech backstabbing code
+ DeltaFire15:
+ - rscdel: teleport-to-ark ability of the eminence, commented out
+ - rscadd: teleport-to-obelisk ability for the eminence
+ Detective-Google:
+ - tweak: plasmamen have no more slowdown
+ - rscadd: object reskins now use very nice and cool radials
+ EmeraldSundisk:
+ - rscadd: Adds a pool to MetaStation
+ - tweak: Slight readjustments to the surrounding area
+ - bugfix: Fixes a handful of external airlocks
+ ForrestWick:
+ - balance: removes wall walking boots from nukie uplink
+ - tweak: removes wall walking boots from nukie uplink
+ Ghommie:
+ - bugfix: e-gun overlays and some floor decals should have been fixed.
+ LetterN:
+ - rscadd: tgchat
+ Lynxless:
+ - tweak: Changed anatomic panacea into a direct buff, instead of a chem injection
+ - balance: Changed the values of anatomic panacea
+ - imageadd: Added a new icon for panacea's buff alert
+ Putnam3145:
+ - tweak: Pref for genital/vore examine text
+ - bugfix: Fixed a couple events having ghost roles eligible.
+ - balance: 'Buffed slaughter demon: gets stronger as it eats people'
+ - balance: 'Nerfed slaughter demon: no longer permanently round-removes all who
+ are eaten by it, instead releasing their now-heartless bodies'
+ - bugfix: Dynamic storytellers now calculate property weights properly.
+ Sonic121x:
+ - bugfix: Fix the four type of new tea that will stuck inside your vein.
+ - rscadd: drinking glass sprite for those tea.
+ kappa-sama:
+ - balance: miners can no longer acquire funny antag item
+ lolman360:
+ - bugfix: shuttle engine/heater sprites now face the right way
+ raspy-on-osu:
+ - tweak: TEG power output
+ - tweak: tesla movement priorities
+ - rscadd: tesla counterplay
+ - rscadd: tesla containment check (containment variable now usable)
+ silicons:
+ - bugfix: brooms now sweep objects on MOVABLE_PRE_MOVE rather than MOVABLE_MOVED
+ - balance: firedoors no longer automatically open on touch when there's no pressure
+ differences.
+ timothyteakettle:
+ - tweak: buzz, buzz2 and ping are now all unrestricted emotes and can be used by
+ anyone
+ - imagedel: the drake credit and pickle credit sprites have been removed
+ - refactor: tongue speech handling is now done by accent datums
+ zeroisthebiggay:
+ - rscdel: waffleco
+2020-09-01:
+ BlueWildrose:
+ - bugfix: fixed slimes starting off hungry
+2020-09-02:
+ Putnam3145:
+ - code_imp: Added a unit test for character saving.
+ - balance: Plastitanium rapier no longer silently sleeps with no chance at counterplay
+ when used by pacifists.
+ - bugfix: Fusion scan is now actually useful.
+ Tupinambis:
+ - tweak: moved the dakis, genital growth pills, and genital autosurgeons out of
+ the maintenance loot table and into kinkmates.
+ raspy-on-osu:
+ - bugfix: pyroclastic anomaly client spam
+ timothyteakettle:
+ - rscadd: you can hide your ckey now from the roundend report
+2020-09-03:
+ Ghommie:
+ - bugfix: Jaunters should now work with magic mirror chasming.
+ - bugfix: The photocopier can now print more than one copy at a time.
+ - bugfix: Alkali perspiration infos don't crash the Pandemic UI anymore.
+ - bugfix: Windoors can be actually tinted now.
+2020-09-04:
+ timothyteakettle:
+ - bugfix: ipcs can speak
+2020-09-05:
+ Bhijn:
+ - rscadd: Readded the old method of temperature notifications in the form of a new
+ pair of shivering/sweating notifications.
+ Putnam3145:
+ - tweak: Hilbert hotel flavor text for one particular snowflake hotel changed.
+ - admin: admins can now actually reduce threat level in dynamic
+ - tweak: Made owo.ogg smaller.
+ - tweak: Character saving unit test is now more verbose on failure.
+ - refactor: Added an extools proc hook alternative to rust_g logging.
+ raspy-on-osu:
+ - tweak: supermatter shard examine text
+ - tweak: protolathe item categories
+2020-09-06:
+ Putnam3145:
+ - rscdel: Dynamic no longer pushes events.
+ - balance: Made spontaneous brain trauma a good deal less annoying.
+ lolman360, NecromancerAnne:
+ - rscadd: The ancient art of blacksmithing, now in pixels.
+ - imageadd: cool swords and shit (thanks anne)
+ raspy-on-osu:
+ - tweak: thermomachine examine text
+2020-09-07:
+ DeltaFire15:
+ - spellcheck: fixed a typo causing a span not to show.
+ Putnam for debugging ammo loading! Thx!!:
+ - rscdel: Removed spell blade, diamond picaxe, and fire wand from lava land loot
+ tables
+ - tweak: Each mini boss now has its own type of crate.
+ - tweak: Each spike has its own type of crate that it pulls its now smaller loot
+ table from
+ - balance: Moved god eye from spike loot table to hard spawn collosses fight
+ - balance: Moved holoparasight from spike loot table to bubble gum
+ - balance: Moved magic meat hook from spike loot table to drake
+ - rscadd: 2 more crates to Arena Shuttle as well as 4-4-4 of each new type of chest
+ - balance: Replaced the diamond pick loot with a better one
+ - tweak: Replaced the cursted katana with a non cursted verson that deals half the
+ damage and has less block!
+ - rscadd: Three new potions, blue heals the mind like a mama potion, Green heals
+ the soul aka the organs! Lastly Red heals the body, by 100 damage of each main
+ types. Best not to waste them!
+ - rscadd: Four more "wands" Spellbooks! These fun little guys shoot out its own
+ pages to do the affect, one will set the target on fire like a bullet, one will
+ harm them a bit, one will heal the target a small bit - How nice! Last one will
+ give them a few statis affects like a taser bolt but without as much power or
+ tasing affect
+2020-09-08:
+ Ghommie:
+ - spellcheck: fixed names of the Electrical Toolbox goodie pack and green croptop
+ christmas suit.
+ - bugfix: Fixed turf visuals for real. Original PR by AnturK on tgstation.
+ KeRSedChaplain:
+ - soundadd: added borg_deathsound.ogg and android_scream.ogg
+ silicons:
+ - balance: meteor waves now have a static 5 minute timer.
+2020-09-09:
+ Putnam3145:
+ - bugfix: Made superconductivity work for the first time literally ever.
+ timothyteakettle:
+ - bugfix: accents work better
+2020-09-11:
+ Putnam3145:
+ - balance: Superconducting turfs now can't go below 0 celsius.
+2020-09-12:
+ BlueWildrose:
+ - bugfix: The Polychromic winter coat's hoodie will now polychrome, alongside any
+ other new polychromic items with toggleable headwear.
+ - bugfix: Minesweeper will no longer blow up the player's ears when they select
+ "Play on the same board"
+ - rscadd: Slimepeople will find warm donk pockets among other toxin healing items
+ even more repulsive, as they are anti-toxic.
+ - tweak: Slimepeople are now neutral to gross foods.
+ DeltaFire15:
+ - balance: AIs can no longer qdel() the gravity generator
+ Putnam3145:
+ - code_imp: Added some unit tests for reactions.
+ - refactor: replaced handle_changeling and handle_bloodsucker with signal registration
+ Sonic121x:
+ - bugfix: Fixed pill button on chemical press
+ TheObserver-sys:
+ - rscadd: Brass now has a proper datum. Aspiring Forgetenders rejoice!
+ Trilbyspaceclone:
+ - bugfix: Race based drinks will no longer stay inside your blood for ever.
+ Tupinambis:
+ - tweak: Redid Cogstation atmos pipes to make it less cluttered.
+ - tweak: Removed a few doors from the main hallway to mitigate chokepoint issues
+ - bugfix: All belt hell conveyers are now on by default, so that belt hell actually
+ works.
+ - bugfix: IDs for poddoors and belts and the like. Everything is now properly hooked
+ and should work as expected (except for the pressure triggered mass drivers)
+ - bugfix: addresses most if not all roundstart active turfs.
+ - bugfix: Issue where wires were connected to the SMES improperly, and SMES were
+ not properly precharged, resulting in power failure earlier than intended.
+ - bugfix: various rogue turfs and wirings.
+ - bugfix: security office APC being hooked to maintenance for some reason.
+ - bugfix: TEG is now directly wired to the SMES.
+ - code_imp: 'adds a subtype of mass drivers that is triggered by things being on
+ it. TODO: Make these mass drivers trigger poddoors, to make belt hell fully
+ functional.'
+ lolman360:
+ - bugfix: glaives now work again
+ zeroisthebiggay:
+ - balance: Revolver is now poplocked down to fifteen people.
+2020-09-16:
+ timothyteakettle:
+ - tweak: fixed an icon path
+2020-09-17:
+ DeltaFire15:
+ - rscadd: Failing the plushmium reaction can now create peculiar plushies, depending
+ on reaction volume.
+ - bugfix: The mood-buff from petting a plushie now works properly again.
+ - bugfix: Fixed wacky necropolis loot chest behavior
+ EmeraldSundisk:
+ - rscadd: Adds the Research Director's office to Omega Station
+ - rscadd: Adds 2 new solar arrays (and control rooms)
+ - rscadd: Adds some action figures that weren't there previously
+ - rscadd: The CMO's office now has a light switch
+ - tweak: Slight readjustments to impacted areas
+ - tweak: Readjusts the toxins air supply line to (ideally) be easier to service
+ - bugfix: Department camera consoles should now be able to actually check appropriate
+ cameras
+ - bugfix: Xenobiology can now be locked down (by the Research Director)
+ MrJWhit:
+ - rscadd: Adds a brain damage line
+ Putnam3145:
+ - bugfix: Your balls finally feel full, again.
+ timothyteakettle:
+ - rscadd: due to changes in policy, and several lawsuits, Nanotrasen has been forced
+ to allow disabled people to sign up
diff --git a/html/changelogs/AutoChangeLog-pr-12983.yml b/html/changelogs/AutoChangeLog-pr-12983.yml
deleted file mode 100644
index ca87590c23..0000000000
--- a/html/changelogs/AutoChangeLog-pr-12983.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: "silicons"
-delete-after: True
-changes:
- - tweak: "war operatives now actually time 20 minutes since roundstart to depart instead of 15."
diff --git a/html/changelogs/AutoChangeLog-pr-12999.yml b/html/changelogs/AutoChangeLog-pr-12999.yml
deleted file mode 100644
index c2358348fd..0000000000
--- a/html/changelogs/AutoChangeLog-pr-12999.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: "KeRSedChaplain"
-delete-after: True
-changes:
- - bugfix: "fixed clockwork guardians being able to reflect ranged weapons"
diff --git a/html/changelogs/AutoChangeLog-pr-13382.yml b/html/changelogs/AutoChangeLog-pr-13382.yml
new file mode 100644
index 0000000000..2dfa78c540
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-13382.yml
@@ -0,0 +1,4 @@
+author: "silicons"
+delete-after: True
+changes:
+ - balance: "hulks can smash again (walls no longer break their hands)"
diff --git a/html/changelogs/AutoChangeLog-pr-13394.yml b/html/changelogs/AutoChangeLog-pr-13394.yml
new file mode 100644
index 0000000000..6fe20032da
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-13394.yml
@@ -0,0 +1,5 @@
+author: "DeltaFire15"
+delete-after: True
+changes:
+ - bugfix: "Sutures work on simplemobs again."
+ - bugfix: "Attacking dismembered bodyparts now targets the chest instead, for weapons aswell as unarmed attacks."
diff --git a/html/changelogs/AutoChangeLog-pr-13399.yml b/html/changelogs/AutoChangeLog-pr-13399.yml
new file mode 100644
index 0000000000..2da88988a0
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-13399.yml
@@ -0,0 +1,4 @@
+author: "silicons"
+delete-after: True
+changes:
+ - rscdel: "acid no longer degrades armor"
diff --git a/html/changelogs/AutoChangeLog-pr-13401.yml b/html/changelogs/AutoChangeLog-pr-13401.yml
new file mode 100644
index 0000000000..6bfc8e4e21
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-13401.yml
@@ -0,0 +1,4 @@
+author: "MrJWhit"
+delete-after: True
+changes:
+ - rscadd: "New sprites for chess pieces! You can craft them in-game with metal sheets."
diff --git a/code/modules/goonchat/browserassets/js/jquery.min.js b/html/jquery.min.js
similarity index 100%
rename from code/modules/goonchat/browserassets/js/jquery.min.js
rename to html/jquery.min.js
diff --git a/icons/effects/160x160.dmi b/icons/effects/160x160.dmi
index 0a97573a9b..2adedb6c03 100644
Binary files a/icons/effects/160x160.dmi and b/icons/effects/160x160.dmi differ
diff --git a/icons/effects/96x96.dmi b/icons/effects/96x96.dmi
index b60ff97b2b..34f4adf6ce 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 0c4784553c..e5bff44ac4 100644
Binary files a/icons/effects/beam.dmi and b/icons/effects/beam.dmi differ
diff --git a/icons/effects/beam_splash.dmi b/icons/effects/beam_splash.dmi
new file mode 100644
index 0000000000..d7deb3e927
Binary files /dev/null and b/icons/effects/beam_splash.dmi differ
diff --git a/icons/effects/effects.dmi b/icons/effects/effects.dmi
index a2fce4678f..52164e1d33 100644
Binary files a/icons/effects/effects.dmi and b/icons/effects/effects.dmi differ
diff --git a/icons/effects/eldritch.dmi b/icons/effects/eldritch.dmi
new file mode 100644
index 0000000000..82549dccf0
Binary files /dev/null and b/icons/effects/eldritch.dmi differ
diff --git a/icons/effects/mouse_pointers/barn_target.dmi b/icons/effects/mouse_pointers/barn_target.dmi
new file mode 100644
index 0000000000..475a2b88b1
Binary files /dev/null and b/icons/effects/mouse_pointers/barn_target.dmi differ
diff --git a/icons/effects/mouse_pointers/blind_target.dmi b/icons/effects/mouse_pointers/blind_target.dmi
new file mode 100644
index 0000000000..1d33fc7a4c
Binary files /dev/null and b/icons/effects/mouse_pointers/blind_target.dmi differ
diff --git a/icons/effects/mouse_pointers/cult_target.dmi b/icons/effects/mouse_pointers/cult_target.dmi
new file mode 100644
index 0000000000..650feb3361
Binary files /dev/null and b/icons/effects/mouse_pointers/cult_target.dmi differ
diff --git a/icons/effects/mouse_pointers/mecha_mouse-disable.dmi b/icons/effects/mouse_pointers/mecha_mouse-disable.dmi
new file mode 100644
index 0000000000..48924c58c2
Binary files /dev/null and b/icons/effects/mouse_pointers/mecha_mouse-disable.dmi differ
diff --git a/icons/effects/mouse_pointers/mecha_mouse.dmi b/icons/effects/mouse_pointers/mecha_mouse.dmi
new file mode 100644
index 0000000000..4b46a44684
Binary files /dev/null and b/icons/effects/mouse_pointers/mecha_mouse.dmi differ
diff --git a/icons/effects/mouse_pointers/mindswap_target.dmi b/icons/effects/mouse_pointers/mindswap_target.dmi
new file mode 100644
index 0000000000..32ccda154d
Binary files /dev/null and b/icons/effects/mouse_pointers/mindswap_target.dmi differ
diff --git a/icons/effects/mouse_pointers/mute_target.dmi b/icons/effects/mouse_pointers/mute_target.dmi
new file mode 100644
index 0000000000..8e3c278d3b
Binary files /dev/null and b/icons/effects/mouse_pointers/mute_target.dmi differ
diff --git a/icons/effects/mouse_pointers/overload_machine_target.dmi b/icons/effects/mouse_pointers/overload_machine_target.dmi
new file mode 100644
index 0000000000..8bc67cdab6
Binary files /dev/null and b/icons/effects/mouse_pointers/overload_machine_target.dmi differ
diff --git a/icons/effects/mouse_pointers/override_machine_target.dmi b/icons/effects/mouse_pointers/override_machine_target.dmi
new file mode 100644
index 0000000000..77dbb4ba32
Binary files /dev/null and b/icons/effects/mouse_pointers/override_machine_target.dmi differ
diff --git a/icons/effects/supplypod_down_target.dmi b/icons/effects/mouse_pointers/supplypod_down_target.dmi
similarity index 100%
rename from icons/effects/supplypod_down_target.dmi
rename to icons/effects/mouse_pointers/supplypod_down_target.dmi
diff --git a/icons/effects/mouse_pointers/supplypod_pickturf.dmi b/icons/effects/mouse_pointers/supplypod_pickturf.dmi
new file mode 100644
index 0000000000..3ca1131e1a
Binary files /dev/null and b/icons/effects/mouse_pointers/supplypod_pickturf.dmi differ
diff --git a/icons/effects/mouse_pointers/supplypod_pickturf_down.dmi b/icons/effects/mouse_pointers/supplypod_pickturf_down.dmi
new file mode 100644
index 0000000000..113fe47540
Binary files /dev/null and b/icons/effects/mouse_pointers/supplypod_pickturf_down.dmi differ
diff --git a/icons/effects/supplypod_target.dmi b/icons/effects/mouse_pointers/supplypod_target.dmi
similarity index 100%
rename from icons/effects/supplypod_target.dmi
rename to icons/effects/mouse_pointers/supplypod_target.dmi
diff --git a/icons/effects/mouse_pointers/throw_target.dmi b/icons/effects/mouse_pointers/throw_target.dmi
new file mode 100644
index 0000000000..660eafbf2b
Binary files /dev/null and b/icons/effects/mouse_pointers/throw_target.dmi differ
diff --git a/icons/effects/mouse_pointers/wrap_target.dmi b/icons/effects/mouse_pointers/wrap_target.dmi
new file mode 100644
index 0000000000..2e9a338c9e
Binary files /dev/null and b/icons/effects/mouse_pointers/wrap_target.dmi differ
diff --git a/icons/misc/language.dmi b/icons/misc/language.dmi
index 155dbab98d..9501dc9216 100644
Binary files a/icons/misc/language.dmi and b/icons/misc/language.dmi differ
diff --git a/icons/mob/actions/actions_ecult.dmi b/icons/mob/actions/actions_ecult.dmi
new file mode 100644
index 0000000000..d083206454
Binary files /dev/null and b/icons/mob/actions/actions_ecult.dmi differ
diff --git a/icons/mob/actions/backgrounds.dmi b/icons/mob/actions/backgrounds.dmi
index 07839588ce..6b983df95a 100644
Binary files a/icons/mob/actions/backgrounds.dmi and b/icons/mob/actions/backgrounds.dmi differ
diff --git a/icons/mob/animal.dmi b/icons/mob/animal.dmi
index 13f97d3761..50bf65b27f 100644
Binary files a/icons/mob/animal.dmi and b/icons/mob/animal.dmi differ
diff --git a/icons/mob/augmentation/cosmetic_prosthetic/bishop.dmi b/icons/mob/augmentation/cosmetic_prosthetic/bishop.dmi
new file mode 100644
index 0000000000..8641ef2d3c
Binary files /dev/null and b/icons/mob/augmentation/cosmetic_prosthetic/bishop.dmi differ
diff --git a/icons/mob/augmentation/cosmetic_prosthetic/cybersolutions.dmi b/icons/mob/augmentation/cosmetic_prosthetic/cybersolutions.dmi
new file mode 100644
index 0000000000..eeed0d0c01
Binary files /dev/null and b/icons/mob/augmentation/cosmetic_prosthetic/cybersolutions.dmi differ
diff --git a/icons/mob/augmentation/cosmetic_prosthetic/grayson.dmi b/icons/mob/augmentation/cosmetic_prosthetic/grayson.dmi
new file mode 100644
index 0000000000..9ed6c5f06f
Binary files /dev/null and b/icons/mob/augmentation/cosmetic_prosthetic/grayson.dmi differ
diff --git a/icons/mob/augmentation/cosmetic_prosthetic/hephaestus.dmi b/icons/mob/augmentation/cosmetic_prosthetic/hephaestus.dmi
new file mode 100644
index 0000000000..4bb6b72fd0
Binary files /dev/null and b/icons/mob/augmentation/cosmetic_prosthetic/hephaestus.dmi differ
diff --git a/icons/mob/augmentation/cosmetic_prosthetic/nanotrasen.dmi b/icons/mob/augmentation/cosmetic_prosthetic/nanotrasen.dmi
new file mode 100644
index 0000000000..18c7738ba3
Binary files /dev/null and b/icons/mob/augmentation/cosmetic_prosthetic/nanotrasen.dmi differ
diff --git a/icons/mob/augmentation/cosmetic_prosthetic/talon.dmi b/icons/mob/augmentation/cosmetic_prosthetic/talon.dmi
new file mode 100644
index 0000000000..9e4b93fb54
Binary files /dev/null and b/icons/mob/augmentation/cosmetic_prosthetic/talon.dmi differ
diff --git a/icons/mob/augmentation/cosmetic_prosthetic/ward.dmi b/icons/mob/augmentation/cosmetic_prosthetic/ward.dmi
new file mode 100644
index 0000000000..b2622ef6a1
Binary files /dev/null and b/icons/mob/augmentation/cosmetic_prosthetic/ward.dmi differ
diff --git a/icons/mob/augmentation/cosmetic_prosthetic/xion.dmi b/icons/mob/augmentation/cosmetic_prosthetic/xion.dmi
new file mode 100644
index 0000000000..823205742a
Binary files /dev/null and b/icons/mob/augmentation/cosmetic_prosthetic/xion.dmi differ
diff --git a/icons/mob/clothing/back.dmi b/icons/mob/clothing/back.dmi
index ae8e6221c6..8594af8ec2 100644
Binary files a/icons/mob/clothing/back.dmi and b/icons/mob/clothing/back.dmi differ
diff --git a/icons/mob/clothing/belt.dmi b/icons/mob/clothing/belt.dmi
index 52ef75020c..4ac82ca299 100644
Binary files a/icons/mob/clothing/belt.dmi and b/icons/mob/clothing/belt.dmi differ
diff --git a/icons/mob/clothing/custom_w.dmi b/icons/mob/clothing/custom_w.dmi
index 7452d63134..3e787fee77 100644
Binary files a/icons/mob/clothing/custom_w.dmi and b/icons/mob/clothing/custom_w.dmi differ
diff --git a/icons/mob/clothing/eyes.dmi b/icons/mob/clothing/eyes.dmi
index cd2b84a143..876159c258 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 755280b332..44499649f9 100644
Binary files a/icons/mob/clothing/hands.dmi and b/icons/mob/clothing/hands.dmi differ
diff --git a/icons/mob/clothing/head.dmi b/icons/mob/clothing/head.dmi
index a6e75123bb..16571a4aa1 100644
Binary files a/icons/mob/clothing/head.dmi and b/icons/mob/clothing/head.dmi differ
diff --git a/icons/mob/clothing/neck.dmi b/icons/mob/clothing/neck.dmi
index 86dec62a41..7bfecb4158 100644
Binary files a/icons/mob/clothing/neck.dmi and b/icons/mob/clothing/neck.dmi differ
diff --git a/icons/mob/clothing/suit.dmi b/icons/mob/clothing/suit.dmi
index 7a690bb29f..08d276a484 100644
Binary files a/icons/mob/clothing/suit.dmi and b/icons/mob/clothing/suit.dmi differ
diff --git a/icons/mob/clothing/suit_digi.dmi b/icons/mob/clothing/suit_digi.dmi
index 2616c0b893..dd00713770 100644
Binary files a/icons/mob/clothing/suit_digi.dmi and b/icons/mob/clothing/suit_digi.dmi differ
diff --git a/icons/mob/clothing/underwear.dmi b/icons/mob/clothing/underwear.dmi
index f0c2cde93d..8cf1144a68 100644
Binary files a/icons/mob/clothing/underwear.dmi and b/icons/mob/clothing/underwear.dmi differ
diff --git a/icons/mob/clothing/uniform.dmi b/icons/mob/clothing/uniform.dmi
index fa376635f9..3d24a9addd 100644
Binary files a/icons/mob/clothing/uniform.dmi and b/icons/mob/clothing/uniform.dmi differ
diff --git a/icons/mob/clothing/uniform_digi.dmi b/icons/mob/clothing/uniform_digi.dmi
index af72ac0e7b..bcb894033e 100644
Binary files a/icons/mob/clothing/uniform_digi.dmi and b/icons/mob/clothing/uniform_digi.dmi differ
diff --git a/icons/mob/eldritch_mobs.dmi b/icons/mob/eldritch_mobs.dmi
new file mode 100644
index 0000000000..8a16d53f88
Binary files /dev/null and b/icons/mob/eldritch_mobs.dmi differ
diff --git a/icons/mob/hud.dmi b/icons/mob/hud.dmi
index c21fa47b9c..1d2007fd63 100644
Binary files a/icons/mob/hud.dmi and b/icons/mob/hud.dmi differ
diff --git a/icons/mob/human_face.dmi b/icons/mob/human_face.dmi
index a4e2a9d5b2..8055233ea7 100644
Binary files a/icons/mob/human_face.dmi and b/icons/mob/human_face.dmi differ
diff --git a/icons/mob/human_parts.dmi b/icons/mob/human_parts.dmi
index 916686e63f..60978d55a2 100644
Binary files a/icons/mob/human_parts.dmi and b/icons/mob/human_parts.dmi differ
diff --git a/icons/mob/human_parts_greyscale.dmi b/icons/mob/human_parts_greyscale.dmi
index 989ec8049c..794074bfe4 100644
Binary files a/icons/mob/human_parts_greyscale.dmi and b/icons/mob/human_parts_greyscale.dmi differ
diff --git a/icons/mob/inhands/64x64_lefthand.dmi b/icons/mob/inhands/64x64_lefthand.dmi
index 6b47171066..6dc8d82753 100644
Binary files a/icons/mob/inhands/64x64_lefthand.dmi and b/icons/mob/inhands/64x64_lefthand.dmi differ
diff --git a/icons/mob/inhands/64x64_righthand.dmi b/icons/mob/inhands/64x64_righthand.dmi
index 3750e28906..ca87f74a6f 100644
Binary files a/icons/mob/inhands/64x64_righthand.dmi and b/icons/mob/inhands/64x64_righthand.dmi differ
diff --git a/icons/mob/inhands/antag/clockwork_lefthand.dmi b/icons/mob/inhands/antag/clockwork_lefthand.dmi
index 88bd8ab710..080d7fdc83 100644
Binary files a/icons/mob/inhands/antag/clockwork_lefthand.dmi and b/icons/mob/inhands/antag/clockwork_lefthand.dmi differ
diff --git a/icons/mob/inhands/antag/clockwork_righthand.dmi b/icons/mob/inhands/antag/clockwork_righthand.dmi
index 20190e4add..42715d6e92 100644
Binary files a/icons/mob/inhands/antag/clockwork_righthand.dmi and b/icons/mob/inhands/antag/clockwork_righthand.dmi differ
diff --git a/icons/mob/inhands/items_lefthand.dmi b/icons/mob/inhands/items_lefthand.dmi
index 315ca5e924..0a82845910 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 6af883f2e8..abfbb18f84 100644
Binary files a/icons/mob/inhands/items_righthand.dmi and b/icons/mob/inhands/items_righthand.dmi differ
diff --git a/icons/mob/inhands/weapons/hammers_lefthand.dmi b/icons/mob/inhands/weapons/hammers_lefthand.dmi
index 306fd0db8a..b753a1f181 100644
Binary files a/icons/mob/inhands/weapons/hammers_lefthand.dmi and b/icons/mob/inhands/weapons/hammers_lefthand.dmi differ
diff --git a/icons/mob/inhands/weapons/hammers_righthand.dmi b/icons/mob/inhands/weapons/hammers_righthand.dmi
index 674e4d510b..7650f6c760 100644
Binary files a/icons/mob/inhands/weapons/hammers_righthand.dmi and b/icons/mob/inhands/weapons/hammers_righthand.dmi differ
diff --git a/icons/mob/inhands/weapons/swords_lefthand.dmi b/icons/mob/inhands/weapons/swords_lefthand.dmi
index 0ca36ad43d..23d80af9ef 100644
Binary files a/icons/mob/inhands/weapons/swords_lefthand.dmi and b/icons/mob/inhands/weapons/swords_lefthand.dmi differ
diff --git a/icons/mob/inhands/weapons/swords_righthand.dmi b/icons/mob/inhands/weapons/swords_righthand.dmi
index 59d7bbce69..702c0299b5 100644
Binary files a/icons/mob/inhands/weapons/swords_righthand.dmi and b/icons/mob/inhands/weapons/swords_righthand.dmi differ
diff --git a/icons/mob/mob.dmi b/icons/mob/mob.dmi
index de09fb1c63..23f0e2cc13 100644
Binary files a/icons/mob/mob.dmi and b/icons/mob/mob.dmi differ
diff --git a/icons/mob/screen_alert.dmi b/icons/mob/screen_alert.dmi
index 30c23601a5..e64c037cf8 100644
Binary files a/icons/mob/screen_alert.dmi and b/icons/mob/screen_alert.dmi differ
diff --git a/icons/mob/wings.dmi b/icons/mob/wings.dmi
index ace37b1f17..4523403344 100644
Binary files a/icons/mob/wings.dmi and b/icons/mob/wings.dmi differ
diff --git a/icons/obj/ammo.dmi b/icons/obj/ammo.dmi
index bebb625440..e1caad8279 100644
Binary files a/icons/obj/ammo.dmi and b/icons/obj/ammo.dmi differ
diff --git a/icons/obj/assemblies/new_assemblies.dmi b/icons/obj/assemblies/new_assemblies.dmi
index 2283b84c43..32eda1eb49 100644
Binary files a/icons/obj/assemblies/new_assemblies.dmi and b/icons/obj/assemblies/new_assemblies.dmi differ
diff --git a/icons/obj/bureaucracy.dmi b/icons/obj/bureaucracy.dmi
index 95ae326f1e..b778a9d1a2 100644
Binary files a/icons/obj/bureaucracy.dmi and b/icons/obj/bureaucracy.dmi differ
diff --git a/icons/obj/chemical.dmi b/icons/obj/chemical.dmi
index aea15e2e39..14eb973e25 100644
Binary files a/icons/obj/chemical.dmi and b/icons/obj/chemical.dmi differ
diff --git a/icons/obj/chess.dmi b/icons/obj/chess.dmi
index 09d9c766fd..ec9415eab4 100644
Binary files a/icons/obj/chess.dmi and b/icons/obj/chess.dmi differ
diff --git a/icons/obj/clockwork_objects.dmi b/icons/obj/clockwork_objects.dmi
index ae919d85a4..156d4fa0c6 100644
Binary files a/icons/obj/clockwork_objects.dmi and b/icons/obj/clockwork_objects.dmi differ
diff --git a/icons/obj/clothing/accessories.dmi b/icons/obj/clothing/accessories.dmi
index c62a88c829..7d13e3f802 100644
Binary files a/icons/obj/clothing/accessories.dmi and b/icons/obj/clothing/accessories.dmi differ
diff --git a/icons/obj/clothing/belts.dmi b/icons/obj/clothing/belts.dmi
index c106bd12fc..1a98872463 100644
Binary files a/icons/obj/clothing/belts.dmi and b/icons/obj/clothing/belts.dmi differ
diff --git a/icons/obj/clothing/glasses.dmi b/icons/obj/clothing/glasses.dmi
index e8ba88a12f..4fce479a9d 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 874f0d3660..c0f78ee604 100644
Binary files a/icons/obj/clothing/gloves.dmi and b/icons/obj/clothing/gloves.dmi differ
diff --git a/icons/obj/clothing/hats.dmi b/icons/obj/clothing/hats.dmi
index 90f1fba315..8fbb2abe1e 100644
Binary files a/icons/obj/clothing/hats.dmi and b/icons/obj/clothing/hats.dmi differ
diff --git a/icons/obj/clothing/suits.dmi b/icons/obj/clothing/suits.dmi
index 258eb0dc35..3476b16258 100644
Binary files a/icons/obj/clothing/suits.dmi and b/icons/obj/clothing/suits.dmi differ
diff --git a/icons/obj/clothing/uniforms.dmi b/icons/obj/clothing/uniforms.dmi
index 57424c7b99..76f5722c8b 100644
Binary files a/icons/obj/clothing/uniforms.dmi and b/icons/obj/clothing/uniforms.dmi differ
diff --git a/icons/obj/crates.dmi b/icons/obj/crates.dmi
index a1bc443efb..9b42188f5e 100644
Binary files a/icons/obj/crates.dmi and b/icons/obj/crates.dmi differ
diff --git a/icons/obj/custom.dmi b/icons/obj/custom.dmi
index d0be1113ee..788c3e6795 100644
Binary files a/icons/obj/custom.dmi and b/icons/obj/custom.dmi differ
diff --git a/icons/obj/decals.dmi b/icons/obj/decals.dmi
index c280ee786f..7dac61a663 100644
Binary files a/icons/obj/decals.dmi and b/icons/obj/decals.dmi differ
diff --git a/icons/obj/drinks.dmi b/icons/obj/drinks.dmi
index 5962e7522d..008618199a 100644
Binary files a/icons/obj/drinks.dmi and b/icons/obj/drinks.dmi differ
diff --git a/icons/obj/eldritch.dmi b/icons/obj/eldritch.dmi
new file mode 100644
index 0000000000..294deda628
Binary files /dev/null and b/icons/obj/eldritch.dmi differ
diff --git a/icons/obj/food/food.dmi b/icons/obj/food/food.dmi
index 28cdefd331..078cadfd60 100644
Binary files a/icons/obj/food/food.dmi and b/icons/obj/food/food.dmi differ
diff --git a/icons/obj/food/piecake.dmi b/icons/obj/food/piecake.dmi
index 5638235217..935f7e8ad5 100644
Binary files a/icons/obj/food/piecake.dmi and b/icons/obj/food/piecake.dmi differ
diff --git a/icons/obj/guns/projectile.dmi b/icons/obj/guns/projectile.dmi
index 8c50e7da27..00670916db 100644
Binary files a/icons/obj/guns/projectile.dmi and b/icons/obj/guns/projectile.dmi differ
diff --git a/icons/obj/hydroponics/growing.dmi b/icons/obj/hydroponics/growing.dmi
index 5415f47528..0866791ed6 100644
Binary files a/icons/obj/hydroponics/growing.dmi and b/icons/obj/hydroponics/growing.dmi differ
diff --git a/icons/obj/hydroponics/growing_vegetables.dmi b/icons/obj/hydroponics/growing_vegetables.dmi
index e174b4fb0b..ce0beb86ce 100644
Binary files a/icons/obj/hydroponics/growing_vegetables.dmi and b/icons/obj/hydroponics/growing_vegetables.dmi differ
diff --git a/icons/obj/implants.dmi b/icons/obj/implants.dmi
index a6d4697673..b7cb90e9f4 100644
Binary files a/icons/obj/implants.dmi and b/icons/obj/implants.dmi differ
diff --git a/icons/obj/items_and_weapons.dmi b/icons/obj/items_and_weapons.dmi
index 57105cc089..c5aca2394f 100644
Binary files a/icons/obj/items_and_weapons.dmi and b/icons/obj/items_and_weapons.dmi differ
diff --git a/icons/obj/janitor.dmi b/icons/obj/janitor.dmi
index 1f520a42bd..b240391328 100644
Binary files a/icons/obj/janitor.dmi and b/icons/obj/janitor.dmi differ
diff --git a/icons/obj/lavaland/terrain.dmi b/icons/obj/lavaland/terrain.dmi
new file mode 100644
index 0000000000..4db51145ee
Binary files /dev/null and b/icons/obj/lavaland/terrain.dmi differ
diff --git a/icons/obj/library.dmi b/icons/obj/library.dmi
index 727d69b1e3..6e14c4c6ca 100644
Binary files a/icons/obj/library.dmi and b/icons/obj/library.dmi differ
diff --git a/icons/obj/lighting.dmi b/icons/obj/lighting.dmi
index c3ca67eeae..0e262895fd 100644
Binary files a/icons/obj/lighting.dmi and b/icons/obj/lighting.dmi differ
diff --git a/icons/obj/machines/medipen_refiller.dmi b/icons/obj/machines/medipen_refiller.dmi
new file mode 100644
index 0000000000..300d218d2d
Binary files /dev/null and b/icons/obj/machines/medipen_refiller.dmi differ
diff --git a/icons/obj/machines/research.dmi b/icons/obj/machines/research.dmi
index 7d64c494fd..7dcd4e6bcb 100644
Binary files a/icons/obj/machines/research.dmi and b/icons/obj/machines/research.dmi differ
diff --git a/icons/obj/mafia.dmi b/icons/obj/mafia.dmi
new file mode 100644
index 0000000000..c44b80aba1
Binary files /dev/null and b/icons/obj/mafia.dmi differ
diff --git a/icons/obj/mining.dmi b/icons/obj/mining.dmi
index 43bc9c48aa..400c5bdfa0 100644
Binary files a/icons/obj/mining.dmi and b/icons/obj/mining.dmi differ
diff --git a/icons/obj/modular_console.dmi b/icons/obj/modular_console.dmi
index cdcf6f1bd5..5d3cd0312c 100644
Binary files a/icons/obj/modular_console.dmi and b/icons/obj/modular_console.dmi differ
diff --git a/icons/obj/modular_laptop.dmi b/icons/obj/modular_laptop.dmi
index 7cf5a77621..fd24d27a97 100644
Binary files a/icons/obj/modular_laptop.dmi and b/icons/obj/modular_laptop.dmi differ
diff --git a/icons/obj/modular_tablet.dmi b/icons/obj/modular_tablet.dmi
index 2f9a0559ae..32edb57475 100644
Binary files a/icons/obj/modular_tablet.dmi and b/icons/obj/modular_tablet.dmi differ
diff --git a/icons/obj/plumbing/fluid_ducts.dmi b/icons/obj/plumbing/fluid_ducts.dmi
new file mode 100644
index 0000000000..87d9d2233b
Binary files /dev/null and b/icons/obj/plumbing/fluid_ducts.dmi differ
diff --git a/icons/obj/plumbing/plumbers.dmi b/icons/obj/plumbing/plumbers.dmi
new file mode 100644
index 0000000000..242622e000
Binary files /dev/null and b/icons/obj/plumbing/plumbers.dmi differ
diff --git a/icons/obj/projectiles.dmi b/icons/obj/projectiles.dmi
index 92e76f78bb..94568b8633 100644
Binary files a/icons/obj/projectiles.dmi and b/icons/obj/projectiles.dmi differ
diff --git a/icons/obj/reagentfillings.dmi b/icons/obj/reagentfillings.dmi
index d7a9a03c41..1ac941bae3 100644
Binary files a/icons/obj/reagentfillings.dmi and b/icons/obj/reagentfillings.dmi differ
diff --git a/icons/obj/smith.dmi b/icons/obj/smith.dmi
new file mode 100644
index 0000000000..a40b9a2b19
Binary files /dev/null and b/icons/obj/smith.dmi differ
diff --git a/icons/obj/surgery.dmi b/icons/obj/surgery.dmi
index 3996f0ead2..8db1156dea 100755
Binary files a/icons/obj/surgery.dmi and b/icons/obj/surgery.dmi differ
diff --git a/icons/obj/watercloset.dmi b/icons/obj/watercloset.dmi
index 4a299f29dd..2b64176878 100644
Binary files a/icons/obj/watercloset.dmi and b/icons/obj/watercloset.dmi differ
diff --git a/icons/stamp_icons/font.png b/icons/stamp_icons/font.png
new file mode 100644
index 0000000000..4297937696
Binary files /dev/null and b/icons/stamp_icons/font.png differ
diff --git a/icons/stamp_icons/large_stamp-cap.png b/icons/stamp_icons/large_stamp-cap.png
index 7f7ce460a4..19883563b2 100644
Binary files a/icons/stamp_icons/large_stamp-cap.png and b/icons/stamp_icons/large_stamp-cap.png differ
diff --git a/icons/stamp_icons/large_stamp-ce.png b/icons/stamp_icons/large_stamp-ce.png
index 189db310cf..d07f70c4d5 100644
Binary files a/icons/stamp_icons/large_stamp-ce.png and b/icons/stamp_icons/large_stamp-ce.png differ
diff --git a/icons/stamp_icons/large_stamp-centcom.png b/icons/stamp_icons/large_stamp-centcom.png
new file mode 100644
index 0000000000..6250cbff88
Binary files /dev/null and b/icons/stamp_icons/large_stamp-centcom.png differ
diff --git a/icons/stamp_icons/large_stamp-chap.png b/icons/stamp_icons/large_stamp-chap.png
new file mode 100644
index 0000000000..2586c13bad
Binary files /dev/null and b/icons/stamp_icons/large_stamp-chap.png differ
diff --git a/icons/stamp_icons/large_stamp-clown.png b/icons/stamp_icons/large_stamp-clown.png
index 5abbff7113..d606870507 100644
Binary files a/icons/stamp_icons/large_stamp-clown.png and b/icons/stamp_icons/large_stamp-clown.png differ
diff --git a/icons/stamp_icons/large_stamp-cmo.png b/icons/stamp_icons/large_stamp-cmo.png
index eb004be621..bac4c7b2f9 100644
Binary files a/icons/stamp_icons/large_stamp-cmo.png and b/icons/stamp_icons/large_stamp-cmo.png differ
diff --git a/icons/stamp_icons/large_stamp-deny.png b/icons/stamp_icons/large_stamp-deny.png
index 7c216fce48..1c7f416597 100644
Binary files a/icons/stamp_icons/large_stamp-deny.png and b/icons/stamp_icons/large_stamp-deny.png differ
diff --git a/icons/stamp_icons/large_stamp-hop.png b/icons/stamp_icons/large_stamp-hop.png
index 3f9aa4a76f..1447d58a87 100644
Binary files a/icons/stamp_icons/large_stamp-hop.png and b/icons/stamp_icons/large_stamp-hop.png differ
diff --git a/icons/stamp_icons/large_stamp-hos.png b/icons/stamp_icons/large_stamp-hos.png
index 67b69d7503..bd59590975 100644
Binary files a/icons/stamp_icons/large_stamp-hos.png and b/icons/stamp_icons/large_stamp-hos.png differ
diff --git a/icons/stamp_icons/large_stamp-law.png b/icons/stamp_icons/large_stamp-law.png
index d6d77eee9b..aee5aa3fbc 100644
Binary files a/icons/stamp_icons/large_stamp-law.png and b/icons/stamp_icons/large_stamp-law.png differ
diff --git a/icons/stamp_icons/large_stamp-mime.png b/icons/stamp_icons/large_stamp-mime.png
new file mode 100644
index 0000000000..c9a0143439
Binary files /dev/null and b/icons/stamp_icons/large_stamp-mime.png differ
diff --git a/icons/stamp_icons/large_stamp-ok.png b/icons/stamp_icons/large_stamp-ok.png
index e568ae0921..b40d3e3f1b 100644
Binary files a/icons/stamp_icons/large_stamp-ok.png and b/icons/stamp_icons/large_stamp-ok.png differ
diff --git a/icons/stamp_icons/large_stamp-qm.png b/icons/stamp_icons/large_stamp-qm.png
index ea863078b4..4ba31b3741 100644
Binary files a/icons/stamp_icons/large_stamp-qm.png and b/icons/stamp_icons/large_stamp-qm.png differ
diff --git a/icons/stamp_icons/large_stamp-rd.png b/icons/stamp_icons/large_stamp-rd.png
index 35479e805f..a9e7ad040b 100644
Binary files a/icons/stamp_icons/large_stamp-rd.png and b/icons/stamp_icons/large_stamp-rd.png differ
diff --git a/icons/stamp_icons/large_stamp-syndicate.png b/icons/stamp_icons/large_stamp-syndicate.png
new file mode 100644
index 0000000000..68f17d8f5e
Binary files /dev/null and b/icons/stamp_icons/large_stamp-syndicate.png differ
diff --git a/icons/turf/floors.dmi b/icons/turf/floors.dmi
index 83dd4f5df8..f2b84bbe03 100644
Binary files a/icons/turf/floors.dmi and b/icons/turf/floors.dmi differ
diff --git a/icons/turf/floors/glass.dmi b/icons/turf/floors/glass.dmi
new file mode 100644
index 0000000000..adf6f57aaa
Binary files /dev/null and b/icons/turf/floors/glass.dmi differ
diff --git a/icons/turf/floors/reinf_glass.dmi b/icons/turf/floors/reinf_glass.dmi
new file mode 100644
index 0000000000..dda99cd07f
Binary files /dev/null and b/icons/turf/floors/reinf_glass.dmi differ
diff --git a/icons/turf/shuttle.dmi b/icons/turf/shuttle.dmi
index 985b9991a0..fbca824f47 100644
Binary files a/icons/turf/shuttle.dmi and b/icons/turf/shuttle.dmi differ
diff --git a/interface/interface.dm b/interface/interface.dm
index 2b33ebb5e8..2aece7cb6c 100644
--- a/interface/interface.dm
+++ b/interface/interface.dm
@@ -71,7 +71,7 @@
return
var/static/issue_template = file2text(".github/ISSUE_TEMPLATE.md")
var/servername = CONFIG_GET(string/servername)
- var/url_params = "Reporting client version: [byond_version]\n\n[issue_template]"
+ var/url_params = "Reporting client version: [byond_version].[byond_build]\n\n[issue_template]"
if(GLOB.round_id || servername)
url_params = "Issue reported from [GLOB.round_id ? " Round ID: [GLOB.round_id][servername ? " ([servername])" : ""]" : servername]\n\n[url_params]"
DIRECT_OUTPUT(src, link("[githuburl]/issues/new?body=[url_encode(url_params)]"))
@@ -79,151 +79,13 @@
to_chat(src, "The Github URL is not set in the server configuration.")
return
-/client/verb/hotkeys_help()
- set name = "hotkeys-help"
- set category = "OOC"
-
- var/adminhotkeys = {"
-Admin:
-\tF3 = asay
-\tF5 = Aghost (admin-ghost)
-\tF6 = player-panel
-\tF7 = Buildmode
-\tF8 = Invisimin
-\tCtrl+F8 = Stealthmin
-"}
-
- mob.hotkey_help()
-
- if(holder)
- to_chat(src, adminhotkeys)
-
/client/verb/changelog()
set name = "Changelog"
set category = "OOC"
- var/datum/asset/changelog = get_asset_datum(/datum/asset/simple/changelog)
+ var/datum/asset/simple/namespaced/changelog = get_asset_datum(/datum/asset/simple/namespaced/changelog)
changelog.send(src)
- src << browse('html/changelog.html', "window=changes;size=675x650")
+ src << browse(changelog.get_htmlloader("changelog.html"), "window=changes;size=675x650")
if(prefs.lastchangelog != GLOB.changelog_hash)
prefs.lastchangelog = GLOB.changelog_hash
prefs.save_preferences()
winset(src, "infowindow.changelog", "font-style=;")
-
-
-/mob/proc/hotkey_help()
- var/hotkey_mode = {"
-Hotkey-Mode: (hotkey-mode must be on)
-\tTAB = toggle hotkey-mode
-\ta = left
-\ts = down
-\td = right
-\tw = up
-\tq = drop
-\te = equip
-\tr = throw
-\tm = me
-\tt = say
-\to = OOC
-\tb = resist
-\tv = rest
-\th = stop pulling
-\tx = swap-hand
-\tz = activate held object (or y)
-\tShift+e = Put held item into belt or take out most recent item added to belt.
-\tShift+b = Put held item into backpack or take out most recent item added to backpack.
-\tf = cycle-intents-left
-\tg = cycle-intents-right
-\t1 = help-intent
-\t2 = disarm-intent
-\t3 = grab-intent
-\t4 = harm-intent
-\tNumpad = Body target selection (Press 8 repeatedly for Head->Eyes->Mouth)
-\tAlt(HOLD) = Alter movement intent
-"}
-
- var/other = {"
-Any-Mode: (hotkey doesn't need to be on)
-\tCtrl+a = left
-\tCtrl+s = down
-\tCtrl+d = right
-\tCtrl+w = up
-\tCtrl+q = drop
-\tCtrl+e = equip
-\tCtrl+r = throw
-\tCtrl+b = resist
-\tCtrl+h = stop pulling
-\tCtrl+o = OOC
-\tCtrl+x = swap-hand
-\tCtrl+z = activate held object (or Ctrl+y)
-\tCtrl+f = cycle-intents-left
-\tCtrl+g = cycle-intents-right
-\tCtrl+1 = help-intent
-\tCtrl+2 = disarm-intent
-\tCtrl+3 = grab-intent
-\tCtrl+4 = harm-intent
-\tCtrl+'+/-' OR
-\tShift+Mousewheel = Ghost zoom in/out
-\tDEL = stop pulling
-\tINS = cycle-intents-right
-\tHOME = drop
-\tPGUP = swap-hand
-\tPGDN = activate held object
-\tEND = throw
-\tCtrl+Numpad = Body target selection (Press 8 repeatedly for Head->Eyes->Mouth)
-"}
-
- to_chat(src, hotkey_mode)
- to_chat(src, other)
-
-/mob/living/silicon/robot/hotkey_help()
- //h = talk-wheel has a nonsense tag in it because \th is an escape sequence in BYOND.
- var/hotkey_mode = {"
-Hotkey-Mode: (hotkey-mode must be on)
-\tTAB = toggle hotkey-mode
-\ta = left
-\ts = down
-\td = right
-\tw = up
-\tq = unequip active module
-\tv = rest
-\th = stop pulling
-\tm = me
-\tt = say
-\to = OOC
-\tx = cycle active modules
-\tb = resist
-\tz = activate held object (or y)
-\tf = cycle-intents-left
-\tg = cycle-intents-right
-\t1 = activate module 1
-\t2 = activate module 2
-\t3 = activate module 3
-\t4 = toggle intents
-"}
-
- var/other = {"
-Any-Mode: (hotkey doesn't need to be on)
-\tCtrl+a = left
-\tCtrl+s = down
-\tCtrl+d = right
-\tCtrl+w = up
-\tCtrl+q = unequip active module
-\tCtrl+x = cycle active modules
-\tCtrl+b = resist
-\tCtrl+h = stop pulling
-\tCtrl+o = OOC
-\tCtrl+z = activate held object (or Ctrl+y)
-\tCtrl+f = cycle-intents-left
-\tCtrl+g = cycle-intents-right
-\tCtrl+1 = activate module 1
-\tCtrl+2 = activate module 2
-\tCtrl+3 = activate module 3
-\tCtrl+4 = toggle intents
-\tDEL = stop pulling
-\tINS = toggle intents
-\tPGUP = cycle active modules
-\tPGDN = activate held object
-"}
-
- to_chat(src, hotkey_mode)
- to_chat(src, other)
diff --git a/interface/menu.dm b/interface/menu.dm
index 3c881656da..976abb16b3 100644
--- a/interface/menu.dm
+++ b/interface/menu.dm
@@ -63,7 +63,6 @@ GLOBAL_LIST_EMPTY(menulist)
return
M.Set_checked(src, verbpath)
-
/datum/verbs/menu/Icon/Load_checked(client/C) //So we can be lazy, we invoke the "checked" menu item on menu load.
var/procpath/verbpath = Get_checked(C)
if (!verbpath || !(verbpath in typesof("[type]/verb")))
@@ -115,4 +114,3 @@ GLOBAL_LIST_EMPTY(menulist)
/datum/verbs/menu/Icon/Scaling/verb/BL()
set name = "@.winset \"mapwindow.map.zoom-mode=blur\""
set desc = "Bilinear"
-
diff --git a/interface/skin.dmf b/interface/skin.dmf
index cf49f0f30f..d7e461eadd 100644
--- a/interface/skin.dmf
+++ b/interface/skin.dmf
@@ -1,320 +1,290 @@
-macro "default"
-
-
-menu "menu"
- elem
- name = "&File"
- command = ""
- saved-params = "is-checked"
- elem
- name = "&Quick screenshot\tF2"
- command = ".screenshot auto"
- category = "&File"
- saved-params = "is-checked"
- elem
- name = "&Save screenshot as...\tShift+F2"
- command = ".screenshot"
- category = "&File"
- saved-params = "is-checked"
- elem
- name = ""
- command = ""
- category = "&File"
- saved-params = "is-checked"
- elem "reconnectbutton"
- name = "&Reconnect"
- command = ".reconnect"
- category = "&File"
- saved-params = "is-checked"
- elem
- name = "&Quit\tAlt-F4"
- command = ".quit"
- category = "&File"
- saved-params = "is-checked"
- elem
- name = "&Help"
- command = ""
- saved-params = "is-checked"
- elem
- name = "&Admin Help\tF1"
- command = "adminhelp"
- category = "&Help"
- saved-params = "is-checked"
- elem
- name = "&Hotkeys"
- command = "hotkeys-help"
- category = "&Help"
- saved-params = "is-checked"
-
-
-window "mainwindow"
- elem "mainwindow"
- type = MAIN
- pos = 0,0
- size = 640x440
- anchor1 = none
- anchor2 = none
- background-color = #242424
- is-default = true
- saved-params = "pos;size;is-minimized;is-maximized"
- icon = 'icons\\ss13_64.png'
- macro = "default"
- menu = "menu"
- elem "split"
- type = CHILD
- pos = 0,0
- size = 637x440
- anchor1 = 0,0
- anchor2 = 100,100
- background-color = #272727
- saved-params = "splitter"
- left = "mapwindow"
- right = "infowindow"
- is-vert = true
- splitter = 75
- elem "asset_cache_browser"
- type = BROWSER
- pos = 0,0
- size = 200x200
- anchor1 = none
- anchor2 = none
- background-color = #272727
- is-visible = false
- auto-format = false
- saved-params = ""
- elem "tooltip"
- type = BROWSER
- pos = 0,0
- size = 999x999
- anchor1 = none
- anchor2 = none
- background-color = #272727
- is-visible = false
- saved-params = ""
-
-window "mapwindow"
- elem "mapwindow"
- type = MAIN
- pos = 418,0
- size = 1024x1024
- anchor1 = none
- anchor2 = none
- background-color = none
- saved-params = "pos;size;is-minimized;is-maximized"
- statusbar = false
- is-pane = true
- outer-size = 684x617
- inner-size = 662x561
- elem "map"
- type = MAP
- pos = 0,0
- size = 1024x1024
- anchor1 = 0,0
- anchor2 = 100,100
- font-family = "Arial"
- font-size = 7
- is-default = true
- saved-params = "icon-size"
- zoom-mode = distort
- style = ".center { text-align: center; }\n.maptext { font-family: 'Small Fonts'; font-size: 7px; -dm-text-outline: 1px black; color: white; line-height: 1.1; }\n.command_headset { font-weight: bold;\tfont-size: 8px; } .small { font-size: 6px; }\n.big { font-size: 8px; }\n.reallybig { font-size: 8px; }\n.extremelybig { font-size: 8px; }\n.greentext { color: #00ff00; font-size: 7px; }\n.redtext { color: #ff0000; font-size: 7px; }\n.clown { color: #ff69bf; font-size: 7px; font-weight: bold; }\n.his_grace { color: #15d512; }\n.hypnophrase { color: #0d0d0d; font-weight: bold; }\n.yell { font-weight: bold; }\n.italics { font-size: 6px; }"
-
-window "infowindow"
- elem "infowindow"
- type = MAIN
- pos = 0,0
- size = 640x480
- anchor1 = none
- anchor2 = none
- background-color = #242424
- saved-params = "pos;size;is-minimized;is-maximized"
- is-pane = true
- elem "info"
- type = CHILD
- pos = 0,30
- size = 640x445
- anchor1 = 0,0
- anchor2 = 100,100
- background-color = #272727
- saved-params = "splitter"
- left = "statwindow"
- right = "outputwindow"
- is-vert = false
- elem "changelog"
- type = BUTTON
- pos = 16,5
- size = 104x20
- anchor1 = 3,0
- anchor2 = 19,0
- text-color = #e0e0e0
- background-color = #40628a
- saved-params = "is-checked"
- text = "Changelog"
- command = "changelog"
- elem "rules"
- type = BUTTON
- pos = 120,5
- size = 100x20
- anchor1 = 19,0
- anchor2 = 34,0
- text-color = #e0e0e0
- background-color = #40628a
- saved-params = "is-checked"
- text = "Rules"
- command = "rules"
- elem "wiki"
- type = BUTTON
- pos = 220,5
- size = 100x20
- anchor1 = 34,0
- anchor2 = 50,0
- text-color = #e0e0e0
- background-color = #40628a
- saved-params = "is-checked"
- text = "Wiki"
- command = "wiki"
- elem "forum"
- type = BUTTON
- pos = 320,5
- size = 100x20
- anchor1 = 50,0
- anchor2 = 66,0
- text-color = #e0e0e0
- background-color = #40628a
- saved-params = "is-checked"
- text = "Forum"
- command = "forum"
- elem "github"
- type = BUTTON
- pos = 420,5
- size = 100x20
- anchor1 = 66,0
- anchor2 = 81,0
- text-color = #e0e0e0
- background-color = #40628a
- saved-params = "is-checked"
- text = "GitHub"
- command = "github"
- elem "report-issue"
- type = BUTTON
- pos = 520,5
- size = 100x20
- anchor1 = 81,0
- anchor2 = 97,0
- font-size = 8
- text-color = #e0e0e0
- background-color = #a92c2c
- saved-params = "is-checked"
- text = "Report Issue"
- command = "report-issue"
-
-window "outputwindow"
- elem "outputwindow"
- type = MAIN
- pos = 0,0
- size = 640x480
- anchor1 = none
- anchor2 = none
- background-color = #272727
- saved-params = "pos;size;is-minimized;is-maximized"
- titlebar = false
- statusbar = false
- can-close = false
- can-minimize = false
- can-resize = false
- is-pane = true
- elem "input"
- type = INPUT
- pos = 2,460
- size = 595x20
- anchor1 = 0,100
- anchor2 = 100,100
- background-color = #d3b5b5
- is-default = true
- border = sunken
- saved-params = "command"
- elem "say"
- type = BUTTON
- pos = 600,460
- size = 37x20
- anchor1 = 100,100
- anchor2 = none
- text-color = #e0e0e0
- background-color = #272727
- saved-params = "is-checked"
- text = "Chat"
- command = ".winset \"say.is-checked=true ? input.command=\"!say \\\"\" : input.command=\""
- is-flat = true
- button-type = pushbox
- elem "browseroutput"
- type = BROWSER
- pos = 0,0
- size = 640x456
- anchor1 = 0,0
- anchor2 = 100,100
- background-color = #272727
- is-visible = false
- is-disabled = true
- saved-params = ""
- auto-format = false
- elem "output"
- type = OUTPUT
- pos = 0,0
- size = 640x456
- anchor1 = 0,0
- anchor2 = 100,100
- text-color = #e0e0e0
- background-color = #272727
- is-default = true
- saved-params = ""
-
-window "statwindow"
- elem "statwindow"
- type = MAIN
- pos = 281,0
- size = 640x480
- anchor1 = none
- anchor2 = none
- background-color = #242424
- saved-params = "pos;size;is-minimized;is-maximized"
- is-pane = true
- elem "stat"
- type = INFO
- pos = 0,0
- size = 640x480
- anchor1 = 0,0
- anchor2 = 100,100
- text-color = #e0e0e0
- background-color = #272727
- is-default = true
- saved-params = ""
- tab-text-color = #e0e0e0
- tab-background-color = #242424
- prefix-color = #e0e0e0
- suffix-color = #e0e0e0
-
-window "preferences_window"
- elem "preferences_window"
- type = MAIN
- pos = 372,0
- size = 1280x1000
- anchor1 = none
- anchor2 = none
- is-visible = false
- saved-params = "pos;size;is-minimized;is-maximized"
- statusbar = false
- elem "preferences_browser"
- type = BROWSER
- pos = 0,0
- size = 960x1000
- anchor1 = 0,0
- anchor2 = 75,100
- saved-params = ""
- elem "character_preview_map"
- type = MAP
- pos = 960,0
- size = 320x1000
- anchor1 = 75,0
- anchor2 = 100,100
- right-click = true
- saved-params = "zoom;letterbox;zoom-mode"
-
+macro "default"
+
+
+menu "menu"
+ elem
+ name = "&File"
+ command = ""
+ saved-params = "is-checked"
+ elem
+ name = "&Quick screenshot\tF2"
+ command = ".screenshot auto"
+ category = "&File"
+ saved-params = "is-checked"
+ elem
+ name = "&Save screenshot as...\tShift+F2"
+ command = ".screenshot"
+ category = "&File"
+ saved-params = "is-checked"
+ elem
+ name = ""
+ command = ""
+ category = "&File"
+ saved-params = "is-checked"
+ elem "reconnectbutton"
+ name = "&Reconnect"
+ command = ".reconnect"
+ category = "&File"
+ saved-params = "is-checked"
+ elem
+ name = "&Quit\tAlt-F4"
+ command = ".quit"
+ category = "&File"
+ saved-params = "is-checked"
+ elem
+ name = "&Help"
+ command = ""
+ saved-params = "is-checked"
+ elem
+ name = "&Admin Help\tF1"
+ command = "adminhelp"
+ category = "&Help"
+ saved-params = "is-checked"
+ elem
+ name = "&Hotkeys"
+ command = "hotkeys-help"
+ category = "&Help"
+ saved-params = "is-checked"
+
+window "mainwindow"
+ elem "mainwindow"
+ type = MAIN
+ pos = 0,0
+ size = 640x440
+ anchor1 = none
+ anchor2 = none
+ is-default = true
+ saved-params = "pos;size;is-minimized;is-maximized"
+ icon = 'icons\\ss13_64.png'
+ macro = "default"
+ menu = "menu"
+ elem "split"
+ type = CHILD
+ pos = 3,0
+ size = 637x440
+ anchor1 = 0,0
+ anchor2 = 100,100
+ saved-params = "splitter"
+ left = "mapwindow"
+ right = "infowindow"
+ is-vert = true
+ elem "asset_cache_browser"
+ type = BROWSER
+ pos = 0,0
+ size = 200x200
+ anchor1 = none
+ anchor2 = none
+ is-visible = false
+ auto-format = false
+ saved-params = ""
+ elem "tooltip"
+ type = BROWSER
+ pos = 0,0
+ size = 999x999
+ anchor1 = none
+ anchor2 = none
+ is-visible = false
+ saved-params = ""
+
+window "mapwindow"
+ elem "mapwindow"
+ type = MAIN
+ pos = 0,0
+ size = 1024x1024
+ anchor1 = none
+ anchor2 = none
+ saved-params = "pos;size;is-minimized;is-maximized"
+ is-pane = true
+ elem "map"
+ type = MAP
+ pos = 0,0
+ size = 1024x1024
+ anchor1 = 0,0
+ anchor2 = 100,100
+ font-family = "Arial"
+ font-size = 7
+ text-color = none
+ is-default = true
+ style=".center { text-align: center; } .maptext { font-family: 'Small Fonts'; font-size: 7px; -dm-text-outline: 1px black; color: white; line-height: 1.1; } .command_headset { font-weight: bold; font-size: 8px; } .small { font-size: 6px; } .big { font-size: 8px; } .reallybig { font-size: 8px; } .extremelybig { font-size: 8px; } .greentext { color: #00FF00; font-size: 7px; } .redtext { color: #FF0000; font-size: 7px; } .clown { color: #FF69Bf; font-size: 7px; font-weight: bold; } .his_grace { color: #15D512; } .hypnophrase { color: #0d0d0d; font-weight: bold; } .yell { font-weight: bold; } .italics { font-size: 6px; }"
+
+window "infowindow"
+ elem "infowindow"
+ type = MAIN
+ pos = 0,0
+ size = 640x480
+ anchor1 = none
+ anchor2 = none
+ saved-params = "pos;size;is-minimized;is-maximized"
+ is-pane = true
+ elem "info"
+ type = CHILD
+ pos = 0,30
+ size = 640x445
+ anchor1 = 0,0
+ anchor2 = 100,100
+ saved-params = "splitter"
+ left = "statwindow"
+ right = "outputwindow"
+ is-vert = false
+ elem "changelog"
+ type = BUTTON
+ pos = 16,5
+ size = 104x20
+ anchor1 = 3,0
+ anchor2 = 19,0
+ saved-params = "is-checked"
+ text = "Changelog"
+ command = "changelog"
+ elem "rules"
+ type = BUTTON
+ pos = 120,5
+ size = 100x20
+ anchor1 = 19,0
+ anchor2 = 34,0
+ saved-params = "is-checked"
+ text = "Rules"
+ command = "rules"
+ elem "wiki"
+ type = BUTTON
+ pos = 220,5
+ size = 100x20
+ anchor1 = 34,0
+ anchor2 = 50,0
+ saved-params = "is-checked"
+ text = "Wiki"
+ command = "wiki"
+ elem "forum"
+ type = BUTTON
+ pos = 320,5
+ size = 100x20
+ anchor1 = 50,0
+ anchor2 = 66,0
+ saved-params = "is-checked"
+ text = "Forum"
+ command = "forum"
+ elem "github"
+ type = BUTTON
+ pos = 420,5
+ size = 100x20
+ anchor1 = 66,0
+ anchor2 = 81,0
+ saved-params = "is-checked"
+ text = "Github"
+ command = "github"
+ elem "report-issue"
+ type = BUTTON
+ pos = 520,5
+ size = 100x20
+ anchor1 = 81,0
+ anchor2 = 97,0
+ saved-params = "is-checked"
+ text = "Report Issue"
+ command = "report-issue"
+
+window "outputwindow"
+ elem "outputwindow"
+ type = MAIN
+ pos = 0,0
+ size = 640x480
+ anchor1 = none
+ anchor2 = none
+ saved-params = "pos;size;is-minimized;is-maximized"
+ is-pane = true
+ elem "input"
+ type = INPUT
+ pos = 2,460
+ size = 595x20
+ anchor1 = 0,100
+ anchor2 = 100,100
+ background-color = #d3b5b5
+ is-default = true
+ border = sunken
+ saved-params = "command"
+ elem "saybutton"
+ type = BUTTON
+ pos = 600,460
+ size = 37x20
+ anchor1 = 100,100
+ anchor2 = none
+ saved-params = "is-checked"
+ text = "Chat"
+ command = ".winset \"saybutton.is-checked=true ? input.command=\"!say \\\"\" : input.command=\""
+ is-flat = true
+ button-type = pushbox
+ elem "browseroutput"
+ type = BROWSER
+ pos = 0,0
+ size = 640x456
+ anchor1 = 0,0
+ anchor2 = 100,100
+ background-color = #ffffff
+ is-visible = false
+ is-disabled = true
+ saved-params = ""
+ auto-format = false
+ elem "output"
+ type = OUTPUT
+ pos = 0,0
+ size = 640x456
+ anchor1 = 0,0
+ anchor2 = 100,100
+ is-default = true
+ saved-params = ""
+
+window "popupwindow"
+ elem "popupwindow"
+ type = MAIN
+ pos = 281,0
+ size = 120x120
+ anchor1 = none
+ anchor2 = none
+ background-color = none
+ is-visible = false
+ saved-params = "pos;size;is-minimized;is-maximized"
+ statusbar = false
+ can-resize = false
+
+window "preferences_window"
+ elem "preferences_window"
+ type = MAIN
+ pos = 281,0
+ size = 1280x1000
+ anchor1 = none
+ anchor2 = none
+ is-visible = false
+ saved-params = "pos;size;is-minimized;is-maximized"
+ statusbar = false
+ elem "preferences_browser"
+ type = BROWSER
+ pos = 0,0
+ size = 960x1000
+ anchor1 = 0,0
+ anchor2 = 75,100
+ saved-params = ""
+ elem "character_preview_map"
+ type = MAP
+ pos = 960,0
+ size = 320x1000
+ anchor1 = 75,0
+ anchor2 = 100,100
+ right-click = true
+ saved-params = "zoom;letterbox;zoom-mode"
+
+window "statwindow"
+ elem "statwindow"
+ type = MAIN
+ pos = 281,0
+ size = 640x480
+ anchor1 = none
+ anchor2 = none
+ saved-params = "pos;size;is-minimized;is-maximized"
+ is-pane = true
+ elem "stat"
+ type = INFO
+ pos = 0,0
+ size = 640x480
+ anchor1 = 0,0
+ anchor2 = 100,100
+ is-default = true
+ saved-params = ""
+
diff --git a/interface/stylesheet.dm b/interface/stylesheet.dm
index 9f3d8911ec..f375fea88f 100644
--- a/interface/stylesheet.dm
+++ b/interface/stylesheet.dm
@@ -2,7 +2,7 @@
/// !!!!!!!!!!HEY LISTEN!!!!!!!!!!!!!!!!!!!!!!!!
/// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
-// If you modify this file you ALSO need to modify code/modules/goonchat/browserAssets/browserOutput.css
+// If you modify this file you ALSO need to modify code/modules/goonchat/browserAssets/browserOutput.css and browserOutput_white.css
// BUT you have to use PX font sizes with are on a x8 scale of these font sizes
// Sample font-size: DM: 8 CSS: 64px
@@ -26,18 +26,24 @@ em {font-style: normal; font-weight: bold;}
.prefix { font-weight: bold;}
.ooc { font-weight: bold;}
+.looc {color: #6699CC; font-weight: bold;}
+.antagooc {color: #b8002e; font-weight: bold;}
.adminobserverooc {color: #0099cc; font-weight: bold;}
.adminooc {color: #700038; font-weight: bold;}
.adminobserver {color: #996600; font-weight: bold;}
+.adminsay {color: #FF4500; font-weight: bold;}
.admin {color: #386aff; font-weight: bold;}
+.mentorsay {color: #E236D8; font-weight: bold;}
+.mentorsay_admin {color: #8A2BE2; font-weight: bold;}
+
.name { font-weight: bold;}
.say {}
.deadsay {color: #5c00e6;}
-.binarysay {color: #20c20e; background-color: #000000; display: block;}
-.binarysay a {color: #00ff00;}
+.binarysay {color: #20c20e; background-color: #000000; display: block;}
+.binarysay a {color: #00ff00;}
.binarysay a:active, .binarysay a:visited {color: #88ff88;}
.radio {color: #008000;}
.sciradio {color: #993399;}
@@ -50,8 +56,8 @@ em {font-style: normal; font-weight: bold;}
.syndradio {color: #6d3f40;}
.centcomradio {color: #686868;}
.aiprivradio {color: #ff00ff;}
-.redteamradio {color: #ff0000;}
-.blueteamradio {color: #0000ff;}
+.redteamradio {color: #ff0000;}
+.blueteamradio {color: #0000ff;}
.yell { font-weight: bold;}
@@ -65,40 +71,47 @@ h1.alert, h2.alert {color: #000000;}
.disarm {color: #990000;}
.passive {color: #660000;}
-.userdanger {color: #ff0000; font-weight: bold; font-size: 3;}
-.danger {color: #ff0000;}
+.userdanger {color: #ff0000; font-weight: bold; font-size: 3;}
+.danger {color: #ff0000; font-weight: bold;}
+.tinydanger {color: #ff0000; font-size: 85%;}
+.smalldanger {color: #ff0000; font-size: 90%;}
.warning {color: #ff0000; font-style: italic;}
.boldwarning {color: #ff0000; font-style: italic; font-weight: bold}
-.announce {color: #228b22; font-weight: bold;}
+.announce {color: #228b22; font-weight: bold;}
.boldannounce {color: #ff0000; font-weight: bold;}
.greenannounce {color: #00ff00; font-weight: bold;}
.rose {color: #ff5050;}
.info {color: #0000CC;}
.notice {color: #000099;}
+.tinynotice {color: #000099; font-size: 85%;}
+.smallnotice {color: #000099; font-size: 90%;}
+.smallnoticeital {color: #000099; font-style: italic; font-size: 90%;}
.boldnotice {color: #000099; font-weight: bold;}
+.hear {color: #000099; font-style: italic;}
.adminnotice {color: #0000ff;}
-.adminhelp {color: #ff0000; font-weight: bold;}
+.adminhelp {color: #ff0000; font-weight: bold;}
.unconscious {color: #0000ff; font-weight: bold;}
.suicide {color: #ff5050; font-style: italic;}
.green {color: #03ff39;}
-.nicegreen {color: #14a833;}
+.nicegreen {color: #14a833;}
.shadowling {color: #3b2769;}
.cult {color: #960000;}
-.cultlarge {color: #960000; font-weight: bold; font-size: 3;}
-.narsie {color: #960000; font-weight: bold; font-size: 15;}
-.narsiesmall {color: #960000; font-weight: bold; font-size: 6;}
-.colossus {color: #7F282A; font-size: 5;}
-.hierophant {color: #660099; font-weight: bold; font-style: italic;}
-.hierophant_warning {color: #660099; font-style: italic;}
+.cultlarge {color: #960000; font-weight: bold; font-size: 3;}
+.narsie {color: #960000; font-weight: bold; font-size: 15;}
+.narsiesmall {color: #960000; font-weight: bold; font-size: 6;}
+.colossus {color: #7F282A; font-size: 5;}
+.hierophant {color: #660099; font-weight: bold; font-style: italic;}
+.hierophant_warning {color: #660099; font-style: italic;}
.purple {color: #5e2d79;}
.holoparasite {color: #35333a;}
+.bounty {color: #ab6613; font-style: italic;}
.revennotice {color: #1d2953;}
.revenboldnotice {color: #1d2953; font-weight: bold;}
-.revenbignotice {color: #1d2953; font-weight: bold; font-size: 3;}
+.revenbignotice {color: #1d2953; font-weight: bold; font-size: 3;}
.revenminor {color: #823abb}
.revenwarning {color: #760fbb; font-style: italic;}
-.revendanger {color: #760fbb; font-weight: bold; font-size: 3;}
+.revendanger {color: #760fbb; font-weight: bold; font-size: 3;}
.umbra {color: #5000A0;}
.umbra_emphasis {color: #5000A0; font-weight: bold; font-style: italic;}
.umbra_large {color: #5000A0; font-size: 3; font-weight: bold; font-style: italic;}
@@ -128,8 +141,7 @@ h1.alert, h2.alert {color: #000000;}
.neovgre {color: #6E001A; font-weight: bold; font-style: italic;}
.neovgre_small {color: #6E001A;}
-.newscaster {color: #800000;}
-.ghostalert {color: #5c00e6; font-style: italic; font-weight: bold;}
+.ghostalert {color: #5c00e6; font-style: italic; font-weight: bold;}
.alien {color: #543354;}
.noticealien {color: #00c000;}
@@ -144,7 +156,7 @@ h1.alert, h2.alert {color: #000000;}
.papyrus {font-family: "Papyrus", cursive, sans-serif;}
.robot {font-family: "Courier New", cursive, sans-serif;}
-.command_headset {font-weight: bold; font-size: 3;}
+.command_headset {font-weight: bold; font-size: 3;}
.small {font-size: 1;}
.big {font-size: 3;}
.reallybig {font-size: 4;}
@@ -152,9 +164,9 @@ h1.alert, h2.alert {color: #000000;}
.greentext {color: #00FF00; font-size: 3;}
.redtext {color: #FF0000; font-size: 3;}
.yellowtext {color: #FFCC00; font-size: 3;}
-.clown {color: #FF69Bf; font-size: 3; font-family: "Comic Sans MS", cursive, sans-serif; font-weight: bold;}
-.his_grace {color: #15D512; font-family: "Courier New", cursive, sans-serif; font-style: italic;}
-.spooky {color: #FF9100;}
+.clown {color: #FF69Bf; font-size: 3; font-family: "Comic Sans MS", cursive, sans-serif; font-weight: bold;}
+.singing {font-family: "Trebuchet MS", cursive, sans-serif; font-style: italic;}
+.his_grace {color: #15D512; font-family: "Courier New", cursive, sans-serif; font-style: italic;}
.velvet {color: #660015; font-weight: bold; animation: velvet 5000ms infinite;}
@keyframes velvet {
0% { color: #400020; }
@@ -164,28 +176,29 @@ h1.alert, h2.alert {color: #000000;}
100% { color: #400020; }
}
-.hypnophrase {color: #3bb5d3; font-weight: bold; animation: hypnocolor 1500ms infinite;}
-@keyframes hypnocolor {
- 0% { color: #0d0d0d; }
- 25% { color: #410194; }
- 50% { color: #7f17d8; }
- 75% { color: #410194; }
- 100% { color: #3bb5d3; }
+.hypnophrase {color: #3bb5d3; font-weight: bold; animation: hypnocolor 1500ms infinite; animation-direction: alternate;}
+ @keyframes hypnocolor {
+ 0% {color: #0d0d0d;}
+ 25% {color: #410194;}
+ 50% {color: #7f17d8;}
+ 75% {color: #410194;}
+ 100% {color: #3bb5d3;}
}
-.phobia {color: #dd0000; font-weight: bold; animation: phobia 750ms infinite;}
+
+.phobia {color: #dd0000; font-weight: bold; animation: phobia 750ms infinite;}
@keyframes phobia {
- 0% { color: #0d0d0d; }
- 50% { color: #dd0000; }
- 100% { color: #0d0d0d; }
+ 0% {color: #0d0d0d;}
+ 50% {color: #dd0000;}
+ 100% {color: #0d0d0d;}
}
-.icon {height: 1em; width: auto;}
+.icon {height: 1em; width: auto;}
.memo {color: #638500; text-align: center;}
.memoedit {text-align: center; font-size: 2;}
-.abductor {color: #800080; font-style: italic;}
-.mind_control {color: #A00D6F; font-size: 3; font-weight: bold; font-style: italic;}
+.abductor {color: #800080; font-style: italic;}
+.mind_control {color: #A00D6F; font-size: 3; font-weight: bold; font-style: italic;}
.slime {color: #00CED1;}
.drone {color: #848482;}
.monkey {color: #975032;}
diff --git a/libbyond-extools.so b/libbyond-extools.so
index 8e17f952f2..052a8cb037 100644
Binary files a/libbyond-extools.so and b/libbyond-extools.so differ
diff --git a/modular_citadel/code/datums/status_effects/chems.dm b/modular_citadel/code/datums/status_effects/chems.dm
index 4fa2a51fac..0e971d4ced 100644
--- a/modular_citadel/code/datums/status_effects/chems.dm
+++ b/modular_citadel/code/datums/status_effects/chems.dm
@@ -583,16 +583,6 @@
C.Stun(60)
to_chat(owner, "Your muscles seize up, then start spasming wildy!")
- //wah intensifies wah-rks
- else if (lowertext(customTriggers[trigger]) == "cum")//aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
- if (lewd)
- if(ishuman(C))
- var/mob/living/carbon/human/H = C
- H.mob_climax(forced_climax=TRUE)
- C.SetStun(10)//We got your stun effects in somewhere, Kev.
- else
- C.throw_at(get_step_towards(hearing_args[HEARING_SPEAKER],C), 3, 1) //cut this if it's too hard to get working
-
//kneel (knockdown)
else if (lowertext(customTriggers[trigger]) == "kneel")//as close to kneeling as you can get, I suppose.
to_chat(owner, "You drop to the ground unsurreptitiously.")
@@ -674,15 +664,6 @@
deltaResist *= 1.25
if (owner.reagents.has_reagent(/datum/reagent/medicine/neurine))
deltaResist *= 1.5
- if (!(owner.client?.prefs.cit_toggles & NO_APHRO) && lewd)
- if (owner.reagents.has_reagent(/datum/reagent/drug/anaphrodisiac))
- deltaResist *= 1.5
- if (owner.reagents.has_reagent(/datum/reagent/drug/anaphrodisiacplus))
- deltaResist *= 2
- if (owner.reagents.has_reagent(/datum/reagent/drug/aphrodisiac))
- deltaResist *= 0.75
- if (owner.reagents.has_reagent(/datum/reagent/drug/aphrodisiacplus))
- deltaResist *= 0.5
//Antag resistance
//cultists are already brainwashed by their god
if(iscultist(owner))
diff --git a/modular_citadel/code/modules/client/loadout/__donator.dm b/modular_citadel/code/modules/client/loadout/__donator.dm
index d428fc290a..5add1e3f87 100644
--- a/modular_citadel/code/modules/client/loadout/__donator.dm
+++ b/modular_citadel/code/modules/client/loadout/__donator.dm
@@ -1,496 +1,518 @@
//This is the file that handles donator loadout items.
-/datum/gear/pingcoderfailsafe
+/datum/gear/donator
name = "IF YOU SEE THIS, PING A CODER RIGHT NOW!"
- category = SLOT_IN_BACKPACK
+ slot = SLOT_IN_BACKPACK
path = /obj/item/bikehorn/golden
+ category = LOADOUT_CATEGORY_DONATOR
ckeywhitelist = list("This entry should never appear with this variable set.") //If it does, then that means somebody fucked up the whitelist system pretty hard
-/datum/gear/donortestingbikehorn
+/datum/gear/donator/pet
+ name = "Pet Beacon"
+ slot = SLOT_IN_BACKPACK
+ path = /obj/item/choice_beacon/pet
+ ckeywhitelist = list()
+ donator_group_id = DONATOR_GROUP_TIER_1 // can be accessed by all donators
+
+/datum/gear/donator/carpet
+ name = "Carpet Beacon"
+ slot = SLOT_IN_BACKPACK
+ path = /obj/item/choice_beacon/box/carpet
+ ckeywhitelist = list()
+ donator_group_id = DONATOR_GROUP_TIER_1
+
+/datum/gear/donator/chameleon_bedsheet
+ name = "Chameleon Bedsheet"
+ slot = SLOT_NECK
+ path = /obj/item/bedsheet/chameleon
+ ckeywhitelist = list()
+ donator_group_id = DONATOR_GROUP_TIER_1
+
+/datum/gear/donator/donortestingbikehorn
name = "Donor item testing bikehorn"
- category = SLOT_IN_BACKPACK
+ slot = SLOT_IN_BACKPACK
path = /obj/item/bikehorn
geargroupID = list("DONORTEST") //This is a list mainly for the sake of testing, but geargroupID works just fine with ordinary strings
-/datum/gear/kevhorn
+/datum/gear/donator/kevhorn
name = "Airhorn"
- category = SLOT_IN_BACKPACK
+ slot = SLOT_IN_BACKPACK
path = /obj/item/bikehorn/airhorn
ckeywhitelist = list("kevinz000")
-/datum/gear/cebusoap
- name = "Cebutris' soap"
- category = SLOT_IN_BACKPACK
- path = /obj/item/custom/ceb_soap
- ckeywhitelist = list("cebutris")
-
-/datum/gear/kiaracloak
+/datum/gear/donator/kiaracloak
name = "Kiara's cloak"
- category = SLOT_NECK
+ slot = SLOT_NECK
path = /obj/item/clothing/neck/cloak/inferno
ckeywhitelist = list("inferno707")
-/datum/gear/kiaracollar
+/datum/gear/donator/kiaracollar
name = "Kiara's collar"
- category = SLOT_NECK
+ slot = SLOT_NECK
path = /obj/item/clothing/neck/petcollar/inferno
ckeywhitelist = list("inferno707")
-/datum/gear/kiaramedal
+/datum/gear/donator/kiaramedal
name = "Insignia of Steele"
- category = SLOT_IN_BACKPACK
+ slot = SLOT_IN_BACKPACK
path = /obj/item/clothing/accessory/medal/steele
ckeywhitelist = list("inferno707")
-/datum/gear/hheart
+/datum/gear/donator/hheart
name = "The Hollow Heart"
- category = SLOT_WEAR_MASK
+ slot = SLOT_WEAR_MASK
path = /obj/item/clothing/mask/hheart
ckeywhitelist = list("inferno707")
-/datum/gear/engravedzippo
+/datum/gear/donator/engravedzippo
name = "Engraved zippo"
- category = SLOT_HANDS
+ slot = SLOT_HANDS
path = /obj/item/lighter/gold
ckeywhitelist = list("dirtyoldharry")
-/datum/gear/geisha
+/datum/gear/donator/geisha
name = "Geisha suit"
- category = SLOT_W_UNIFORM
+ slot = SLOT_W_UNIFORM
path = /obj/item/clothing/under/costume/geisha
ckeywhitelist = list("atiefling")
-/datum/gear/specialscarf
+/datum/gear/donator/specialscarf
name = "Special scarf"
- category = SLOT_NECK
+ slot = SLOT_NECK
path = /obj/item/clothing/neck/scarf/zomb
ckeywhitelist = list("zombierobin")
-/datum/gear/redmadcoat
+/datum/gear/donator/redmadcoat
name = "The Mad's labcoat"
- category = SLOT_WEAR_SUIT
+ slot = SLOT_WEAR_SUIT
path = /obj/item/clothing/suit/toggle/labcoat/mad/red
ckeywhitelist = list("zombierobin")
-/datum/gear/santahat
+/datum/gear/donator/santahat
name = "Santa hat"
- category = SLOT_HEAD
+ slot = SLOT_HEAD
path = /obj/item/clothing/head/santa/fluff
ckeywhitelist = list("illotafv")
-/datum/gear/reindeerhat
+/datum/gear/donator/reindeerhat
name = "Reindeer hat"
- category = SLOT_HEAD
+ slot = SLOT_HEAD
path = /obj/item/clothing/head/hardhat/reindeer/fluff
ckeywhitelist = list("illotafv")
-/datum/gear/treeplushie
+/datum/gear/donator/treeplushie
name = "Christmas tree plushie"
- category = SLOT_IN_BACKPACK
+ slot = SLOT_IN_BACKPACK
path = /obj/item/toy/plush/tree
ckeywhitelist = list("illotafv")
-/datum/gear/santaoutfit
+/datum/gear/donator/santaoutfit
name = "Santa costume"
- category = SLOT_WEAR_SUIT
+ slot = SLOT_WEAR_SUIT
path = /obj/item/clothing/suit/space/santa/fluff
ckeywhitelist = list("illotafv")
-/datum/gear/treecloak
+/datum/gear/donator/treecloak
name = "Christmas tree cloak"
- category = SLOT_NECK
+ slot = SLOT_NECK
path = /obj/item/clothing/neck/cloak/festive
ckeywhitelist = list("illotafv")
-/datum/gear/carrotplush
+/datum/gear/donator/carrotplush
name = "Carrot plushie"
- category = SLOT_IN_BACKPACK
+ slot = SLOT_IN_BACKPACK
path = /obj/item/toy/plush/carrot
ckeywhitelist = list("improvedname")
-/datum/gear/carrotcloak
+/datum/gear/donator/carrotcloak
name = "Carrot cloak"
- category = SLOT_NECK
+ slot = SLOT_NECK
path = /obj/item/clothing/neck/cloak/carrot
ckeywhitelist = list("improvedname")
-/datum/gear/albortorosamask
+/datum/gear/donator/albortorosamask
name = "Alborto Rosa mask"
- category = SLOT_WEAR_MASK
+ slot = SLOT_WEAR_MASK
path = /obj/item/clothing/mask/luchador/zigfie
ckeywhitelist = list("zigfie")
-/datum/gear/mankini
+/datum/gear/donator/mankini
name = "Mankini"
- category = SLOT_W_UNIFORM
+ slot = SLOT_W_UNIFORM
path = /obj/item/clothing/under/misc/stripper/mankini
ckeywhitelist = list("zigfie")
-/datum/gear/pinkshoes
+/datum/gear/donator/pinkshoes
name = "Pink shoes"
- category = SLOT_SHOES
+ slot = SLOT_SHOES
path = /obj/item/clothing/shoes/sneakers/pink
ckeywhitelist = list("zigfie")
-/datum/gear/reecesgreatcoat
+/datum/gear/donator/reecesgreatcoat
name = "Reece's Great Coat"
- category = SLOT_WEAR_SUIT
+ slot = SLOT_WEAR_SUIT
path = /obj/item/clothing/suit/trenchcoat/green
ckeywhitelist = list("geemiesif")
-/datum/gear/russianflask
+/datum/gear/donator/russianflask
name = "Russian flask"
- category = SLOT_IN_BACKPACK
+ slot = SLOT_IN_BACKPACK
path = /obj/item/reagent_containers/food/drinks/flask/russian
cost = 2
ckeywhitelist = list("slomka")
-/datum/gear/stalkermask
+/datum/gear/donator/stalkermask
name = "S.T.A.L.K.E.R. mask"
- category = SLOT_WEAR_MASK
+ slot = SLOT_WEAR_MASK
path = /obj/item/clothing/mask/gas/stalker
ckeywhitelist = list("slomka")
-/datum/gear/stripedcollar
+/datum/gear/donator/stripedcollar
name = "Striped collar"
- category = SLOT_NECK
+ slot = SLOT_NECK
path = /obj/item/clothing/neck/petcollar/stripe
ckeywhitelist = list("jademanique")
-/datum/gear/performersoutfit
+/datum/gear/donator/performersoutfit
name = "Bluish performer's outfit"
- category = SLOT_W_UNIFORM
+ slot = SLOT_W_UNIFORM
path = /obj/item/clothing/under/costume/singer/yellow/custom
ckeywhitelist = list("killer402402")
-/datum/gear/vermillion
+/datum/gear/donator/vermillion
name = "Vermillion clothing"
- category = SLOT_W_UNIFORM
+ slot = SLOT_W_UNIFORM
path = /obj/item/clothing/suit/vermillion
ckeywhitelist = list("fractious")
-/datum/gear/AM4B
+/datum/gear/donator/AM4B
name = "Foam Force AM4-B"
- category = SLOT_IN_BACKPACK
+ slot = SLOT_IN_BACKPACK
path = /obj/item/gun/ballistic/automatic/AM4B
ckeywhitelist = list("zeronetalpha")
-/datum/gear/carrotsatchel
+/datum/gear/donator/carrotsatchel
name = "Carrot Satchel"
- category = SLOT_HANDS
+ slot = SLOT_HANDS
path = /obj/item/storage/backpack/satchel/carrot
ckeywhitelist = list("improvedname")
-/datum/gear/naomisweater
+/datum/gear/donator/naomisweater
name = "worn black sweater"
- category = SLOT_W_UNIFORM
+ slot = SLOT_W_UNIFORM
path = /obj/item/clothing/under/sweater/black/naomi
ckeywhitelist = list("technicalmagi")
-/datum/gear/naomicollar
+/datum/gear/donator/naomicollar
name = "worn pet collar"
- category = SLOT_NECK
+ slot = SLOT_NECK
path = /obj/item/clothing/neck/petcollar/naomi
ckeywhitelist = list("technicalmagi")
-/datum/gear/gladiator
+/datum/gear/donator/gladiator
name = "Gladiator Armor"
- category = SLOT_WEAR_SUIT
+ slot = SLOT_WEAR_SUIT
path = /obj/item/clothing/under/costume/gladiator
ckeywhitelist = list("aroche")
-/datum/gear/bloodredtie
+/datum/gear/donator/bloodredtie
name = "Blood Red Tie"
- category = SLOT_NECK
+ slot = SLOT_NECK
path = /obj/item/clothing/neck/tie/bloodred
ckeywhitelist = list("kyutness")
-/datum/gear/puffydress
+/datum/gear/donator/puffydress
name = "Puffy Dress"
- category = SLOT_WEAR_SUIT
+ slot = SLOT_WEAR_SUIT
path = /obj/item/clothing/suit/puffydress
ckeywhitelist = list("stallingratt")
-/datum/gear/labredblack
+/datum/gear/donator/labredblack
name = "Black and Red Coat"
- category = SLOT_WEAR_SUIT
+ slot = SLOT_WEAR_SUIT
path = /obj/item/clothing/suit/toggle/labcoat/labredblack
ckeywhitelist = list("blakeryan", "durandalphor")
-/datum/gear/torisword
+/datum/gear/donator/torisword
name = "Rainbow Zweihander"
- category = SLOT_IN_BACKPACK
+ slot = SLOT_IN_BACKPACK
path = /obj/item/dualsaber/hypereutactic/toy/rainbow
ckeywhitelist = list("annoymous35")
-/datum/gear/darksabre
+/datum/gear/donator/darksabre
name = "Dark Sabre"
- category = SLOT_IN_BACKPACK
+ slot = SLOT_IN_BACKPACK
path = /obj/item/toy/darksabre
ckeywhitelist = list("inferno707")
-datum/gear/darksabresheath
+/datum/gear/donator/darksabresheath
name = "Dark Sabre Sheath"
- category = SLOT_IN_BACKPACK
+ slot = SLOT_IN_BACKPACK
path = /obj/item/storage/belt/sabre/darksabre
ckeywhitelist = list("inferno707")
-/datum/gear/toriball
+/datum/gear/donator/toriball
name = "Rainbow Tennis Ball"
- category = SLOT_IN_BACKPACK
+ slot = SLOT_IN_BACKPACK
path = /obj/item/toy/tennis/rainbow
ckeywhitelist = list("annoymous35")
-/datum/gear/izzyball
+/datum/gear/donator/izzyball
name = "Katlin's Ball"
- category = SLOT_IN_BACKPACK
+ slot = SLOT_IN_BACKPACK
path = /obj/item/toy/tennis/rainbow/izzy
ckeywhitelist = list("izzyinbox")
-/datum/gear/cloak
+/datum/gear/donator/cloak
name = "Green Cloak"
- category = SLOT_NECK
+ slot = SLOT_NECK
path = /obj/item/clothing/neck/cloak/green
ckeywhitelist = list("killer402402")
-/datum/gear/steelflask
+/datum/gear/donator/steelflask
name = "Steel Flask"
- category = SLOT_IN_BACKPACK
+ slot = SLOT_IN_BACKPACK
path = /obj/item/reagent_containers/food/drinks/flask/steel
cost = 2
ckeywhitelist = list("nik707")
-/datum/gear/paperhat
+/datum/gear/donator/paperhat
name = "Paper Hat"
- category = SLOT_HEAD
+ slot = SLOT_HEAD
path = /obj/item/clothing/head/paperhat
ckeywhitelist = list("kered2")
-/datum/gear/cloakce
+/datum/gear/donator/cloakce
name = "Polychromic CE Cloak"
- category = SLOT_IN_BACKPACK
+ slot = SLOT_IN_BACKPACK
path = /obj/item/clothing/neck/cloak/polychromic/polyce
ckeywhitelist = list("worksbythesea", "blakeryan")
-/datum/gear/ssk
+/datum/gear/donator/ssk
name = "Stun Sword Kit"
- category = SLOT_IN_BACKPACK
+ slot = SLOT_IN_BACKPACK
path = /obj/item/ssword_kit
ckeywhitelist = list("phillip458")
-/datum/gear/techcoat
+/datum/gear/donator/techcoat
name = "Techomancers Labcoat"
- category = SLOT_IN_BACKPACK
+ slot = SLOT_IN_BACKPACK
path = /obj/item/clothing/suit/toggle/labcoat/mad/techcoat
ckeywhitelist = list("wilchen")
-/datum/gear/leechjar
+/datum/gear/donator/leechjar
name = "Jar of Leeches"
- category = SLOT_IN_BACKPACK
+ slot = SLOT_IN_BACKPACK
path = /obj/item/custom/leechjar
ckeywhitelist = list("sgtryder")
-/datum/gear/darkarmor
+/datum/gear/donator/darkarmor
name = "Dark Armor"
- category = SLOT_IN_BACKPACK
+ slot = SLOT_IN_BACKPACK
path = /obj/item/clothing/suit/armor/vest/darkcarapace
ckeywhitelist = list("inferno707")
-/datum/gear/devilwings
+/datum/gear/donator/devilwings
name = "Strange Wings"
- category = SLOT_NECK
+ slot = SLOT_NECK
path = /obj/item/clothing/neck/devilwings
ckeywhitelist = list("kitsun")
-/datum/gear/flagcape
+/datum/gear/donator/flagcape
name = "US Flag Cape"
- category = SLOT_IN_BACKPACK
+ slot = SLOT_IN_BACKPACK
path = /obj/item/clothing/neck/flagcape
ckeywhitelist = list("darnchacha")
-/datum/gear/luckyjack
+/datum/gear/donator/luckyjack
name = "Lucky Jackboots"
- category = SLOT_IN_BACKPACK
+ slot = SLOT_IN_BACKPACK
path = /obj/item/clothing/shoes/lucky
ckeywhitelist = list("donaldtrumpthecommunist")
-/datum/gear/raiqbawks
+/datum/gear/donator/raiqbawks
name = "Miami Boombox"
- category = SLOT_HANDS
+ slot = SLOT_HANDS
cost = 2
path = /obj/item/boombox/raiq
ckeywhitelist = list("chefferz")
-/datum/gear/m41
+/datum/gear/donator/m41
name = "Toy M41"
- category = SLOT_IN_BACKPACK
+ slot = SLOT_IN_BACKPACK
path = /obj/item/toy/gun/m41
ckeywhitelist = list("thalverscholen")
-/datum/gear/Divine_robes
+/datum/gear/donator/Divine_robes
name = "Divine robes"
- category = SLOT_W_UNIFORM
+ slot = SLOT_W_UNIFORM
path = /obj/item/clothing/under/custom/lunasune
ckeywhitelist = list("invader4352")
-/datum/gear/gothcoat
+/datum/gear/donator/gothcoat
name = "Goth Coat"
- category = SLOT_WEAR_SUIT
+ slot = SLOT_WEAR_SUIT
path = /obj/item/clothing/suit/gothcoat
ckeywhitelist = list("norko")
-/datum/gear/corgisuit
+/datum/gear/donator/corgisuit
name = "Corgi Suit"
- category = SLOT_WEAR_SUIT
+ slot = SLOT_WEAR_SUIT
path = /obj/item/clothing/suit/hooded/ian_costume
ckeywhitelist = list("cathodetherobot")
-/datum/gear/sharkcloth
+/datum/gear/donator/sharkcloth
name = "Leon's Skimpy Outfit"
- category = SLOT_WEAR_SUIT
+ slot = SLOT_WEAR_SUIT
path = /obj/item/clothing/under/custom/leoskimpy
ckeywhitelist = list("spectrosis")
-/datum/gear/mimemask
+/datum/gear/donator/mimemask
name = "Mime Mask"
- category = SLOT_WEAR_MASK
+ slot = SLOT_WEAR_MASK
path = /obj/item/clothing/mask/gas/mime
ckeywhitelist = list("pireamaineach")
-/datum/gear/mimeoveralls
+/datum/gear/donator/mimeoveralls
name = "Mime's Overalls"
- category = SLOT_WEAR_SUIT
+ slot = SLOT_WEAR_SUIT
path = /obj/item/clothing/under/custom/mimeoveralls
ckeywhitelist = list("pireamaineach")
-/datum/gear/soulneck
+/datum/gear/donator/soulneck
name = "Soul Necklace"
- category = SLOT_NECK
+ slot = SLOT_NECK
path = /obj/item/clothing/neck/undertale
ckeywhitelist = list("twilightic")
-/datum/gear/frenchberet
+/datum/gear/donator/frenchberet
name = "French Beret"
- category = SLOT_HEAD
+ slot = SLOT_HEAD
path = /obj/item/clothing/head/frenchberet
ckeywhitelist = list("notazoltan")
-/datum/gear/zuliecloak
+/datum/gear/donator/zuliecloak
name = "Project: Zul-E"
- category = SLOT_WEAR_SUIT
+ slot = SLOT_WEAR_SUIT
path = /obj/item/clothing/suit/hooded/cloak/zuliecloak
ckeywhitelist = list("asky")
-/datum/gear/blackredgold
+/datum/gear/donator/blackredgold
name = "Black, Red, and Gold Coat"
- category = SLOT_WEAR_SUIT
+ slot = SLOT_WEAR_SUIT
path = /obj/item/clothing/suit/blackredgold
ckeywhitelist = list("ttbnc")
-/datum/gear/fritzplush
+/datum/gear/donator/fritzplush
name = "Fritz Plushie"
- category = SLOT_IN_BACKPACK
+ slot = SLOT_IN_BACKPACK
path = /obj/item/toy/plush/mammal/dog/fritz
ckeywhitelist = list("analwerewolf")
-/datum/gear/kimono
+/datum/gear/donator/kimono
name = "Kimono"
- category = SLOT_WEAR_SUIT
+ slot = SLOT_WEAR_SUIT
path = /obj/item/clothing/suit/kimono
ckeywhitelist = list("sfox63")
-/datum/gear/commjacket
+/datum/gear/donator/commjacket
name = "Dusty Commisar's Cloak"
- category = SLOT_WEAR_SUIT
+ slot = SLOT_WEAR_SUIT
path = /obj/item/clothing/suit/commjacket
ckeywhitelist = list("sadisticbatter")
-/datum/gear/mw2_russian_para
+/datum/gear/donator/mw2_russian_para
name = "Russian Paratrooper Jumper"
- category = SLOT_W_UNIFORM
+ slot = SLOT_W_UNIFORM
path = /obj/item/clothing/under/custom/mw2_russian_para
ckeywhitelist = list("investigator77")
-/datum/gear/longblackgloves
+/datum/gear/donator/longblackgloves
name = "Luna's Gauntlets"
- category = SLOT_GLOVES
+ slot = SLOT_GLOVES
path = /obj/item/clothing/gloves/longblackgloves
ckeywhitelist = list("bigmanclancy")
-/datum/gear/trendy_fit
+/datum/gear/donator/trendy_fit
name = "Trendy Fit"
- category = SLOT_W_UNIFORM
+ slot = SLOT_W_UNIFORM
path = /obj/item/clothing/under/custom/trendy_fit
ckeywhitelist = list("midgetdragon")
-/datum/gear/singery
+/datum/gear/donator/singery
name = "Yellow Performer Outfit"
- category = SLOT_W_UNIFORM
+ slot = SLOT_W_UNIFORM
path = /obj/item/clothing/under/costume/singer/yellow
ckeywhitelist = list("maxlynchy")
-/datum/gear/csheet
+/datum/gear/donator/csheet
name = "NT Bedsheet"
- category = SLOT_NECK
+ slot = SLOT_NECK
path = /obj/item/bedsheet/captain
ckeywhitelist = list("tikibomb")
-/datum/gear/borgplush
+/datum/gear/donator/borgplush
name = "Robot Plush"
- category = SLOT_IN_BACKPACK
+ slot = SLOT_IN_BACKPACK
path = /obj/item/toy/plush/borgplushie
ckeywhitelist = list("nicholaiavenicci")
-/datum/gear/donorberet
+/datum/gear/donator/donorberet
name = "Atmos Beret"
- category = SLOT_HEAD
+ slot = SLOT_HEAD
path = /obj/item/clothing/head/blueberet
ckeywhitelist = list("foxystalin")
-/datum/gear/donorgoggles
+/datum/gear/donator/donorgoggles
name = "Flight Goggles"
- category = SLOT_HEAD
+ slot = SLOT_HEAD
path = /obj/item/clothing/head/flight
ckeywhitelist = list("maxlynchy")
-/datum/gear/onionneck
+/datum/gear/donator/onionneck
name = "Onion Necklace"
- category = SLOT_NECK
+ slot = SLOT_NECK
path = /obj/item/clothing/neck/necklace/onion
ckeywhitelist = list("cdrcross")
-/datum/gear/mikubikini
+/datum/gear/donator/mikubikini
name = "starlight singer bikini"
- category = SLOT_W_UNIFORM
+ slot = SLOT_W_UNIFORM
path = /obj/item/clothing/under/custom/mikubikini
ckeywhitelist = list("grandvegeta")
-/datum/gear/mikujacket
+/datum/gear/donator/mikujacket
name = "starlight singer jacket"
- category = SLOT_WEAR_SUIT
+ slot = SLOT_WEAR_SUIT
path = /obj/item/clothing/suit/mikujacket
ckeywhitelist = list("grandvegeta")
-/datum/gear/mikuhair
+/datum/gear/donator/mikuhair
name = "starlight singer hair"
- category = SLOT_W_UNIFORM
+ slot = SLOT_W_UNIFORM
path = /obj/item/clothing/head/mikuhair
ckeywhitelist = list("grandvegeta")
-/datum/gear/mikugloves
+/datum/gear/donator/mikugloves
name = "starlight singer gloves"
- category = SLOT_GLOVES
+ slot = SLOT_GLOVES
path = /obj/item/clothing/gloves/mikugloves
ckeywhitelist = list("grandvegeta")
-/datum/gear/mikuleggings
+/datum/gear/donator/mikuleggings
name = "starlight singer leggings"
- category = SLOT_SHOES
+ slot = SLOT_SHOES
path = /obj/item/clothing/shoes/sneakers/mikuleggings
ckeywhitelist = list("grandvegeta")
-/datum/gear/cosmos
+/datum/gear/donator/cosmos
name = "cosmic space bedsheet"
- category = SLOT_IN_BACKPACK
+ slot = SLOT_IN_BACKPACK
path = /obj/item/bedsheet/cosmos
ckeywhitelist = list("grunnyyy")
+
+/datum/gear/donator/hisakaki
+ name = "halo"
+ slot = SLOT_HEAD
+ path = /obj/item/clothing/head/halo
+ ckeywhitelist = list("hisakaki")
diff --git a/modular_citadel/code/modules/client/loadout/_loadout.dm b/modular_citadel/code/modules/client/loadout/_loadout.dm
index 51256f8cde..0ebfa060f2 100644
--- a/modular_citadel/code/modules/client/loadout/_loadout.dm
+++ b/modular_citadel/code/modules/client/loadout/_loadout.dm
@@ -27,8 +27,13 @@ GLOBAL_LIST_EMPTY(loadout_whitelist_ids)
/proc/initialize_global_loadout_items()
load_loadout_config()
for(var/item in subtypesof(/datum/gear))
- var/datum/gear/I = new item
- LAZYSET(GLOB.loadout_items[slot_to_string(I.category)], I.name, I)
+ var/datum/gear/I = item
+ if(!initial(I.name))
+ continue
+ I = new item
+ LAZYINITLIST(GLOB.loadout_items[I.category])
+ LAZYINITLIST(GLOB.loadout_items[I.category][I.subcategory])
+ GLOB.loadout_items[I.category][I.subcategory][I.name] = I
if(islist(I.geargroupID))
var/list/ggidlist = I.geargroupID
I.ckeywhitelist = list()
@@ -41,7 +46,9 @@ GLOBAL_LIST_EMPTY(loadout_whitelist_ids)
/datum/gear
var/name
- var/category
+ var/category = LOADOUT_CATEGORY_NONE
+ var/subcategory = LOADOUT_SUBCATEGORY_NONE
+ var/slot
var/description
var/path //item-to-spawn path
var/cost = 1 //normally, each loadout costs a single point.
diff --git a/modular_citadel/code/modules/client/loadout/_medical.dm b/modular_citadel/code/modules/client/loadout/_medical.dm
index 604a0f96ae..e371db94fc 100644
--- a/modular_citadel/code/modules/client/loadout/_medical.dm
+++ b/modular_citadel/code/modules/client/loadout/_medical.dm
@@ -1,47 +1,45 @@
-/datum/gear/medicbriefcase
+/datum/gear/hands/medicbriefcase
name = "Medical Briefcase"
- category = SLOT_HANDS
path = /obj/item/storage/briefcase/medical
restricted_roles = list("Medical Doctor", "Chief Medical Officer")
restricted_desc = "MD, CMO"
-/datum/gear/stethoscope
+/datum/gear/neck/stethoscope
name = "Stethoscope"
- category = SLOT_NECK
path = /obj/item/clothing/neck/stethoscope
restricted_roles = list("Medical Doctor", "Chief Medical Officer")
-/datum/gear/bluescrubs
+/datum/gear/uniform/bluescrubs
name = "Blue Scrubs"
- category = SLOT_W_UNIFORM
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_JOBS
path = /obj/item/clothing/under/rank/medical/doctor/blue
restricted_roles = list("Medical Doctor", "Chief Medical Officer", "Geneticist", "Chemist", "Virologist")
restricted_desc = "Medical"
-
-/datum/gear/greenscrubs
+
+/datum/gear/uniform/greenscrubs
name = "Green Scrubs"
- category = SLOT_W_UNIFORM
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_JOBS
path = /obj/item/clothing/under/rank/medical/doctor/green
restricted_roles = list("Medical Doctor", "Chief Medical Officer", "Geneticist", "Chemist", "Virologist")
restricted_desc = "Medical"
-/datum/gear/purplescrubs
+/datum/gear/uniform/purplescrubs
name = "Purple Scrubs"
- category = SLOT_W_UNIFORM
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_JOBS
path = /obj/item/clothing/under/rank/medical/doctor/purple
restricted_roles = list("Medical Doctor", "Chief Medical Officer", "Geneticist", "Chemist", "Virologist")
restricted_desc = "Medical"
-/datum/gear/nursehat
+/datum/gear/head/nursehat
name = "Nurse Hat"
- category = SLOT_HEAD
path = /obj/item/clothing/head/nursehat
+ subcategory = LOADOUT_SUBCATEGORY_HEAD_JOBS
restricted_roles = list("Medical Doctor", "Chief Medical Officer", "Geneticist", "Chemist", "Virologist")
restricted_desc = "Medical"
-/datum/gear/nursesuit
+/datum/gear/uniform/nursesuit
name = "Nurse Suit"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/rank/medical/doctor/nurse
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_JOBS
restricted_roles = list("Medical Doctor", "Chief Medical Officer", "Geneticist", "Chemist", "Virologist")
- restricted_desc = "Medical"
\ No newline at end of file
+ restricted_desc = "Medical"
diff --git a/modular_citadel/code/modules/client/loadout/_security.dm b/modular_citadel/code/modules/client/loadout/_security.dm
index 72a6aab394..ab316d577b 100644
--- a/modular_citadel/code/modules/client/loadout/_security.dm
+++ b/modular_citadel/code/modules/client/loadout/_security.dm
@@ -1,71 +1,70 @@
-/datum/gear/navyblueuniformhos
+/datum/gear/uniform/navyblueuniformhos
name = "Head of Security navyblue uniform"
- category = SLOT_W_UNIFORM
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_JOBS
path = /obj/item/clothing/under/rank/security/head_of_security/formal
restricted_roles = list("Head of Security")
-/datum/gear/navybluehosberet
+/datum/gear/head/navybluehosberet
name = "Head of security's navyblue beret"
- category = SLOT_HEAD
path = /obj/item/clothing/head/beret/sec/navyhos
+ subcategory = LOADOUT_SUBCATEGORY_HEAD_JOBS
restricted_roles = list("Head of Security")
-/datum/gear/navybluejackethos
+/datum/gear/suit/navybluejackethos
name = "head of security's navyblue jacket"
- category = SLOT_WEAR_SUIT
+ subcategory = LOADOUT_SUBCATEGORY_SUIT_JOBS
path = /obj/item/clothing/suit/armor/hos/navyblue
restricted_roles = list("Head of Security")
-/datum/gear/navybluejacketofficer
+/datum/gear/suit/navybluejacketofficer
name = "security officer's navyblue jacket"
- category = SLOT_WEAR_SUIT
+ subcategory = LOADOUT_SUBCATEGORY_SUIT_JOBS
path = /obj/item/clothing/suit/armor/navyblue
restricted_roles = list("Security Officer")
-/datum/gear/navyblueofficerberet
+/datum/gear/head/navyblueofficerberet
name = "Security officer's Navyblue beret"
- category = SLOT_HEAD
path = /obj/item/clothing/head/beret/sec/navyofficer
+ subcategory = LOADOUT_SUBCATEGORY_HEAD_JOBS
restricted_roles = list("Security Officer")
-/datum/gear/navyblueuniformofficer
+/datum/gear/uniform/navyblueuniformofficer
name = "Security officer navyblue uniform"
- category = SLOT_W_UNIFORM
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_JOBS
path = /obj/item/clothing/under/rank/security/officer/formal
restricted_roles = list("Security Officer")
-/datum/gear/navybluejacketwarden
+/datum/gear/suit/navybluejacketwarden
name = "warden navyblue jacket"
- category = SLOT_WEAR_SUIT
+ subcategory = LOADOUT_SUBCATEGORY_SUIT_JOBS
path = /obj/item/clothing/suit/armor/vest/warden/navyblue
restricted_roles = list("Warden")
-/datum/gear/navybluewardenberet
+/datum/gear/head/navybluewardenberet
name = "Warden's navyblue beret"
- category = SLOT_HEAD
path = /obj/item/clothing/head/beret/sec/navywarden
+ subcategory = LOADOUT_SUBCATEGORY_HEAD_JOBS
restricted_roles = list("Warden")
-/datum/gear/navyblueuniformwarden
+/datum/gear/uniform/navyblueuniformwarden
name = "Warden navyblue uniform"
- category = SLOT_W_UNIFORM
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_JOBS
path = /obj/item/clothing/under/rank/security/warden/formal
restricted_roles = list("Warden")
-/datum/gear/secskirt
+/datum/gear/uniform/secskirt
name = "Security skirt"
- category = SLOT_W_UNIFORM
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_JOBS
path = /obj/item/clothing/under/rank/security/officer/skirt
restricted_roles = list("Security Officer", "Warden", "Head of Security")
-/datum/gear/hosskirt
+/datum/gear/uniform/hosskirt
name = "Head of security's skirt"
- category = SLOT_W_UNIFORM
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_JOBS
path = /obj/item/clothing/under/rank/security/head_of_security/skirt
restricted_roles = list("Head of Security")
-/datum/gear/sechud
+/datum/gear/glasses/sechud
name = "Security Hud"
- category = SLOT_GLASSES
path = /obj/item/clothing/glasses/hud/security
- restricted_roles = list("Security Officer", "Warden", "Head of Security")
\ No newline at end of file
+ restricted_roles = list("Security Officer", "Warden", "Head of Security")
diff --git a/modular_citadel/code/modules/client/loadout/_service.dm b/modular_citadel/code/modules/client/loadout/_service.dm
index ab3daa5f3c..848ad6233c 100644
--- a/modular_citadel/code/modules/client/loadout/_service.dm
+++ b/modular_citadel/code/modules/client/loadout/_service.dm
@@ -1,33 +1,33 @@
-/datum/gear/greytidestationwide
+/datum/gear/uniform/greytidestationwide
name = "Staff Assistant's jumpsuit"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/misc/staffassistant
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_JOBS
restricted_roles = list("Assistant")
-/datum/gear/neetsuit
+/datum/gear/suit/neetsuit
name = "D.A.B. suit"
- category = SLOT_WEAR_SUIT
path = /obj/item/clothing/suit/assu_suit
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_JOBS
restricted_roles = list("Assistant")
cost = 2
-/datum/gear/neethelm
+/datum/gear/head/neethelm
name = "D.A.B. helmet"
- category = SLOT_HEAD
path = /obj/item/clothing/head/assu_helmet
+ subcategory = LOADOUT_SUBCATEGORY_HEAD_JOBS
restricted_roles = list("Assistant")
cost = 2
-/datum/gear/plushvar
+/datum/gear/backpack/plushvar
name = "Ratvar Plushie"
- category = SLOT_IN_BACKPACK
path = /obj/item/toy/plush/plushvar
+ subcategory = LOADOUT_SUBCATEGORY_BACKPACK_TOYS
cost = 5
restricted_roles = list("Chaplain")
-/datum/gear/narplush
+/datum/gear/backpack/narplush
name = "Narsie Plushie"
- category = SLOT_IN_BACKPACK
path = /obj/item/toy/plush/narplush
+ subcategory = LOADOUT_SUBCATEGORY_BACKPACK_TOYS
cost = 5
restricted_roles = list("Chaplain")
diff --git a/modular_citadel/code/modules/client/loadout/backpack.dm b/modular_citadel/code/modules/client/loadout/backpack.dm
index 690e012840..0b700b11e2 100644
--- a/modular_citadel/code/modules/client/loadout/backpack.dm
+++ b/modular_citadel/code/modules/client/loadout/backpack.dm
@@ -1,112 +1,104 @@
-/datum/gear/plushbox
+/datum/gear/backpack
+ category = LOADOUT_CATEGORY_BACKPACK
+ subcategory = LOADOUT_SUBCATEGORY_BACKPACK_GENERAL
+ slot = SLOT_IN_BACKPACK
+
+/datum/gear/backpack/plushbox
name = "Plushie Choice Box"
- category = SLOT_IN_BACKPACK
path = /obj/item/choice_beacon/box/plushie
+ subcategory = LOADOUT_SUBCATEGORY_BACKPACK_TOYS
-/datum/gear/tennis
+/datum/gear/backpack/tennis
name = "Classic Tennis Ball"
- category = SLOT_IN_BACKPACK
path = /obj/item/toy/tennis
+ subcategory = LOADOUT_SUBCATEGORY_BACKPACK_TOYS
-/datum/gear/tennisred
+/datum/gear/backpack/tennis/red
name = "Red Tennis Ball"
- category = SLOT_IN_BACKPACK
path = /obj/item/toy/tennis/red
-/datum/gear/tennisyellow
+/datum/gear/backpack/tennis/yellow
name = "Yellow Tennis Ball"
- category = SLOT_IN_BACKPACK
path = /obj/item/toy/tennis/yellow
-/datum/gear/tennisgreen
+/datum/gear/backpack/tennis/green
name = "Green Tennis Ball"
- category = SLOT_IN_BACKPACK
path = /obj/item/toy/tennis/green
-/datum/gear/tenniscyan
+/datum/gear/backpack/tennis/cyan
name = "Cyan Tennis Ball"
- category = SLOT_IN_BACKPACK
path = /obj/item/toy/tennis/cyan
-/datum/gear/tennisblue
+/datum/gear/backpack/tennis/blue
name = "Blue Tennis Ball"
- category = SLOT_IN_BACKPACK
path = /obj/item/toy/tennis/blue
-/datum/gear/tennispurple
+/datum/gear/backpack/tennis/purple
name = "Purple Tennis Ball"
- category = SLOT_IN_BACKPACK
path = /obj/item/toy/tennis/purple
-/datum/gear/dildo
+/datum/gear/backpack/dildo
name = "Customizable dildo"
- category = SLOT_IN_BACKPACK
path = /obj/item/dildo/custom
+ subcategory = LOADOUT_SUBCATEGORY_BACKPACK_TOYS
-/datum/gear/toykatana
+/datum/gear/backpack/toykatana
name = "Toy Katana"
- category = SLOT_IN_BACKPACK
path = /obj/item/toy/katana
+ subcategory = LOADOUT_SUBCATEGORY_BACKPACK_TOYS
cost = 3
-/datum/gear/tapeplayer
+/datum/gear/backpack/tapeplayer
name = "Taperecorder"
- category = SLOT_IN_BACKPACK
path = /obj/item/taperecorder
-/datum/gear/tape
+/datum/gear/backpack/tape
name = "Spare cassette tape"
- category = SLOT_IN_BACKPACK
path = /obj/item/tape/random
-/datum/gear/newspaper
+/datum/gear/backpack/newspaper
name = "Newspaper"
- category = SLOT_IN_BACKPACK
path = /obj/item/newspaper
-/datum/gear/crayons
+/datum/gear/backpack/crayons
name = "Box of crayons"
- category = SLOT_IN_BACKPACK
path = /obj/item/storage/crayons
+ subcategory = LOADOUT_SUBCATEGORY_BACKPACK_TOYS
-/datum/gear/multipen
+/datum/gear/backpack/multipen
name = "A multicolored pen"
- category = SLOT_IN_BACKPACK
path = /obj/item/pen/fourcolor
-/datum/gear/fountainpen
+/datum/gear/backpack/fountainpen
name = "A fancy pen"
- category = SLOT_IN_BACKPACK
path = /obj/item/pen/fountain
cost = 2
-/datum/gear/modular_tablet
+/datum/gear/backpack/modular_tablet
name = "A modular tablet"
- category = SLOT_IN_BACKPACK
path = /obj/item/modular_computer/tablet/preset/cheap/
cost = 4
-/datum/gear/modular_laptop
+/datum/gear/backpack/modular_laptop
name = "A modular laptop"
- category = SLOT_IN_BACKPACK
path = /obj/item/modular_computer/laptop/preset/civilian
cost = 7
-/datum/gear/ringbox_gold
+/datum/gear/backpack/ringbox_gold
name = "A gold ring box"
- category = SLOT_IN_BACKPACK
path = /obj/item/storage/fancy/ringbox
cost = 3
-/datum/gear/ringbox_silver
+/datum/gear/backpack/ringbox_silver
name = "A silver ring box"
- category = SLOT_IN_BACKPACK
path = /obj/item/storage/fancy/ringbox/silver
cost = 3
-/datum/gear/ringbox_diamond
+/datum/gear/backpack/ringbox_diamond
name = "A diamond ring box"
- category = SLOT_IN_BACKPACK
path = /obj/item/storage/fancy/ringbox/diamond
cost = 5
+/datum/gear/backpack/necklace//this is here because loadout doesn't support proper accessories
+ name = "A renameable necklace"
+ path = /obj/item/clothing/accessory/necklace
diff --git a/modular_citadel/code/modules/client/loadout/glasses.dm b/modular_citadel/code/modules/client/loadout/glasses.dm
index 57270d8e57..b0eecbbf28 100644
--- a/modular_citadel/code/modules/client/loadout/glasses.dm
+++ b/modular_citadel/code/modules/client/loadout/glasses.dm
@@ -1,49 +1,43 @@
-/datum/gear/blindfold
+/datum/gear/glasses
+ category = LOADOUT_CATEGORY_GLASSES
+ slot = SLOT_GLASSES
+
+/datum/gear/glasses/blindfold
name = "Blindfold"
- category = SLOT_GLASSES
path = /obj/item/clothing/glasses/sunglasses/blindfold
-/datum/gear/cold
+/datum/gear/glasses/cold
name = "Cold goggles"
- category = SLOT_GLASSES
path = /obj/item/clothing/glasses/cold
-/datum/gear/eyepatch
+/datum/gear/glasses/eyepatch
name = "Eyepatch"
- category = SLOT_GLASSES
path = /obj/item/clothing/glasses/eyepatch
-/datum/gear/heat
+/datum/gear/glasses/heat
name = "Heat goggles"
- category = SLOT_GLASSES
path = /obj/item/clothing/glasses/heat
-/datum/gear/hipster
+/datum/gear/glasses/hipster
name = "Hipster glasses"
- category = SLOT_GLASSES
path = /obj/item/clothing/glasses/regular/hipster
-/datum/gear/jamjar
+/datum/gear/glasses/jamjar
name = "Jamjar glasses"
- category = SLOT_GLASSES
path = /obj/item/clothing/glasses/regular/jamjar
-/datum/gear/monocle
+/datum/gear/glasses/monocle
name = "Monocle"
- category = SLOT_GLASSES
path = /obj/item/clothing/glasses/monocle
-/datum/gear/orange
+/datum/gear/glasses/orange
name = "Orange glasses"
- category = SLOT_GLASSES
path = /obj/item/clothing/glasses/orange
-/datum/gear/red
+/datum/gear/glasses/red
name = "Red Glasses"
- category = SLOT_GLASSES
path = /obj/item/clothing/glasses/red
-/datum/gear/prescription
+/datum/gear/glasses/prescription
name = "Prescription glasses"
- category = SLOT_GLASSES
path = /obj/item/clothing/glasses/regular
diff --git a/modular_citadel/code/modules/client/loadout/gloves.dm b/modular_citadel/code/modules/client/loadout/gloves.dm
index e9e8e3939b..09694ddec7 100644
--- a/modular_citadel/code/modules/client/loadout/gloves.dm
+++ b/modular_citadel/code/modules/client/loadout/gloves.dm
@@ -1,28 +1,34 @@
-/datum/gear/fingerless
+/datum/gear/gloves
+ category = LOADOUT_CATEGORY_GLOVES
+ slot = SLOT_GLOVES
+
+/datum/gear/gloves/fingerless
name = "Fingerless Gloves"
- category = SLOT_GLOVES
path = /obj/item/clothing/gloves/fingerless
-/datum/gear/evening
+/datum/gear/gloves/evening
name = "Evening gloves"
- category = SLOT_GLOVES
path = /obj/item/clothing/gloves/evening
+
+/datum/gear/gloves/midnight
+ name = "Midnight gloves"
+ path = /obj/item/clothing/gloves/evening/black
-/datum/gear/goldring
+/datum/gear/gloves/goldring
name = "A gold ring"
- category = SLOT_GLOVES
path = /obj/item/clothing/gloves/ring
cost = 2
-/datum/gear/silverring
+/datum/gear/gloves/silverring
name = "A silver ring"
- category = SLOT_GLOVES
path = /obj/item/clothing/gloves/ring/silver
cost = 2
-/datum/gear/diamondring
+/datum/gear/gloves/diamondring
name = "A diamond ring"
- category = SLOT_GLOVES
path = /obj/item/clothing/gloves/ring/diamond
cost = 4
+/datum/gear/gloves/customring
+ name = "A ring, renameable"
+ path = /obj/item/clothing/gloves/ring/custom
diff --git a/modular_citadel/code/modules/client/loadout/hands.dm b/modular_citadel/code/modules/client/loadout/hands.dm
index 2f03bd3b07..db57fb466b 100644
--- a/modular_citadel/code/modules/client/loadout/hands.dm
+++ b/modular_citadel/code/modules/client/loadout/hands.dm
@@ -1,67 +1,54 @@
-/datum/gear/cane
+/datum/gear/hands
+ category = LOADOUT_CATEGORY_HANDS
+ slot = SLOT_HANDS
+
+/datum/gear/hands/cane
name = "Cane"
- category = SLOT_HANDS
path = /obj/item/cane
-/datum/gear/cigarettes
+/datum/gear/hands/cigarettes
name = "Cigarette pack"
- category = SLOT_HANDS
path = /obj/item/storage/fancy/cigarettes
-/datum/gear/dice
+/datum/gear/hands/dice
name = "Dice bag"
- category = SLOT_HANDS
path = /obj/item/storage/box/dice
-/datum/gear/eightball
+/datum/gear/hands/eightball
name = "Magic eightball"
- category = SLOT_HANDS
path = /obj/item/toy/eightball
-/datum/gear/matches
+/datum/gear/hands/matches
name = "Matchbox"
- category = SLOT_HANDS
path = /obj/item/storage/box/matches
-/datum/gear/cheaplighter
+/datum/gear/hands/cheaplighter
name = "Cheap lighter"
- category = SLOT_HANDS
path = /obj/item/lighter/greyscale
-/datum/gear/cards
+/datum/gear/hands/cards
name = "Playing cards"
- category = SLOT_HANDS
path = /obj/item/toy/cards/deck
-/datum/gear/skub
+/datum/gear/hands/skub
name = "Skub"
- category = SLOT_HANDS
path = /obj/item/skub
-/datum/gear/carpplushie
- name = "Space carp plushie"
- category = SLOT_HANDS
- path = /obj/item/toy/plush/carpplushie
-
-/datum/gear/wallet
+/datum/gear/hands/wallet
name = "Wallet"
- category = SLOT_HANDS
path = /obj/item/storage/wallet
-/datum/gear/flask
+/datum/gear/hands/flask
name = "Flask"
- category = SLOT_HANDS
path = /obj/item/reagent_containers/food/drinks/flask
cost = 2
-/datum/gear/zippolighter
+/datum/gear/hands/zippolighter
name = "Zippo Lighter"
- category = SLOT_HANDS
path = /obj/item/lighter
cost = 2
-/datum/gear/cigar
+/datum/gear/hands/cigar
name = "Cigar"
- category = SLOT_HANDS
path = /obj/item/clothing/mask/cigarette/cigar
cost = 4 //smoking is bad mkay
diff --git a/modular_citadel/code/modules/client/loadout/head.dm b/modular_citadel/code/modules/client/loadout/head.dm
index 2156c9c481..fd03e2279f 100644
--- a/modular_citadel/code/modules/client/loadout/head.dm
+++ b/modular_citadel/code/modules/client/loadout/head.dm
@@ -1,146 +1,136 @@
-/datum/gear/baseball
+/datum/gear/head
+ category = LOADOUT_CATEGORY_HEAD
+ subcategory = LOADOUT_SUBCATEGORY_HEAD_GENERAL
+ slot = SLOT_HEAD
+
+/datum/gear/head/baseball
name = "Ballcap"
- category = SLOT_HEAD
path = /obj/item/clothing/head/soft/mime
-/datum/gear/beanie
+/datum/gear/head/beanie
name = "Beanie"
- category = SLOT_HEAD
path = /obj/item/clothing/head/beanie
-/datum/gear/beret
+/datum/gear/head/beret
name = "Black beret"
- category = SLOT_HEAD
path = /obj/item/clothing/head/beret/black
-/datum/gear/flatcap
+/datum/gear/head/flatcap
name = "Flat cap"
- category = SLOT_HEAD
path = /obj/item/clothing/head/flatcap
-/datum/gear/pirate
+/datum/gear/head/pirate
name = "Pirate hat"
- category = SLOT_HEAD
path = /obj/item/clothing/head/pirate
-/datum/gear/rice_hat
+/datum/gear/head/rice_hat
name = "Rice hat"
- category = SLOT_HEAD
path = /obj/item/clothing/head/rice_hat
-/datum/gear/ushanka
- name = "Ushanka"
- category = SLOT_HEAD
+/datum/gear/head/ushanka
path = /obj/item/clothing/head/ushanka
-/datum/gear/slime
+/datum/gear/head/slime
name = "Slime hat"
- category = SLOT_HEAD
path = /obj/item/clothing/head/collectable/slime
-/datum/gear/fedora
+/datum/gear/head/fedora
name = "Fedora"
- category = SLOT_HEAD
path = /obj/item/clothing/head/fedora
-/datum/gear/that
+/datum/gear/head/that
name = "Top Hat"
- category = SLOT_HEAD
path = /obj/item/clothing/head/that
-/datum/gear/maidband
+/datum/gear/head/maidband
name = "Maid headband"
- category = SLOT_HEAD
path= /obj/item/clothing/head/maid
-/datum/gear/flakhelm
+/datum/gear/head/flakhelm
name = "Flak Helmet"
- category = SLOT_HEAD
path = /obj/item/clothing/head/flakhelm
cost = 2
-/datum/gear/bunnyears
+/datum/gear/head/bunnyears
name = "Bunny Ears"
- category = SLOT_HEAD
path = /obj/item/clothing/head/rabbitears
-/datum/gear/mailmanhat
+/datum/gear/head/mailmanhat
name = "Mailman's Hat"
- category = SLOT_HEAD
path = /obj/item/clothing/head/mailman
//trek fancy Hats!
-/datum/gear/trekcap
+/datum/gear/head/trekcap
name = "Federation Officer's Cap (White)"
- category = SLOT_HEAD
path = /obj/item/clothing/head/caphat/formal/fedcover
+ subcategory = LOADOUT_SUBCATEGORY_HEAD_JOBS
restricted_roles = list("Captain","Head of Personnel")
-/datum/gear/trekcapcap
+/datum/gear/head/trekcapcap
name = "Federation Officer's Cap (Black)"
- category = SLOT_HEAD
path = /obj/item/clothing/head/caphat/formal/fedcover/black
+ subcategory = LOADOUT_SUBCATEGORY_HEAD_JOBS
restricted_roles = list("Captain","Head of Personnel")
-/datum/gear/trekcapmedisci
+/datum/gear/head/trekcapmedisci
name = "Federation Officer's Cap (Blue)"
- category = SLOT_HEAD
path = /obj/item/clothing/head/caphat/formal/fedcover/medsci
+ subcategory = LOADOUT_SUBCATEGORY_HEAD_JOBS
restricted_desc = "Medical and Science"
restricted_roles = list("Chief Medical Officer","Medical Doctor","Chemist","Virologist","Paramedic","Geneticist","Research Director","Scientist", "Roboticist")
-/datum/gear/trekcapeng
+/datum/gear/head/trekcapeng
name = "Federation Officer's Cap (Yellow)"
- category = SLOT_HEAD
path = /obj/item/clothing/head/caphat/formal/fedcover/eng
+ subcategory = LOADOUT_SUBCATEGORY_HEAD_JOBS
restricted_desc = "Engineering, Security, and Cargo"
restricted_roles = list("Chief Engineer","Atmospheric Technician","Station Engineer","Warden","Detective","Security Officer","Head of Security","Cargo Technician", "Shaft Miner", "Quartermaster")
-/datum/gear/trekcapsec
+/datum/gear/head/trekcapsec
name = "Federation Officer's Cap (Red)"
- category = SLOT_HEAD
path = /obj/item/clothing/head/caphat/formal/fedcover/sec
+ subcategory = LOADOUT_SUBCATEGORY_HEAD_JOBS
restricted_desc = "Engineering, Security, and Cargo"
restricted_roles = list("Chief Engineer","Atmospheric Technician","Station Engineer","Warden","Detective","Security Officer","Head of Security","Cargo Technician", "Shaft Miner", "Quartermaster")
// orvilike "original" kepi
-/datum/gear/orvkepicom
+/datum/gear/head/orvkepicom
name = "Federation Kepi, command"
description = "A visored cap. Intended to be used with ORV uniform."
- category = SLOT_HEAD
path = /obj/item/clothing/head/kepi/orvi/command
+ subcategory = LOADOUT_SUBCATEGORY_HEAD_JOBS
restricted_desc = "Heads of Staff"
restricted_roles = list("Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Research Director", "Chief Medical Officer", "Quartermaster")
-/datum/gear/orvkepiops
+/datum/gear/head/orvkepiops
name = "Federation Kepi, ops/sec"
description = "A visored cap. Intended to be used with ORV uniform."
- category = SLOT_HEAD
path = /obj/item/clothing/head/kepi/orvi/engsec
+ subcategory = LOADOUT_SUBCATEGORY_HEAD_JOBS
restricted_desc = "Engineering, Security and Cargo"
restricted_roles = list("Chief Engineer", "Atmospheric Technician", "Station Engineer", "Warden", "Detective", "Security Officer", "Head of Security", "Cargo Technician", "Shaft Miner", "Quartermaster")
-/datum/gear/orvkepimedsci
+/datum/gear/head/orvkepimedsci
name = "Federation Kepi, medsci"
description = "A visored cap. Intended to be used with ORV uniform."
- category = SLOT_HEAD
path = /obj/item/clothing/head/kepi/orvi/medsci
+ subcategory = LOADOUT_SUBCATEGORY_HEAD_JOBS
restricted_desc = "Medical and Science"
restricted_roles = list("Chief Medical Officer", "Medical Doctor", "Chemist", "Virologist", "Paramedic", "Geneticist", "Research Director", "Scientist", "Roboticist")
-/datum/gear/orvkepisrv
+/datum/gear/head/orvkepisrv
name = "Federation Kepi, service"
description = "A visored cap. Intended to be used with ORV uniform."
- category = SLOT_HEAD
path = /obj/item/clothing/head/kepi/orvi/service
+ subcategory = LOADOUT_SUBCATEGORY_HEAD_JOBS
restricted_desc = "Service and Civilian, barring Clown, Mime and Lawyer"
restricted_roles = list("Assistant", "Bartender", "Botanist", "Cook", "Curator", "Janitor", "Chaplain")
-/datum/gear/orvkepiass
+/datum/gear/head/orvkepiass
name = "Federation Kepi, assistant"
description = "A visored cap. Intended to be used with ORV uniform."
- category = SLOT_HEAD
path = /obj/item/clothing/head/kepi/orvi
+ subcategory = LOADOUT_SUBCATEGORY_HEAD_JOBS
restricted_roles = list("Assistant")
/*Commenting out Until next Christmas or made automatic
@@ -156,35 +146,37 @@
*/
//Cowboy Stuff
-/datum/gear/cowboyhat
+/datum/gear/head/cowboyhat
name = "Cowboy Hat, Brown"
- category = SLOT_HEAD
path = /obj/item/clothing/head/cowboyhat
-/datum/gear/cowboyhat/black
+/datum/gear/head/cowboyhat/black
name = "Cowboy Hat, Black"
- category = SLOT_HEAD
path = /obj/item/clothing/head/cowboyhat/black
-/datum/gear/cowboyhat/white
+/datum/gear/head/cowboyhat/white
name = "Cowboy Hat, White"
- category = SLOT_HEAD
path = /obj/item/clothing/head/cowboyhat/white
-/datum/gear/cowboyhat/pink
+/datum/gear/head/cowboyhat/pink
name = "Cowboy Hat, Pink"
- category = SLOT_HEAD
path = /obj/item/clothing/head/cowboyhat/pink
-/datum/gear/cowboyhat/sec
+/datum/gear/head/cowboyhat/sec
name = "Cowboy Hat, Security"
- category = SLOT_HEAD
path = /obj/item/clothing/head/cowboyhat/sec
+ subcategory = LOADOUT_SUBCATEGORY_HEAD_JOBS
restricted_desc = "Security"
restricted_roles = list("Warden","Detective","Security Officer","Head of Security")
-// Misc
-/datum/gear/wkepi
+/datum/gear/head/wkepi
name = "white kepi"
- category = SLOT_HEAD
path = /obj/item/clothing/head/kepi
+
+/datum/gear/head/widered
+ name = "Wide red hat"
+ path = /obj/item/clothing/head/widered
+
+/datum/gear/head/kabuto
+ name = "Kabuto helmet"
+ path = /obj/item/clothing/head/kabuto
diff --git a/modular_citadel/code/modules/client/loadout/mask.dm b/modular_citadel/code/modules/client/loadout/mask.dm
index eeba06cad4..0d7e32552e 100644
--- a/modular_citadel/code/modules/client/loadout/mask.dm
+++ b/modular_citadel/code/modules/client/loadout/mask.dm
@@ -1,16 +1,16 @@
-/datum/gear/balaclava
+/datum/gear/mask
+ category = LOADOUT_CATEGORY_MASK
+ slot = SLOT_WEAR_MASK
+
+/datum/gear/mask/balaclava
name = "Balaclava"
- category = SLOT_WEAR_MASK
path = /obj/item/clothing/mask/balaclava
-/datum/gear/moustache
+/datum/gear/mask/moustache
name = "Fake moustache"
- category = SLOT_WEAR_MASK
path = /obj/item/clothing/mask/fakemoustache
-/datum/gear/joy
+/datum/gear/mask/joy
name = "Joy mask"
- category = SLOT_WEAR_MASK
path = /obj/item/clothing/mask/joy
cost = 3
-
diff --git a/modular_citadel/code/modules/client/loadout/neck.dm b/modular_citadel/code/modules/client/loadout/neck.dm
index 0f69296406..19311f703a 100644
--- a/modular_citadel/code/modules/client/loadout/neck.dm
+++ b/modular_citadel/code/modules/client/loadout/neck.dm
@@ -1,99 +1,88 @@
-/datum/gear/bluetie
+/datum/gear/neck
+ category = LOADOUT_CATEGORY_NECK
+ subcategory = LOADOUT_SUBCATEGORY_NECK_GENERAL
+ slot = SLOT_NECK
+
+/datum/gear/neck/bluetie
name = "Blue tie"
- category = SLOT_NECK
+ subcategory = LOADOUT_SUBCATEGORY_NECK_TIE
path = /obj/item/clothing/neck/tie/blue
-/datum/gear/redtie
+/datum/gear/neck/redtie
name = "Red tie"
- category = SLOT_NECK
+ subcategory = LOADOUT_SUBCATEGORY_NECK_TIE
path = /obj/item/clothing/neck/tie/red
-/datum/gear/blacktie
+/datum/gear/neck/blacktie
name = "Black tie"
- category = SLOT_NECK
+ subcategory = LOADOUT_SUBCATEGORY_NECK_TIE
path = /obj/item/clothing/neck/tie/black
-/datum/gear/collar
+/datum/gear/neck/collar
name = "Collar"
- category = SLOT_NECK
path = /obj/item/clothing/neck/petcollar
-/datum/gear/leathercollar
+/datum/gear/neck/leathercollar
name = "Leather collar"
- category = SLOT_NECK
path = /obj/item/clothing/neck/petcollar/leather
-/datum/gear/choker
+/datum/gear/neck/choker
name = "Choker"
- category = SLOT_NECK
path = /obj/item/clothing/neck/petcollar/choker
-/datum/gear/scarf
+/datum/gear/neck/scarf
name = "White scarf"
- category = SLOT_NECK
+ subcategory = LOADOUT_SUBCATEGORY_NECK_SCARVES
path = /obj/item/clothing/neck/scarf
-/datum/gear/blackscarf
+/datum/gear/neck/scarf/black
name = "Black scarf"
- category = SLOT_NECK
path = /obj/item/clothing/neck/scarf/black
-/datum/gear/redscarf
+/datum/gear/neck/scarf/red
name = "Red scarf"
- category = SLOT_NECK
path = /obj/item/clothing/neck/scarf/red
-/datum/gear/greenscarf
+/datum/gear/neck/scarf/green
name = "Green scarf"
- category = SLOT_NECK
path = /obj/item/clothing/neck/scarf/green
-/datum/gear/darkbluescarf
+/datum/gear/neck/scarf/darkblue
name = "Dark blue scarf"
- category = SLOT_NECK
path = /obj/item/clothing/neck/scarf/darkblue
-/datum/gear/purplescarf
+/datum/gear/neck/scarf/purple
name = "Purple scarf"
- category = SLOT_NECK
path = /obj/item/clothing/neck/scarf/purple
-/datum/gear/yellowscarf
+/datum/gear/neck/scarf/yellow
name = "Yellow scarf"
- category = SLOT_NECK
path = /obj/item/clothing/neck/scarf/yellow
-/datum/gear/orangescarf
+/datum/gear/neck/scarf/orange
name = "Orange scarf"
- category = SLOT_NECK
path = /obj/item/clothing/neck/scarf/orange
-/datum/gear/cyanscarf
+/datum/gear/neck/scarf/cyan
name = "Cyan scarf"
- category = SLOT_NECK
path = /obj/item/clothing/neck/scarf/cyan
-/datum/gear/stripedredscarf
+/datum/gear/neck/scarf/stripedred
name = "Striped red scarf"
- category = SLOT_NECK
path = /obj/item/clothing/neck/stripedredscarf
-/datum/gear/stripedbluescarf
+/datum/gear/neck/scarf/stripedblue
name = "Striped blue scarf"
- category = SLOT_NECK
path = /obj/item/clothing/neck/stripedbluescarf
-/datum/gear/stripedgreenscarf
+/datum/gear/neck/scarf/stripedgreen
name = "Striped green scarf"
- category = SLOT_NECK
path = /obj/item/clothing/neck/stripedgreenscarf
-/datum/gear/headphones
+/datum/gear/neck/headphones
name = "Headphones"
- category = SLOT_NECK
path = /obj/item/clothing/ears/headphones
-/datum/gear/polycloak
+/datum/gear/neck/polycloak
name = "Polychromatic Cloak"
- category = SLOT_NECK
path = /obj/item/clothing/neck/cloak/polychromic
diff --git a/modular_citadel/code/modules/client/loadout/shoes.dm b/modular_citadel/code/modules/client/loadout/shoes.dm
index 3531e69cfd..76d7305971 100644
--- a/modular_citadel/code/modules/client/loadout/shoes.dm
+++ b/modular_citadel/code/modules/client/loadout/shoes.dm
@@ -1,84 +1,71 @@
-/datum/gear/laceup
+/datum/gear/shoes
+ category = LOADOUT_CATEGORY_SHOES
+ slot = SLOT_SHOES
+
+/datum/gear/shoes/laceup
name = "Laceup shoes"
- category = SLOT_SHOES
path = /obj/item/clothing/shoes/laceup
-/datum/gear/workboots
+/datum/gear/shoes/workboots
name = "Work boots"
- category = SLOT_SHOES
path = /obj/item/clothing/shoes/workboots
-/datum/gear/jackboots
+/datum/gear/shoes/jackboots
name = "Jackboots"
- category = SLOT_SHOES
path = /obj/item/clothing/shoes/jackboots
-/datum/gear/winterboots
+/datum/gear/shoes/winterboots
name = "Winter boots"
- category = SLOT_SHOES
path = /obj/item/clothing/shoes/winterboots
-/datum/gear/sandals
+/datum/gear/shoes/sandals
name = "Sandals"
- category = SLOT_SHOES
path = /obj/item/clothing/shoes/sandal
-/datum/gear/blackshoes
+/datum/gear/shoes/blackshoes
name = "Black shoes"
- category = SLOT_SHOES
path = /obj/item/clothing/shoes/sneakers/black
-/datum/gear/brownshoes
+/datum/gear/shoes/brownshoes
name = "Brown shoes"
- category = SLOT_SHOES
path = /obj/item/clothing/shoes/sneakers/brown
-/datum/gear/whiteshoes
+/datum/gear/shoes/whiteshoes
name = "White shoes"
- category = SLOT_SHOES
path = /obj/item/clothing/shoes/sneakers/white
-/datum/gear/gildedcuffs
+/datum/gear/shoes/gildedcuffs
name = "Gilded leg wraps"
- category = SLOT_SHOES
path= /obj/item/clothing/shoes/wraps
-/datum/gear/silvercuffs
+/datum/gear/shoes/silvercuffs
name = "Silver leg wraps"
- category = SLOT_SHOES
path= /obj/item/clothing/shoes/wraps/silver
-/datum/gear/redcuffs
+/datum/gear/shoes/redcuffs
name = "Red leg wraps"
- category = SLOT_SHOES
path= /obj/item/clothing/shoes/wraps/red
-/datum/gear/bluecuffs
+/datum/gear/shoes/bluecuffs
name = "Blue leg wraps"
- category = SLOT_SHOES
path= /obj/item/clothing/shoes/wraps/blue
-/datum/gear/christmasbootsr
+/datum/gear/shoes/christmasbootsr
name = "Red Christmas Boots"
- category = SLOT_SHOES
path= /obj/item/clothing/shoes/winterboots/christmasbootsr
-/datum/gear/christmasbootsg
+/datum/gear/shoes/christmasbootsg
name = "Green Christmas Boots"
- category = SLOT_SHOES
path= /obj/item/clothing/shoes/winterboots/christmasbootsg
-/datum/gear/santaboots
+/datum/gear/shoes/santaboots
name = "Santa Boots"
- category = SLOT_SHOES
path= /obj/item/clothing/shoes/winterboots/santaboots
-/datum/gear/cowboyboots
+/datum/gear/shoes/cowboyboots
name = "Cowboy Boots, Brown"
- category = SLOT_SHOES
path = /obj/item/clothing/shoes/cowboyboots
-/datum/gear/cowboyboots/black
+/datum/gear/shoes/cowboyboots/black
name = "Cowboy Boots, Black"
- category = SLOT_SHOES
- path = /obj/item/clothing/shoes/cowboyboots/black
\ No newline at end of file
+ path = /obj/item/clothing/shoes/cowboyboots/black
diff --git a/modular_citadel/code/modules/client/loadout/suit.dm b/modular_citadel/code/modules/client/loadout/suit.dm
index 57c5772f48..d0be26a8a4 100644
--- a/modular_citadel/code/modules/client/loadout/suit.dm
+++ b/modular_citadel/code/modules/client/loadout/suit.dm
@@ -1,261 +1,250 @@
-/datum/gear/poncho
+/datum/gear/suit
+ category = LOADOUT_CATEGORY_SUIT
+ subcategory = LOADOUT_SUBCATEGORY_SUIT_GENERAL
+ slot = SLOT_WEAR_SUIT
+
+/datum/gear/suit/poncho
name = "Poncho"
- category = SLOT_WEAR_SUIT
path = /obj/item/clothing/suit/poncho
-/datum/gear/ponchogreen
+/datum/gear/suit/ponchogreen
name = "Green poncho"
- category = SLOT_WEAR_SUIT
path = /obj/item/clothing/suit/poncho/green
-/datum/gear/ponchored
+/datum/gear/suit/ponchored
name = "Red poncho"
- category = SLOT_WEAR_SUIT
path = /obj/item/clothing/suit/poncho/red
-/datum/gear/redhood
+/datum/gear/suit/redhood
name = "Red cloak"
- category = SLOT_WEAR_SUIT
path = /obj/item/clothing/suit/hooded/cloak/david
cost = 3
-/datum/gear/jacketbomber
+/datum/gear/suit/jacketbomber
name = "Bomber jacket"
- category = SLOT_WEAR_SUIT
path = /obj/item/clothing/suit/jacket
+ subcategory = LOADOUT_SUBCATEGORY_SUIT_JACKETS
-/datum/gear/jacketflannelblack // all of these are reskins of bomber jackets but with the vibe to make you look like a true lumberjack
+/datum/gear/suit/jacketflannelblack // all of these are reskins of bomber jackets but with the vibe to make you look like a true lumberjack
name = "Black flannel jacket"
- category = SLOT_WEAR_SUIT
path = /obj/item/clothing/suit/jacket/flannel
+ subcategory = LOADOUT_SUBCATEGORY_SUIT_JACKETS
-/datum/gear/jacketflannelred
+/datum/gear/suit/jacketflannelred
name = "Red flannel jacket"
- category = SLOT_WEAR_SUIT
path = /obj/item/clothing/suit/jacket/flannel/red
+ subcategory = LOADOUT_SUBCATEGORY_SUIT_JACKETS
-/datum/gear/jacketflannelaqua
+/datum/gear/suit/jacketflannelaqua
name = "Aqua flannel jacket"
- category = SLOT_WEAR_SUIT
path = /obj/item/clothing/suit/jacket/flannel/aqua
+ subcategory = LOADOUT_SUBCATEGORY_SUIT_JACKETS
-/datum/gear/jacketflannelbrown
+/datum/gear/suit/jacketflannelbrown
name = "Brown flannel jacket"
- category = SLOT_WEAR_SUIT
path = /obj/item/clothing/suit/jacket/flannel/brown
+ subcategory = LOADOUT_SUBCATEGORY_SUIT_JACKETS
-/datum/gear/jacketleather
+/datum/gear/suit/jacketleather
name = "Leather jacket"
- category = SLOT_WEAR_SUIT
path = /obj/item/clothing/suit/jacket/leather
+ subcategory = LOADOUT_SUBCATEGORY_SUIT_JACKETS
-/datum/gear/overcoatleather
+/datum/gear/suit/overcoatleather
name = "Leather overcoat"
- category = SLOT_WEAR_SUIT
path = /obj/item/clothing/suit/jacket/leather/overcoat
+ subcategory = LOADOUT_SUBCATEGORY_SUIT_JACKETS
-/datum/gear/jacketpuffer
+/datum/gear/suit/jacketpuffer
name = "Puffer jacket"
- category = SLOT_WEAR_SUIT
path = /obj/item/clothing/suit/jacket/puffer
+ subcategory = LOADOUT_SUBCATEGORY_SUIT_JACKETS
-/datum/gear/vestpuffer
+/datum/gear/suit/vestpuffer
name = "Puffer vest"
- category = SLOT_WEAR_SUIT
path = /obj/item/clothing/suit/jacket/puffer/vest
+ subcategory = LOADOUT_SUBCATEGORY_SUIT_JACKETS
-/datum/gear/jacketlettermanbrown
+/datum/gear/suit/jacketlettermanbrown
name = "Brown letterman jacket"
- category = SLOT_WEAR_SUIT
path = /obj/item/clothing/suit/jacket/letterman
+ subcategory = LOADOUT_SUBCATEGORY_SUIT_JACKETS
-/datum/gear/jacketlettermanred
+/datum/gear/suit/jacketlettermanred
name = "Red letterman jacket"
- category = SLOT_WEAR_SUIT
path = /obj/item/clothing/suit/jacket/letterman_red
+ subcategory = LOADOUT_SUBCATEGORY_SUIT_JACKETS
-/datum/gear/jacketlettermanNT
+/datum/gear/suit/jacketlettermanNT
name = "Nanotrasen letterman jacket"
- category = SLOT_WEAR_SUIT
path = /obj/item/clothing/suit/jacket/letterman_nanotrasen
+ subcategory = LOADOUT_SUBCATEGORY_SUIT_JACKETS
-/datum/gear/coat
+/datum/gear/suit/coat
name = "Winter coat"
- category = SLOT_WEAR_SUIT
path = /obj/item/clothing/suit/hooded/wintercoat
+ subcategory = LOADOUT_SUBCATEGORY_SUIT_COATS
-/datum/gear/coat/aformal
+/datum/gear/suit/coat/aformal
name = "Assistant's formal winter coat"
- category = SLOT_WEAR_SUIT
path = /obj/item/clothing/suit/hooded/wintercoat/aformal
-/datum/gear/coat/runed
+/datum/gear/suit/coat/runed
name = "Runed winter coat"
- category = SLOT_WEAR_SUIT
path = /obj/item/clothing/suit/hooded/wintercoat/narsie/fake
-/datum/gear/coat/brass
+/datum/gear/suit/coat/brass
name = "Brass winter coat"
- category = SLOT_WEAR_SUIT
path = /obj/item/clothing/suit/hooded/wintercoat/ratvar/fake
-/datum/gear/coat/polycoat
+/datum/gear/suit/coat/polycoat
name = "Polychromic winter coat"
- category = SLOT_WEAR_SUIT
path = /obj/item/clothing/suit/hooded/wintercoat/polychromic
cost = 4 //too many people with neon green coats is hard on the eyes
-/datum/gear/coat/med
+/datum/gear/suit/coat/med
name = "Medical winter coat"
- category = SLOT_WEAR_SUIT
path = /obj/item/clothing/suit/hooded/wintercoat/medical
restricted_roles = list("Chief Medical Officer", "Medical Doctor") // Reserve it to Medical Doctors and their boss, the Chief Medical Officer
-/datum/gear/coat/paramedic
+/datum/gear/suit/coat/paramedic
name = "Paramedic winter coat"
- category = SLOT_WEAR_SUIT
path = /obj/item/clothing/suit/hooded/wintercoat/paramedic
restricted_roles = list("Chief Medical Officer", "Paramedic") // Reserve it to Paramedics and their boss, the Chief Medical Officer
-/datum/gear/coat/robotics
+/datum/gear/suit/coat/robotics
name = "Robotics winter coat"
- category = SLOT_WEAR_SUIT
path = /obj/item/clothing/suit/hooded/wintercoat/robotics
restricted_roles = list("Research Director", "Roboticist")
-/datum/gear/coat/sci
+/datum/gear/suit/coat/sci
name = "Science winter coat"
- category = SLOT_WEAR_SUIT
path = /obj/item/clothing/suit/hooded/wintercoat/science
restricted_roles = list("Research Director", "Scientist", "Roboticist") // Reserve it to the Science Departement
-/datum/gear/coat/eng
+/datum/gear/suit/coat/eng
name = "Engineering winter coat"
- category = SLOT_WEAR_SUIT
path = /obj/item/clothing/suit/hooded/wintercoat/engineering
restricted_roles = list("Chief Engineer", "Station Engineer") // Reserve it to Station Engineers and their boss, the Chief Engineer
-/datum/gear/coat/eng/atmos
+/datum/gear/suit/coat/eng/atmos
name = "Atmospherics winter coat"
- category = SLOT_WEAR_SUIT
path = /obj/item/clothing/suit/hooded/wintercoat/engineering/atmos
restricted_roles = list("Chief Engineer", "Atmospheric Technician") // Reserve it to Atmos Techs and their boss, the Chief Engineer
-/datum/gear/coat/hydro
+/datum/gear/suit/coat/hydro
name = "Hydroponics winter coat"
- category = SLOT_WEAR_SUIT
path = /obj/item/clothing/suit/hooded/wintercoat/hydro
restricted_roles = list("Head of Personnel", "Botanist") // Reserve it to Botanists and their boss, the Head of Personnel
-/datum/gear/coat/bar
+/datum/gear/suit/coat/bar
name = "Bar winter coat"
- category = SLOT_WEAR_SUIT
path = /obj/item/clothing/suit/hooded/wintercoat/bar
restricted_roles = list("Bartender") // Reserve it to Bartenders and not the Head of Personnel because he doesnt deserve to look as fancy as them
-/datum/gear/coat/cargo
+/datum/gear/suit/coat/cargo
name = "Cargo winter coat"
- category = SLOT_WEAR_SUIT
path = /obj/item/clothing/suit/hooded/wintercoat/cargo
restricted_roles = list("Quartermaster", "Cargo Technician") // Reserve it to Cargo Techs and their boss, the Quartermaster
-/datum/gear/coat/miner
+/datum/gear/suit/coat/miner
name = "Mining winter coat"
- category = SLOT_WEAR_SUIT
path = /obj/item/clothing/suit/hooded/wintercoat/miner
restricted_roles = list("Quartermaster", "Shaft Miner") // Reserve it to Miners and their boss, the Quartermaster
-/datum/gear/militaryjacket
+/datum/gear/suit/militaryjacket
name = "Military Jacket"
- category = SLOT_WEAR_SUIT
path = /obj/item/clothing/suit/jacket/miljacket
+ subcategory = LOADOUT_SUBCATEGORY_SUIT_JACKETS
-/datum/gear/ianshirt
+/datum/gear/suit/ianshirt
name = "Ian Shirt"
- category = SLOT_WEAR_SUIT
path = /obj/item/clothing/suit/ianshirt
-/datum/gear/flakjack
+/datum/gear/suit/flakjack
name = "Flak Jacket"
- category = SLOT_WEAR_SUIT
path = /obj/item/clothing/suit/flakjack
+ subcategory = LOADOUT_SUBCATEGORY_SUIT_JACKETS
cost = 2
-/datum/gear/trekds9_coat
+/datum/gear/suit/trekds9_coat
name = "DS9 Overcoat (use uniform)"
- category = SLOT_WEAR_SUIT
path = /obj/item/clothing/suit/storage/trek/ds9
+ subcategory = LOADOUT_SUBCATEGORY_SUIT_JOBS
restricted_desc = "All, barring Service and Civilian"
restricted_roles = list("Head of Security","Captain","Head of Personnel","Chief Engineer","Research Director","Chief Medical Officer","Quartermaster",
"Medical Doctor","Chemist","Virologist","Paramedic","Geneticist","Scientist", "Roboticist",
"Atmospheric Technician","Station Engineer","Warden","Detective","Security Officer",
"Cargo Technician", "Shaft Miner") //everyone who actually deserves a job.
//Federation jackets from movies
-/datum/gear/trekcmdcap
+/datum/gear/suit/trekcmdcap
name = "Fed (movie) uniform, Black"
- category = SLOT_WEAR_SUIT
path = /obj/item/clothing/suit/storage/fluff/fedcoat/capt
+ subcategory = LOADOUT_SUBCATEGORY_SUIT_JOBS
restricted_roles = list("Captain","Head of Personnel")
-/datum/gear/trekcmdmov
+/datum/gear/suit/trekcmdmov
name = "Fed (movie) uniform, Red"
- category = SLOT_WEAR_SUIT
path = /obj/item/clothing/suit/storage/fluff/fedcoat
+ subcategory = LOADOUT_SUBCATEGORY_SUIT_JOBS
restricted_desc = "Heads of Staff and Security"
restricted_roles = list("Head of Security","Captain","Head of Personnel","Chief Engineer","Research Director","Chief Medical Officer","Quartermaster","Warden","Detective","Security Officer")
-/datum/gear/trekmedscimov
+/datum/gear/suit/trekmedscimov
name = "Fed (movie) uniform, Blue"
- category = SLOT_WEAR_SUIT
path = /obj/item/clothing/suit/storage/fluff/fedcoat/medsci
+ subcategory = LOADOUT_SUBCATEGORY_SUIT_JOBS
restricted_desc = "Medical and Science"
restricted_roles = list("Chief Medical Officer","Medical Doctor","Chemist","Virologist","Paramedic","Geneticist","Research Director","Scientist", "Roboticist")
-/datum/gear/trekengmov
+/datum/gear/suit/trekengmov
name = "Fed (movie) uniform, Yellow"
- category = SLOT_WEAR_SUIT
path = /obj/item/clothing/suit/storage/fluff/fedcoat/eng
+ subcategory = LOADOUT_SUBCATEGORY_SUIT_JOBS
restricted_desc = "Engineering and Cargo"
restricted_roles = list("Chief Engineer","Atmospheric Technician","Station Engineer","Cargo Technician", "Shaft Miner", "Quartermaster")
-/datum/gear/trekcmdcapmod
+/datum/gear/suit/trekcmdcapmod
name = "Fed (Modern) uniform, White"
- category = SLOT_WEAR_SUIT
path = /obj/item/clothing/suit/storage/fluff/modernfedcoat
restricted_roles = list("Captain","Head of Personnel")
-/datum/gear/trekcmdmod
+/datum/gear/suit/trekcmdmod
name = "Fed (Modern) uniform, Red"
- category = SLOT_WEAR_SUIT
path = /obj/item/clothing/suit/storage/fluff/modernfedcoat/sec
+ subcategory = LOADOUT_SUBCATEGORY_SUIT_JOBS
restricted_desc = "Heads of Staff and Security"
restricted_roles = list("Head of Security","Captain","Head of Personnel","Chief Engineer","Research Director","Chief Medical Officer","Quartermaster","Warden","Detective","Security Officer")
-/datum/gear/trekmedscimod
+/datum/gear/suit/trekmedscimod
name = "Fed (Modern) uniform, Blue"
- category = SLOT_WEAR_SUIT
path = /obj/item/clothing/suit/storage/fluff/modernfedcoat/medsci
+ subcategory = LOADOUT_SUBCATEGORY_SUIT_JOBS
restricted_desc = "Medical and Science"
restricted_roles = list("Chief Medical Officer","Medical Doctor","Chemist","Virologist","Paramedic","Geneticist","Research Director","Scientist", "Roboticist")
-/datum/gear/trekengmod
+/datum/gear/suit/trekengmod
name = "Fed (Modern) uniform, Yellow"
- category = SLOT_WEAR_SUIT
path = /obj/item/clothing/suit/storage/fluff/modernfedcoat/eng
+ subcategory = LOADOUT_SUBCATEGORY_SUIT_JOBS
restricted_desc = "Engineering and Cargo"
restricted_roles = list("Chief Engineer","Atmospheric Technician","Station Engineer","Cargo Technician", "Shaft Miner", "Quartermaster")
-/datum/gear/christmascoatr
+/datum/gear/suit/christmascoatr
name = "Red Christmas Coat"
- category = SLOT_WEAR_SUIT
path = /obj/item/clothing/suit/hooded/wintercoat/christmascoatr
+ subcategory = LOADOUT_SUBCATEGORY_SUIT_COATS
-/datum/gear/christmascoatg
+/datum/gear/suit/christmascoatg
name = "Green Christmas Coat"
- category = SLOT_WEAR_SUIT
path = /obj/item/clothing/suit/hooded/wintercoat/christmascoatg
+ subcategory = LOADOUT_SUBCATEGORY_SUIT_COATS
-/datum/gear/christmascoatrg
+/datum/gear/suit/christmascoatrg
name = "Red and Green Christmas Coat"
- category = SLOT_WEAR_SUIT
path = /obj/item/clothing/suit/hooded/wintercoat/christmascoatrg
+ subcategory = LOADOUT_SUBCATEGORY_SUIT_COATS
+
+/datum/gear/suit/samurai
+ name = "Samurai outfit"
+ path = /obj/item/clothing/suit/samurai
diff --git a/modular_citadel/code/modules/client/loadout/uniform.dm b/modular_citadel/code/modules/client/loadout/uniform.dm
index e667626968..5ce73d1cfd 100644
--- a/modular_citadel/code/modules/client/loadout/uniform.dm
+++ b/modular_citadel/code/modules/client/loadout/uniform.dm
@@ -1,562 +1,542 @@
-/datum/gear/suitblack
- name = "Black suit"
- category = SLOT_W_UNIFORM
- path = /obj/item/clothing/under/suit/black
+/datum/gear/uniform
+ category = LOADOUT_CATEGORY_UNIFORM
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_GENERAL
+ slot = SLOT_W_UNIFORM
-/datum/gear/suitgreen
+/datum/gear/uniform/suit
+ name = "Black suit"
+ path = /obj/item/clothing/under/suit/black
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_SUITS
+
+/datum/gear/uniform/suit/green
name = "Green suit"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/suit/green
-/datum/gear/suitred
+/datum/gear/uniform/suit/red
name = "Red suit"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/suit/red
-/datum/gear/suitcharcoal
+/datum/gear/uniform/suit/charcoal
name = "Charcoal suit"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/suit/charcoal
-/datum/gear/suitnavy
+/datum/gear/uniform/suit/navy
name = "Navy suit"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/suit/navy
-/datum/gear/suitburgundy
+/datum/gear/uniform/suit/burgundy
name = "Burgundy suit"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/suit/burgundy
-/datum/gear/suittan
+/datum/gear/uniform/suit/tan
name = "Tan suit"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/suit/tan
-/datum/gear/suitwhite
+/datum/gear/uniform/suit/white
name = "White suit"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/suit/white
-/datum/gear/assistantformal
+/datum/gear/uniform/assistantformal
name = "Assistant's formal uniform"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/misc/assistantformal
-/datum/gear/maidcostume
+/datum/gear/uniform/maidcostume
name = "Maid costume"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/costume/maid
-/datum/gear/mailmanuniform
+/datum/gear/uniform/mailmanuniform
name = "Mailman's jumpsuit"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/misc/mailman
-/datum/gear/skirtblack
+/datum/gear/uniform/skirt
name = "Black skirt"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/dress/skirt
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_SKIRTS
-/datum/gear/skirtblue
+/datum/gear/uniform/skirt/blue
name = "Blue skirt"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/dress/skirt/blue
-/datum/gear/skirtred
+/datum/gear/uniform/skirt/red
name = "Red skirt"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/dress/skirt/red
-/datum/gear/skirtpurple
+/datum/gear/uniform/skirt/purple
name = "Purple skirt"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/dress/skirt/purple
-/datum/gear/skirtplaid
+/datum/gear/uniform/skirt/plaid
name = "Plaid skirt"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/dress/skirt/plaid
-/datum/gear/schoolgirlblue
+/datum/gear/uniform/schoolgirlblue
name = "Blue Schoolgirl Uniform"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/costume/schoolgirl
-/datum/gear/schoolgirlred
+/datum/gear/uniform/schoolgirlred
name = "Red Schoolgirl Uniform"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/costume/schoolgirl/red
-/datum/gear/schoolgirlgreen
+/datum/gear/uniform/schoolgirlgreen
name = "Green Schoolgirl Uniform"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/costume/schoolgirl/green
-/datum/gear/schoolgirlorange
+/datum/gear/uniform/schoolgirlorange
name = "Orange Schoolgirl Uniform"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/costume/schoolgirl/orange
-/datum/gear/stripeddress
+/datum/gear/uniform/dress
name = "Striped Dress"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/dress/striped
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_DRESSES
-/datum/gear/sundresswhite
+/datum/gear/uniform/dress/sun/white
name = "White Sundress"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/dress/sundress/white
-/datum/gear/sundress
+/datum/gear/uniform/dress/sun
name = "Sundress"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/dress/sundress
-/datum/gear/greendress
+/datum/gear/uniform/dress/green
name = "Green Dress"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/dress/green
-/datum/gear/pinkdress
+/datum/gear/uniform/dress/pink
name = "Pink Dress"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/dress/pink
-/datum/gear/flowerdress
+
+/datum/gear/uniform/dress/orange
name = "Flower Dress"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/dress/flower
-/datum/gear/sweptskirt
+/datum/gear/uniform/skirt/swept
name = "Swept skirt"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/dress/skirt/swept
-/datum/gear/croptop
+/datum/gear/uniform/croptop
name = "Croptop"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/croptop
-/datum/gear/yoga
+/datum/gear/uniform/pants
name = "Yoga Pants"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/pants/yoga
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_PANTS
-/datum/gear/kilt
+/datum/gear/uniform/kilt
name = "Kilt"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/costume/kilt
-/datum/gear/camoshorts
+/datum/gear/uniform/pants/camo
name = "Camo Pants"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/pants/camo
-/datum/gear/athleticshorts
+/datum/gear/uniform/shorts
name = "Athletic Shorts"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/shorts/red
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_SHORTS
-/datum/gear/bjeans
+/datum/gear/uniform/pants/bjeans
name = "Black Jeans"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/pants/blackjeans
-/datum/gear/cjeans
+/datum/gear/uniform/pants/cjeans
name = "Classic Jeans"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/pants/classicjeans
-/datum/gear/khaki
+/datum/gear/uniform/pants/khaki
name = "Khaki Pants"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/pants/khaki
-/datum/gear/wpants
+/datum/gear/uniform/pants/white
name = "White Pants"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/pants/white
-/datum/gear/rpants
+/datum/gear/uniform/pants/red
name = "Red Pants"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/pants/red
-/datum/gear/tpants
+/datum/gear/uniform/pants/tan
name = "Tan Pants"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/pants/tan
-/datum/gear/trpants
+/datum/gear/uniform/pants/track
name = "Track Pants"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/pants/track
-/datum/gear/rippedjeans
+/datum/gear/uniform/pants/ripped
name = "Ripped Jeans"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/pants/jeanripped
-/datum/gear/jeanshort
+/datum/gear/uniform/shorts/jean
name = "Jean Shorts"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/pants/jeanshort
-/datum/gear/denimskirt
+/datum/gear/uniform/skirt/denim
name = "Denim Skirt"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/pants/denimskirt
-/datum/gear/yoga
- name = "Yoga Pants"
- category = SLOT_W_UNIFORM
- path = /obj/item/clothing/under/pants/yoga
-
// Pantsless Sweaters
-/datum/gear/turtleneck
+/datum/gear/uniform/turtleneck
name = "Tactitool Turtleneck"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/syndicate/cosmetic
-/datum/gear/creamsweater
+/datum/gear/uniform/sweater
name = "Cream Commando Sweater"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/sweater
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_SWEATERS
-/datum/gear/blacksweater
+/datum/gear/uniform/sweater/black
name = "Black Commando Sweater"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/sweater/black
-/datum/gear/purpsweater
+/datum/gear/uniform/sweater/purple
name = "Purple Commando Sweater"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/sweater/purple
-/datum/gear/greensweater
+/datum/gear/uniform/sweater/green
name = "Green Commando Sweater"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/sweater/green
-/datum/gear/redsweater
+/datum/gear/uniform/sweater/red
name = "Red Commando Sweater"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/sweater/red
-/datum/gear/bluesweater
+/datum/gear/uniform/sweater/blue
name = "Navy Commando Sweater"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/sweater/blue
-/datum/gear/keyholesweater
+/datum/gear/uniform/sweater/keyhole
name = "Keyhole Sweater"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/misc/keyholesweater
-/datum/gear/polyjump
+/datum/gear/uniform/polyjump
name = "Polychromic Jumpsuit"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/misc/polyjumpsuit
cost = 2
-
-/datum/gear/polyskirt
+
+/datum/gear/uniform/skirt/poly
name = "Polychromic Jumpskirt"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/dress/skirt/polychromic
cost = 2
-/datum/gear/polysuit
+/datum/gear/uniform/suit/poly
name = "Polychromic Button-up Shirt"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/misc/poly_shirt
cost = 3
-
-/datum/gear/polypleated
+
+/datum/gear/uniform/skirt/poly/pleated
name = "Polychromic Pleated Sweaterskirt"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/dress/skirt/polychromic/pleated
cost = 3
-/datum/gear/polykilt
+/datum/gear/uniform/polykilt
name = "Polychromic Kilt"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/costume/kilt/polychromic
cost = 3
-/datum/gear/polyshorts
+/datum/gear/uniform/shorts/poly
name = "Polychromic Shorts"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/misc/polyshorts
cost = 3
-/datum/gear/polyshortpants
+/datum/gear/uniform/shorts/poly/athletic
name = "Polychromic Athletic Shorts"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/shorts/polychromic
cost = 2
// Trekie things
//TOS
-/datum/gear/trekcmdtos
+/datum/gear/uniform/trekcmdtos
name = "TOS uniform, cmd"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/trek/command
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_JOBS
restricted_desc = "Heads of Staff"
restricted_roles = list("Head of Security","Captain","Head of Personnel","Chief Engineer","Research Director","Chief Medical Officer","Quartermaster")
-/datum/gear/trekmedscitos
+/datum/gear/uniform/trekmedscitos
name = "TOS uniform, med/sci"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/trek/medsci
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_JOBS
restricted_desc = "Medical and Science"
restricted_roles = list("Chief Medical Officer","Medical Doctor","Chemist","Virologist","Paramedic","Geneticist","Research Director","Scientist", "Roboticist")
-/datum/gear/trekengtos
+/datum/gear/uniform/trekengtos
name = "TOS uniform, ops/sec"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/trek/engsec
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_JOBS
restricted_desc = "Engineering, Security, and Cargo"
restricted_roles = list("Chief Engineer","Atmospheric Technician","Station Engineer","Warden","Detective","Security Officer","Head of Security","Cargo Technician", "Shaft Miner", "Quartermaster")
//TNG
-/datum/gear/trekcmdtng
+/datum/gear/uniform/trekcmdtng
name = "TNG uniform, cmd"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/trek/command/next
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_JOBS
restricted_desc = "Heads of Staff"
restricted_roles = list("Head of Security","Captain","Head of Personnel","Chief Engineer","Research Director","Chief Medical Officer","Quartermaster")
-/datum/gear/trekmedscitng
+/datum/gear/uniform/trekmedscitng
name = "TNG uniform, med/sci"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/trek/medsci/next
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_JOBS
restricted_desc = "Medical and Science"
restricted_roles = list("Chief Medical Officer","Medical Doctor","Chemist","Virologist","Paramedic","Geneticist","Research Director","Scientist", "Roboticist")
-/datum/gear/trekengtng
+/datum/gear/uniform/trekengtng
name = "TNG uniform, ops/sec"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/trek/engsec/next
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_JOBS
restricted_desc = "Engineering, Security, and Cargo"
restricted_roles = list("Chief Engineer","Atmospheric Technician","Station Engineer","Warden","Detective","Security Officer","Head of Security","Cargo Technician", "Shaft Miner", "Quartermaster")
//VOY
-/datum/gear/trekcmdvoy
+/datum/gear/uniform/trekcmdvoy
name = "VOY uniform, cmd"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/trek/command/voy
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_JOBS
restricted_desc = "Heads of Staff"
restricted_roles = list("Head of Security","Captain","Head of Personnel","Chief Engineer","Research Director","Chief Medical Officer","Quartermaster")
-/datum/gear/trekmedscivoy
+/datum/gear/uniform/trekmedscivoy
name = "VOY uniform, med/sci"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/trek/medsci/voy
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_JOBS
restricted_desc = "Medical and Science"
restricted_roles = list("Chief Medical Officer","Medical Doctor","Chemist","Virologist","Paramedic","Geneticist","Research Director","Scientist", "Roboticist")
-/datum/gear/trekengvoy
+/datum/gear/uniform/trekengvoy
name = "VOY uniform, ops/sec"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/trek/engsec/voy
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_JOBS
restricted_desc = "Engineering, Security, and Cargo"
restricted_roles = list("Chief Engineer","Atmospheric Technician","Station Engineer","Warden","Detective","Security Officer","Head of Security","Cargo Technician", "Shaft Miner", "Quartermaster")
//DS9
-/datum/gear/trekcmdds9
+/datum/gear/uniform/trekcmdds9
name = "DS9 uniform, cmd"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/trek/command/ds9
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_JOBS
restricted_desc = "Heads of Staff"
restricted_roles = list("Head of Security","Captain","Head of Personnel","Chief Engineer","Research Director","Chief Medical Officer","Quartermaster")
-/datum/gear/trekmedscids9
+/datum/gear/uniform/trekmedscids9
name = "DS9 uniform, med/sci"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/trek/medsci/ds9
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_JOBS
restricted_desc = "Medical and Science"
restricted_roles = list("Chief Medical Officer","Medical Doctor","Chemist","Virologist","Paramedic","Geneticist","Research Director","Scientist", "Roboticist")
-/datum/gear/trekengds9
+/datum/gear/uniform/trekengds9
name = "DS9 uniform, ops/sec"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/trek/engsec/ds9
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_JOBS
restricted_desc = "Engineering, Security, and Cargo"
restricted_roles = list("Chief Engineer","Atmospheric Technician","Station Engineer","Warden","Detective","Security Officer","Head of Security","Cargo Technician", "Shaft Miner", "Quartermaster")
//ENT
-/datum/gear/trekcmdent
+/datum/gear/uniform/trekcmdent
name = "ENT uniform, cmd"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/trek/command/ent
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_JOBS
restricted_desc = "Heads of Staff"
restricted_roles = list("Head of Security","Captain","Head of Personnel","Chief Engineer","Research Director","Chief Medical Officer","Quartermaster")
-/datum/gear/trekmedscient
+/datum/gear/uniform/trekmedscient
name = "ENT uniform, med/sci"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/trek/medsci/ent
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_JOBS
restricted_desc = "Medical and Science"
restricted_roles = list("Chief Medical Officer","Medical Doctor","Chemist","Virologist","Paramedic","Geneticist","Research Director","Scientist", "Roboticist")
-/datum/gear/trekengent
+/datum/gear/uniform/trekengent
name = "ENT uniform, ops/sec"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/trek/engsec/ent
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_JOBS
restricted_desc = "Engineering, Security, and Cargo"
restricted_roles = list("Chief Engineer","Atmospheric Technician","Station Engineer","Warden","Detective","Security Officer","Head of Security","Cargo Technician", "Shaft Miner", "Quartermaster")
//TheMotionPicture
-/datum/gear/trekfedutil
+/datum/gear/uniform/trekfedutil
name = "TMP uniform"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/trek/fedutil
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_JOBS
restricted_desc = "All, barring Service and Civilian"
restricted_roles = list("Head of Security","Captain","Head of Personnel","Chief Engineer","Research Director","Chief Medical Officer","Quartermaster",
"Medical Doctor","Chemist","Virologist","Paramedic","Geneticist","Scientist", "Roboticist",
"Atmospheric Technician","Station Engineer","Warden","Detective","Security Officer",
"Cargo Technician", "Shaft Miner")
-/datum/gear/trekfedtrainee
+/datum/gear/uniform/trekfedtrainee
name = "TMP uniform, trainee"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/trek/fedutil/trainee
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_JOBS
restricted_roles = list("Assistant", "Janitor", "Cargo Technician")
-/datum/gear/trekfedservice
+/datum/gear/uniform/trekfedservice
name = "TMP uniform, service"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/trek/fedutil/service
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_JOBS
restricted_desc = "Service and Civilian, barring Clown, Mime and Lawyer"
restricted_roles = list("Assistant", "Bartender", "Botanist", "Cook", "Curator", "Janitor", "Chaplain")
//Orvilike
-/datum/gear/orvcmd
+/datum/gear/uniform/orvcmd
name = "ORV uniform, cmd"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/trek/command/orv
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_JOBS
restricted_desc = "Heads of Staff"
restricted_roles = list("Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Research Director", "Chief Medical Officer", "Quartermaster")
-/datum/gear/orvcmd_capt
+/datum/gear/uniform/orvcmd_capt
name = "ORV uniform, capt"
- category = SLOT_W_UNIFORM
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_JOBS
path = /obj/item/clothing/under/trek/command/orv/captain
restricted_roles = list("Captain")
-/datum/gear/orvmedsci
+/datum/gear/uniform/orvmedsci
name = "ORV uniform, med/sci"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/trek/medsci/orv
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_JOBS
restricted_desc = "Medical and Science"
restricted_roles = list("Chief Medical Officer", "Medical Doctor", "Chemist", "Virologist", "Paramedic", "Geneticist", "Research Director", "Scientist", "Roboticist")
-/datum/gear/orvcmd_medsci
+/datum/gear/uniform/orvcmd_medsci
name = "ORV uniform, med/sci, cmd"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/trek/command/orv/medsci
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_JOBS
restricted_roles = list("Chief Medical Officer", "Research Director")
-/datum/gear/orvops
+/datum/gear/uniform/orvops
name = "ORV uniform, ops/sec"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/trek/engsec/orv
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_JOBS
restricted_desc = "Engineering, Security and Cargo"
restricted_roles = list("Chief Engineer", "Atmospheric Technician", "Station Engineer", "Warden", "Detective", "Security Officer", "Head of Security", "Cargo Technician", "Shaft Miner", "Quartermaster")
-/datum/gear/orvcmd_ops
+/datum/gear/uniform/orvcmd_ops
name = "ORV uniform, ops/sec, cmd"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/trek/command/orv/engsec
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_JOBS
restricted_roles = list("Chief Engineer", "Head of Security")
-/datum/gear/orvass
+/datum/gear/uniform/orvass
name = "ORV uniform, assistant"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/trek/orv
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_JOBS
restricted_roles = list("Assistant")
-/datum/gear/orvsrv
+/datum/gear/uniform/orvsrv
name = "ORV uniform, service"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/trek/orv/service
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_JOBS
restricted_roles = list("Assistant", "Bartender", "Botanist", "Cook", "Curator", "Janitor", "Chaplain")
restricted_desc = "Service and Civilian, barring Clown, Mime and Lawyer"
//Memes
-/datum/gear/gear_harnesses
+/datum/gear/uniform/gear_harnesses
name = "Gear Harness"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/misc/gear_harness
//Christmas
/*Commenting out Until next Christmas or made automatic
-/datum/gear/christmasmaler
+/datum/gear/uniform/christmasmaler
name = "Red Masculine Christmas Suit"
category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/costume/christmas
-/datum/gear/christmasmaleg
+/datum/gear/uniform/christmasmaleg
name = "Green Masculine Christmas Suit"
category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/costume/christmas/green
-/datum/gear/christmasfemaler
+/datum/gear/uniform/christmasfemaler
name = "Red Feminine Christmas Suit"
category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/costume/christmas/croptop
-/datum/gear/christmasfemaleg
+/datum/gear/uniform/christmasfemaleg
name = "Green Feminine Christmas Suit"
category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/costume/christmas/croptop/green
-/datum/gear/pinkstripper
+/datum/gear/uniform/pinkstripper
name = "Pink stripper outfit"
category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/misc/stripper
cost = 3
*/
-/datum/gear/greenstripper
+/datum/gear/uniform/greenstripper
name = "Green stripper outfit"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/misc/stripper/green
cost = 3
-/datum/gear/qipao
+/datum/gear/uniform/qipao
name = "Qipao, Black"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/costume/qipao
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_DRESSES
cost = 3
-/datum/gear/qipao/white
+/datum/gear/uniform/qipao/white
name = "Qipao, White"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/costume/qipao/white
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_DRESSES
cost = 3
-/datum/gear/qipao/red
+/datum/gear/uniform/qipao/red
name = "Qipao, Red"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/costume/qipao/red
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_DRESSES
cost = 3
-/datum/gear/cheongsam
+/datum/gear/uniform/cheongsam
name = "Cheongsam, Black"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/costume/cheongsam
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_DRESSES
cost = 3
-/datum/gear/cheongsam/white
+/datum/gear/uniform/cheongsam/white
name = "Cheongsam, White"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/costume/cheongsam/white
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_DRESSES
cost = 3
-/datum/gear/cheongsam/red
+/datum/gear/uniform/cheongsam/red
name = "Cheongsam, Red"
- category = SLOT_W_UNIFORM
path = /obj/item/clothing/under/costume/cheongsam/red
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_DRESSES
cost = 3
+
+/datum/gear/uniform/dress/black
+ name = "Black dress"
+ path = /obj/item/clothing/under/misc/black_dress
+
+/datum/gear/uniform/skirt/pinktutu
+ name = "Pink tutu"
+ path = /obj/item/clothing/under/misc/pinktutu
+
+/datum/gear/uniform/bathrobe
+ name = "Bathrobe"
+ path = /obj/item/clothing/under/misc/bathrobe
+
+/datum/gear/uniform/kimono
+ name = "Kimono"
+ path = /obj/item/clothing/under/costume/kimono
+
+/datum/gear/uniform/kimono/black
+ name = "Black kimono"
+ path = /obj/item/clothing/under/costume/kimono/black
+
+/datum/gear/uniform/kimono/kamishimo
+ name = "Kamishimo"
+ path = /obj/item/clothing/under/costume/kimono/kamishimo
+
+/datum/gear/uniform/kimono/fancy
+ name = "Fancy kimono"
+ path = /obj/item/clothing/under/costume/kimono/fancy
+
+/datum/gear/uniform/kimono/sakura
+ name = "Sakura kimono"
+ path = /obj/item/clothing/under/costume/kimono/sakura
diff --git a/modular_citadel/code/modules/custom_loadout/custom_items.dm b/modular_citadel/code/modules/custom_loadout/custom_items.dm
index bd377d6081..9de4cb235b 100644
--- a/modular_citadel/code/modules/custom_loadout/custom_items.dm
+++ b/modular_citadel/code/modules/custom_loadout/custom_items.dm
@@ -1,26 +1,8 @@
-
//For custom items.
// Unless there's a digitigrade version make sure you add mutantrace_variation = NONE to all clothing/under and shoes - Pooj
// Digitigrade stuff is uniform_digi.dmi and digishoes.dmi in icons/mob
-/obj/item/custom/ceb_soap
- name = "Cebutris' Soap"
- desc = "A generic bar of soap that doesn't really seem to work right."
- gender = PLURAL
- icon = 'icons/obj/custom.dmi'
- icon_state = "cebu"
- w_class = WEIGHT_CLASS_TINY
- item_flags = NOBLUDGEON
-
-/obj/item/soap/cebu //real versions, for admin shenanigans. Adminspawn only
- desc = "A bright blue bar of soap that smells of wolves"
- icon = 'icons/obj/custom.dmi'
- icon_state = "cebu"
-
-/obj/item/soap/cebu/fast //speedyquick cleaning version. Still not as fast as Syndiesoap. Adminspawn only.
- cleanspeed = 15
-
/obj/item/clothing/neck/cloak/inferno
name = "Kiara's Cloak"
desc = "The design on this seems a little too familiar."
@@ -316,7 +298,7 @@
/obj/item/clothing/neck/petcollar/naomi
name = "worn pet collar"
- desc = "a pet collar that looks well used."
+ desc = "A pet collar that looks well used."
/obj/item/clothing/neck/cloak/green
name = "Generic Green Cloak"
@@ -568,3 +550,11 @@
desc = "A fancy cloak embroidered with polychromatic thread in a pattern that reminds one of the wielders of unlimited power."
icon_state = "polyce"
poly_colors = list("#808080", "#8CC6FF", "#FF3535")
+
+/obj/item/clothing/head/halo
+ name = "transdimensional halo"
+ desc = "An oddly shaped halo that magically hovers above the head."
+ icon_state = "halo"
+ item_state = "halo"
+ icon = 'icons/obj/custom.dmi'
+ mob_overlay_icon = 'icons/mob/clothing/custom_w.dmi'
\ No newline at end of file
diff --git a/modular_citadel/code/modules/mentor/mentorsay.dm b/modular_citadel/code/modules/mentor/mentorsay.dm
index c13e3c6ef3..dc1281abe0 100644
--- a/modular_citadel/code/modules/mentor/mentorsay.dm
+++ b/modular_citadel/code/modules/mentor/mentorsay.dm
@@ -13,7 +13,7 @@
log_mentor("MSAY: [key_name(src)] : [msg]")
if(check_rights_for(src, R_ADMIN,0))
- msg = "MENTOR:[key_name(src, 0, 0)]: [msg]"
+ msg = "MENTOR:[key_name(src, 0, 0)]: [msg]"
else
- msg = "MENTOR:[key_name(src, 0, 0)]: [msg]"
- to_chat(GLOB.admins | GLOB.mentors, msg)
\ No newline at end of file
+ msg = "MENTOR:[key_name(src, 0, 0)]: [msg]"
+ to_chat(GLOB.admins | GLOB.mentors, msg, confidential = TRUE)
diff --git a/modular_citadel/code/modules/mob/cit_emotes.dm b/modular_citadel/code/modules/mob/cit_emotes.dm
index e58c6bda30..3b442af1c7 100644
--- a/modular_citadel/code/modules/mob/cit_emotes.dm
+++ b/modular_citadel/code/modules/mob/cit_emotes.dm
@@ -3,10 +3,6 @@
/mob
var/nextsoundemote = 1
-//Disables the custom emote blacklist from TG that normally applies to slimes.
-/datum/emote/living/custom
- mob_type_blacklist_typecache = list(/mob/living/brain)
-
/datum/emote/living/insult
key = "insult"
key_third_person = "insults"
@@ -74,7 +70,6 @@
emote_type = EMOTE_AUDIBLE
muzzle_ignore = TRUE
restraint_check = TRUE
- mob_type_allowed_typecache = list(/mob/living/carbon, /mob/living/silicon/pai)
/datum/emote/living/snap/run_emote(mob/living/user, params)
if(!(. = ..()))
@@ -91,7 +86,6 @@
emote_type = EMOTE_AUDIBLE
muzzle_ignore = TRUE
restraint_check = TRUE
- mob_type_allowed_typecache = list(/mob/living/carbon, /mob/living/silicon/pai)
/datum/emote/living/snap2/run_emote(mob/living/user, params)
if(!(. = ..()))
@@ -108,7 +102,6 @@
emote_type = EMOTE_AUDIBLE
muzzle_ignore = TRUE
restraint_check = TRUE
- mob_type_allowed_typecache = list(/mob/living/carbon, /mob/living/silicon/pai)
/datum/emote/living/snap3/run_emote(mob/living/user, params)
if(!(. = ..()))
@@ -125,7 +118,6 @@
emote_type = EMOTE_AUDIBLE
muzzle_ignore = FALSE
restraint_check = FALSE
- mob_type_allowed_typecache = list(/mob/living/carbon, /mob/living/silicon/pai)
/datum/emote/living/awoo/run_emote(mob/living/user, params)
if(!(. = ..()))
@@ -142,7 +134,6 @@
emote_type = EMOTE_AUDIBLE
muzzle_ignore = FALSE
restraint_check = FALSE
- mob_type_allowed_typecache = list(/mob/living/carbon, /mob/living/silicon/pai)
/datum/emote/living/nya/run_emote(mob/living/user, params)
if(!(. = ..()))
@@ -159,7 +150,6 @@
emote_type = EMOTE_AUDIBLE
muzzle_ignore = FALSE
restraint_check = FALSE
- mob_type_allowed_typecache = list(/mob/living/carbon, /mob/living/silicon/pai)
/datum/emote/living/weh/run_emote(mob/living/user, params)
if(!(. = ..()))
@@ -176,7 +166,6 @@
emote_type = EMOTE_AUDIBLE
muzzle_ignore = FALSE
restraint_check = FALSE
- mob_type_allowed_typecache = list(/mob/living/carbon, /mob/living/silicon/pai)
/datum/emote/living/peep/run_emote(mob/living/user, params)
if(!(. = ..()))
@@ -200,7 +189,6 @@
emote_type = EMOTE_AUDIBLE
muzzle_ignore = FALSE
restraint_check = FALSE
- mob_type_allowed_typecache = list(/mob/living/carbon, /mob/living/silicon/pai)
/datum/emote/living/mothsqueak/run_emote(mob/living/user, params)
if(!(. = ..()))
@@ -217,7 +205,6 @@
emote_type = EMOTE_AUDIBLE
muzzle_ignore = FALSE
restraint_check = FALSE
- mob_type_allowed_typecache = list(/mob/living/carbon, /mob/living/silicon/pai)
/datum/emote/living/merp/run_emote(mob/living/user, params)
if(!(. = ..()))
@@ -234,7 +221,6 @@
emote_type = EMOTE_AUDIBLE
muzzle_ignore = FALSE
restraint_check = FALSE
- mob_type_allowed_typecache = list(/mob/living/carbon, /mob/living/silicon/pai)
/datum/emote/living/bark/run_emote(mob/living/user, params)
if(!(. = ..()))
@@ -252,7 +238,6 @@
emote_type = EMOTE_AUDIBLE
muzzle_ignore = FALSE
restraint_check = FALSE
- mob_type_allowed_typecache = list(/mob/living/carbon, /mob/living/silicon/pai)
/datum/emote/living/squish/run_emote(mob/living/user, params)
if(!(. = ..()))
diff --git a/modular_citadel/code/modules/projectiles/guns/ballistic/handguns.dm b/modular_citadel/code/modules/projectiles/guns/ballistic/handguns.dm
index 20917c4ba5..4829fd921c 100644
--- a/modular_citadel/code/modules/projectiles/guns/ballistic/handguns.dm
+++ b/modular_citadel/code/modules/projectiles/guns/ballistic/handguns.dm
@@ -16,13 +16,12 @@
spread = 20
actions_types = list()
-/obj/item/gun/ballistic/automatic/toy/pistol/stealth/update_icon()
- ..()
+/obj/item/gun/ballistic/automatic/toy/pistol/stealth/update_overlays()
+ . = ..()
if(magazine)
- cut_overlays()
- add_overlay("foamsp-magazine")
- else
- cut_overlays()
+ . += "foamsp-magazine"
+
+/obj/item/gun/ballistic/automatic/toy/pistol/stealth/update_icon_state()
icon_state = "[initial(icon_state)][chambered ? "" : "-e"]"
/////////RAYGUN MEMES/////////
diff --git a/modular_citadel/code/modules/projectiles/guns/ballistic/rifles.dm b/modular_citadel/code/modules/projectiles/guns/ballistic/rifles.dm
index 8a1310d2f1..c2bf251de9 100644
--- a/modular_citadel/code/modules/projectiles/guns/ballistic/rifles.dm
+++ b/modular_citadel/code/modules/projectiles/guns/ballistic/rifles.dm
@@ -155,11 +155,15 @@
casing_ejector = 0
spread = 10
recoil = 0.05
+ automatic_burst_overlay = FALSE
+ var/magtype = "flechettegun"
-/obj/item/gun/ballistic/automatic/flechette/update_icon()
- cut_overlays()
+/obj/item/gun/ballistic/automatic/flechette/update_overlays()
+ . = ..()
if(magazine)
- add_overlay("flechettegun-magazine")
+ . += "[magtype]-magazine"
+
+/obj/item/gun/ballistic/automatic/flechette/update_icon_state()
icon_state = "[initial(icon_state)][chambered ? "" : "-e"]"
///unique variant///
@@ -185,12 +189,7 @@
w_class = WEIGHT_CLASS_SMALL
spread = 15
recoil = 0.1
-
-/obj/item/gun/ballistic/automatic/flechette/shredder/update_icon()
- cut_overlays()
- if(magazine)
- add_overlay("shreddergun-magazine")
- icon_state = "[initial(icon_state)][chambered ? "" : "-e"]"
+ magtype = "shreddergun"
/*/////////////////////////////////////////////////////////////
//////////////////////// Zero's Meme //////////////////////////
@@ -218,17 +217,19 @@
burst_size = 4 //Shh.
fire_delay = 1
var/body_color = "#3333aa"
+ automatic_burst_overlay = FALSE
-/obj/item/gun/ballistic/automatic/AM4B/update_icon()
- ..()
+/obj/item/gun/ballistic/automatic/AM4B/ComponentInitialize()
+ . = ..()
+ AddElement(/datum/element/update_icon_updates_onmob)
+
+/obj/item/gun/ballistic/automatic/AM4B/update_overlays()
+ . = ..()
var/mutable_appearance/body_overlay = mutable_appearance('modular_citadel/icons/obj/guns/cit_guns.dmi', "AM4-Body")
if(body_color)
body_overlay.color = body_color
- cut_overlays() //So that it doesn't keep stacking overlays non-stop on top of each other
- add_overlay(body_overlay)
- if(ismob(loc))
- var/mob/M = loc
- M.update_inv_hands()
+ . += body_overlay
+
/obj/item/gun/ballistic/automatic/AM4B/AltClick(mob/living/user)
. = ..()
if(!in_range(src, user)) //Basic checks to prevent abuse
diff --git a/modular_citadel/code/modules/projectiles/guns/ballistic/spinfusor.dm b/modular_citadel/code/modules/projectiles/guns/ballistic/spinfusor.dm
index 3c0a47bfd7..c4cf8fc00f 100644
--- a/modular_citadel/code/modules/projectiles/guns/ballistic/spinfusor.dm
+++ b/modular_citadel/code/modules/projectiles/guns/ballistic/spinfusor.dm
@@ -55,8 +55,7 @@
/obj/item/gun/ballistic/automatic/spinfusor/attack_self(mob/living/user)
return //caseless rounds are too glitchy to unload properly. Best to make it so that you cannot remove disks from the spinfusor
-/obj/item/gun/ballistic/automatic/spinfusor/update_icon()
- ..()
+/obj/item/gun/ballistic/automatic/spinfusor/update_icon_state()
icon_state = "spinfusor[magazine ? "-[get_ammo(1)]" : ""]"
/obj/item/ammo_box/aspinfusor
diff --git a/modular_citadel/code/modules/projectiles/guns/energy/energy_gun.dm b/modular_citadel/code/modules/projectiles/guns/energy/energy_gun.dm
index 49d48e0000..65609f5830 100644
--- a/modular_citadel/code/modules/projectiles/guns/energy/energy_gun.dm
+++ b/modular_citadel/code/modules/projectiles/guns/energy/energy_gun.dm
@@ -14,16 +14,16 @@ obj/item/gun/energy/e_gun/cx
flight_y_offset = 10
var/body_color = "#252528"
-obj/item/gun/energy/e_gun/cx/update_icon()
- ..()
+obj/item/gun/energy/e_gun/cx/ComponentInitialize()
+ . = ..()
+ AddElement(/datum/element/update_icon_updates_onmob)
+
+obj/item/gun/energy/e_gun/cx/update_overlays()
+ . = ..()
var/mutable_appearance/body_overlay = mutable_appearance('modular_citadel/icons/obj/guns/cit_guns.dmi', "cxegun_body")
if(body_color)
body_overlay.color = body_color
- add_overlay(body_overlay)
-
- if(ismob(loc))
- var/mob/M = loc
- M.update_inv_hands()
+ . += body_overlay
obj/item/gun/energy/e_gun/cx/AltClick(mob/living/user)
. = ..()
diff --git a/modular_citadel/code/modules/projectiles/guns/pumpenergy.dm b/modular_citadel/code/modules/projectiles/guns/pumpenergy.dm
index 03a124e306..e81c7c18d3 100644
--- a/modular_citadel/code/modules/projectiles/guns/pumpenergy.dm
+++ b/modular_citadel/code/modules/projectiles/guns/pumpenergy.dm
@@ -12,7 +12,7 @@
/obj/item/gun/energy/pumpaction/emp_act(severity) //makes it not rack itself when emp'd
cell.use(round(cell.charge / severity))
- chambered = 0 //we empty the chamber
+ chambered = null //we empty the chamber
update_icon()
/obj/item/gun/energy/pumpaction/process() //makes it not rack itself when self-charging
@@ -20,7 +20,7 @@
charge_tick++
if(charge_tick < charge_delay)
return
- charge_tick = 0
+ charge_tick = null
if(selfcharge == EGUN_SELFCHARGE_BORG)
var/atom/owner = loc
if(istype(owner, /obj/item/robot_module))
@@ -44,7 +44,7 @@
if(chambered && !chambered.BB) //if BB is null, i.e the shot has been fired...
var/obj/item/ammo_casing/energy/shot = chambered
cell.use(shot.e_cost)//... drain the cell cell
- chambered = 0 //either way, released the prepared shot
+ chambered = null //either way, released the prepared shot
/obj/item/gun/energy/pumpaction/post_set_firemode()
var/has_shot = chambered
@@ -52,13 +52,13 @@
if(has_shot)
recharge_newshot(TRUE)
-/obj/item/gun/energy/pumpaction/update_icon() //adds racked indicators
+/obj/item/gun/energy/pumpaction/update_overlays() //adds racked indicators
..()
var/obj/item/ammo_casing/energy/shot = ammo_type[current_firemode_index]
if(chambered)
- add_overlay("[icon_state]_rack_[shot.select_name]")
+ . += "[icon_state]_rack_[shot.select_name]"
else
- add_overlay("[icon_state]_rack_empty")
+ . += "[icon_state]_rack_empty"
/obj/item/gun/energy/pumpaction/proc/pump(mob/M) //pumping proc. Checks if the gun is empty and plays a different sound if it is.
var/obj/item/ammo_casing/energy/shot = ammo_type[current_firemode_index]
diff --git a/modular_citadel/code/modules/reagents/chemistry/recipes/fermi.dm b/modular_citadel/code/modules/reagents/chemistry/recipes/fermi.dm
index a24a5beaad..0cee2fc5cd 100644
--- a/modular_citadel/code/modules/reagents/chemistry/recipes/fermi.dm
+++ b/modular_citadel/code/modules/reagents/chemistry/recipes/fermi.dm
@@ -178,7 +178,7 @@
name = "Sucubus milk"
id = /datum/reagent/fermi/breast_enlarger
results = list(/datum/reagent/fermi/breast_enlarger = 8)
- required_reagents = list(/datum/reagent/medicine/salglu_solution = 1, /datum/reagent/consumable/milk = 1, /datum/reagent/medicine/synthflesh = 2, /datum/reagent/silicon = 3, /datum/reagent/drug/aphrodisiac = 3)
+ required_reagents = list(/datum/reagent/medicine/salglu_solution = 2, /datum/reagent/consumable/milk = 1, /datum/reagent/medicine/synthflesh = 2, /datum/reagent/silicon = 5)
mix_message = "the reaction gives off a mist of milk."
//FermiChem vars:
OptimalTempMin = 200
@@ -218,7 +218,7 @@
name = "Incubus draft"
id = /datum/reagent/fermi/penis_enlarger
results = list(/datum/reagent/fermi/penis_enlarger = 8)
- required_reagents = list(/datum/reagent/blood = 5, /datum/reagent/medicine/synthflesh = 2, /datum/reagent/carbon = 2, /datum/reagent/drug/aphrodisiac = 2, /datum/reagent/medicine/salglu_solution = 1)
+ required_reagents = list(/datum/reagent/blood = 5, /datum/reagent/medicine/synthflesh = 2, /datum/reagent/carbon = 5, /datum/reagent/medicine/salglu_solution = 2)
mix_message = "the reaction gives off a spicy mist."
//FermiChem vars:
OptimalTempMin = 200
@@ -253,6 +253,7 @@
holder.remove_reagent(type, cached_volume)
holder.add_reagent(/datum/reagent/fermi/PEsmaller, cached_volume)
+/*
/datum/chemical_reaction/fermi/astral
name = "Astrogen"
id = /datum/reagent/fermi/astral
@@ -274,7 +275,7 @@
FermiChem = TRUE
FermiExplode = TRUE
PurityMin = 0.25
-
+*/
/datum/chemical_reaction/fermi/enthrall //check this
name = "MKUltra"
@@ -384,7 +385,7 @@
name = "Furranium"
id = /datum/reagent/fermi/furranium
results = list(/datum/reagent/fermi/furranium = 5)
- required_reagents = list(/datum/reagent/drug/aphrodisiac = 1, /datum/reagent/moonsugar = 1, /datum/reagent/silver = 2, /datum/reagent/medicine/salglu_solution = 1)
+ required_reagents = list(/datum/reagent/pax/catnip = 1, /datum/reagent/silver = 2, /datum/reagent/medicine/salglu_solution = 2)
mix_message = "You think you can hear a howl come from the beaker."
//FermiChem vars:
OptimalTempMin = 350
@@ -402,10 +403,6 @@
FermiChem = TRUE
PurityMin = 0.3
-/datum/chemical_reaction/fermi/furranium/organic
- id = "furranium_organic"
- required_reagents = list(/datum/reagent/drug/aphrodisiac = 1, /datum/reagent/pax/catnip = 1, /datum/reagent/silver = 2, /datum/reagent/medicine/salglu_solution = 1)
-
//FOR INSTANT REACTIONS - DO NOT MULTIPLY LIMIT BY 10.
//There's a weird rounding error or something ugh.
@@ -504,7 +501,10 @@
PurityMin = 0.6
/datum/chemical_reaction/fermi/plushmium/FermiExplode(datum/reagents, var/atom/my_atom, volume, temp, pH)
- new /obj/item/toy/plush/random(get_turf(my_atom))
+ if(volume < 20) //It creates a normal plush at low volume.. at higher amounts, things get slightly more interesting.
+ new /obj/item/toy/plush/random(get_turf(my_atom))
+ else
+ new /obj/item/toy/plush/plushling(get_turf(my_atom))
my_atom.visible_message("The reaction suddenly zaps, creating a plushie!")
my_atom.reagents.clear_reagents()
@@ -607,4 +607,4 @@
ThermicConstant = 0
HIonRelease = 0.01
RateUpLim = 15
- FermiChem = TRUE
\ No newline at end of file
+ FermiChem = TRUE
diff --git a/modular_citadel/icons/mob/citadel_refs/furry_parts_greyscale.dmi b/modular_citadel/icons/mob/citadel_refs/furry_parts_greyscale.dmi
deleted file mode 100644
index c8d5ceb0a6..0000000000
Binary files a/modular_citadel/icons/mob/citadel_refs/furry_parts_greyscale.dmi and /dev/null differ
diff --git a/modular_citadel/icons/mob/mam_snouts.dmi b/modular_citadel/icons/mob/mam_snouts.dmi
index ab1a4654b4..4f6682f789 100644
Binary files a/modular_citadel/icons/mob/mam_snouts.dmi and b/modular_citadel/icons/mob/mam_snouts.dmi differ
diff --git a/modular_citadel/icons/mob/mutant_bodyparts.dmi b/modular_citadel/icons/mob/mutant_bodyparts.dmi
deleted file mode 100644
index 6098dd3567..0000000000
Binary files a/modular_citadel/icons/mob/mutant_bodyparts.dmi and /dev/null differ
diff --git a/sound/ambience/antag/ecult_op.ogg b/sound/ambience/antag/ecult_op.ogg
new file mode 100644
index 0000000000..9944e833a6
Binary files /dev/null and b/sound/ambience/antag/ecult_op.ogg differ
diff --git a/sound/effects/creak1.ogg b/sound/effects/creak1.ogg
new file mode 100644
index 0000000000..0cad4802ff
Binary files /dev/null and b/sound/effects/creak1.ogg differ
diff --git a/sound/effects/creak2.ogg b/sound/effects/creak2.ogg
new file mode 100644
index 0000000000..707bf39e33
Binary files /dev/null and b/sound/effects/creak2.ogg differ
diff --git a/sound/effects/creak3.ogg b/sound/effects/creak3.ogg
new file mode 100644
index 0000000000..88ff37a339
Binary files /dev/null and b/sound/effects/creak3.ogg differ
diff --git a/sound/effects/explosioncreak1.ogg b/sound/effects/explosioncreak1.ogg
new file mode 100644
index 0000000000..474f5febb5
Binary files /dev/null and b/sound/effects/explosioncreak1.ogg differ
diff --git a/sound/effects/explosioncreak2.ogg b/sound/effects/explosioncreak2.ogg
new file mode 100644
index 0000000000..75d323eb06
Binary files /dev/null and b/sound/effects/explosioncreak2.ogg differ
diff --git a/sound/effects/footstep/rustystep1.ogg b/sound/effects/footstep/rustystep1.ogg
new file mode 100644
index 0000000000..bf90d52779
Binary files /dev/null and b/sound/effects/footstep/rustystep1.ogg differ
diff --git a/sound/effects/blood1.ogg b/sound/effects/wounds/blood1.ogg
similarity index 100%
rename from sound/effects/blood1.ogg
rename to sound/effects/wounds/blood1.ogg
diff --git a/sound/effects/blood2.ogg b/sound/effects/wounds/blood2.ogg
similarity index 100%
rename from sound/effects/blood2.ogg
rename to sound/effects/wounds/blood2.ogg
diff --git a/sound/effects/blood3.ogg b/sound/effects/wounds/blood3.ogg
similarity index 100%
rename from sound/effects/blood3.ogg
rename to sound/effects/wounds/blood3.ogg
diff --git a/sound/effects/crack1.ogg b/sound/effects/wounds/crack1.ogg
similarity index 100%
rename from sound/effects/crack1.ogg
rename to sound/effects/wounds/crack1.ogg
diff --git a/sound/effects/crack2.ogg b/sound/effects/wounds/crack2.ogg
similarity index 100%
rename from sound/effects/crack2.ogg
rename to sound/effects/wounds/crack2.ogg
diff --git a/sound/effects/wounds/crackandbleed.ogg b/sound/effects/wounds/crackandbleed.ogg
new file mode 100644
index 0000000000..ea07f13d48
Binary files /dev/null and b/sound/effects/wounds/crackandbleed.ogg differ
diff --git a/sound/effects/wounds/pierce1.ogg b/sound/effects/wounds/pierce1.ogg
new file mode 100644
index 0000000000..cd7b7c3961
Binary files /dev/null and b/sound/effects/wounds/pierce1.ogg differ
diff --git a/sound/effects/wounds/pierce2.ogg b/sound/effects/wounds/pierce2.ogg
new file mode 100644
index 0000000000..4977cab299
Binary files /dev/null and b/sound/effects/wounds/pierce2.ogg differ
diff --git a/sound/effects/wounds/pierce3.ogg b/sound/effects/wounds/pierce3.ogg
new file mode 100644
index 0000000000..e81700b134
Binary files /dev/null and b/sound/effects/wounds/pierce3.ogg differ
diff --git a/sound/effects/sizzle1.ogg b/sound/effects/wounds/sizzle1.ogg
similarity index 100%
rename from sound/effects/sizzle1.ogg
rename to sound/effects/wounds/sizzle1.ogg
diff --git a/sound/effects/sizzle2.ogg b/sound/effects/wounds/sizzle2.ogg
similarity index 100%
rename from sound/effects/sizzle2.ogg
rename to sound/effects/wounds/sizzle2.ogg
diff --git a/sound/machines/card_slide.ogg b/sound/machines/card_slide.ogg
new file mode 100644
index 0000000000..f97ed15fbf
Binary files /dev/null and b/sound/machines/card_slide.ogg differ
diff --git a/sound/machines/sm/accent/delam/1.ogg b/sound/machines/sm/accent/delam/1.ogg
new file mode 100644
index 0000000000..75c79f89ab
Binary files /dev/null and b/sound/machines/sm/accent/delam/1.ogg differ
diff --git a/sound/machines/sm/accent/delam/10.ogg b/sound/machines/sm/accent/delam/10.ogg
new file mode 100644
index 0000000000..c87b63b526
Binary files /dev/null and b/sound/machines/sm/accent/delam/10.ogg differ
diff --git a/sound/machines/sm/accent/delam/11.ogg b/sound/machines/sm/accent/delam/11.ogg
new file mode 100644
index 0000000000..c7f678245b
Binary files /dev/null and b/sound/machines/sm/accent/delam/11.ogg differ
diff --git a/sound/machines/sm/accent/delam/12.ogg b/sound/machines/sm/accent/delam/12.ogg
new file mode 100644
index 0000000000..a395942183
Binary files /dev/null and b/sound/machines/sm/accent/delam/12.ogg differ
diff --git a/sound/machines/sm/accent/delam/13.ogg b/sound/machines/sm/accent/delam/13.ogg
new file mode 100644
index 0000000000..934f17947d
Binary files /dev/null and b/sound/machines/sm/accent/delam/13.ogg differ
diff --git a/sound/machines/sm/accent/delam/14.ogg b/sound/machines/sm/accent/delam/14.ogg
new file mode 100644
index 0000000000..4175e5b947
Binary files /dev/null and b/sound/machines/sm/accent/delam/14.ogg differ
diff --git a/sound/machines/sm/accent/delam/15.ogg b/sound/machines/sm/accent/delam/15.ogg
new file mode 100644
index 0000000000..dcf73deb84
Binary files /dev/null and b/sound/machines/sm/accent/delam/15.ogg differ
diff --git a/sound/machines/sm/accent/delam/16.ogg b/sound/machines/sm/accent/delam/16.ogg
new file mode 100644
index 0000000000..20bc19399b
Binary files /dev/null and b/sound/machines/sm/accent/delam/16.ogg differ
diff --git a/sound/machines/sm/accent/delam/17.ogg b/sound/machines/sm/accent/delam/17.ogg
new file mode 100644
index 0000000000..b517fb3d3d
Binary files /dev/null and b/sound/machines/sm/accent/delam/17.ogg differ
diff --git a/sound/machines/sm/accent/delam/18.ogg b/sound/machines/sm/accent/delam/18.ogg
new file mode 100644
index 0000000000..4ef138d27a
Binary files /dev/null and b/sound/machines/sm/accent/delam/18.ogg differ
diff --git a/sound/machines/sm/accent/delam/19.ogg b/sound/machines/sm/accent/delam/19.ogg
new file mode 100644
index 0000000000..f638a6971b
Binary files /dev/null and b/sound/machines/sm/accent/delam/19.ogg differ
diff --git a/sound/machines/sm/accent/delam/2.ogg b/sound/machines/sm/accent/delam/2.ogg
new file mode 100644
index 0000000000..5b480daa2e
Binary files /dev/null and b/sound/machines/sm/accent/delam/2.ogg differ
diff --git a/sound/machines/sm/accent/delam/20.ogg b/sound/machines/sm/accent/delam/20.ogg
new file mode 100644
index 0000000000..6072bc6227
Binary files /dev/null and b/sound/machines/sm/accent/delam/20.ogg differ
diff --git a/sound/machines/sm/accent/delam/21.ogg b/sound/machines/sm/accent/delam/21.ogg
new file mode 100644
index 0000000000..1223dd946d
Binary files /dev/null and b/sound/machines/sm/accent/delam/21.ogg differ
diff --git a/sound/machines/sm/accent/delam/22.ogg b/sound/machines/sm/accent/delam/22.ogg
new file mode 100644
index 0000000000..9ccfb9b55a
Binary files /dev/null and b/sound/machines/sm/accent/delam/22.ogg differ
diff --git a/sound/machines/sm/accent/delam/23.ogg b/sound/machines/sm/accent/delam/23.ogg
new file mode 100644
index 0000000000..6399a8376a
Binary files /dev/null and b/sound/machines/sm/accent/delam/23.ogg differ
diff --git a/sound/machines/sm/accent/delam/24.ogg b/sound/machines/sm/accent/delam/24.ogg
new file mode 100644
index 0000000000..b51d359807
Binary files /dev/null and b/sound/machines/sm/accent/delam/24.ogg differ
diff --git a/sound/machines/sm/accent/delam/25.ogg b/sound/machines/sm/accent/delam/25.ogg
new file mode 100644
index 0000000000..823f22f136
Binary files /dev/null and b/sound/machines/sm/accent/delam/25.ogg differ
diff --git a/sound/machines/sm/accent/delam/26.ogg b/sound/machines/sm/accent/delam/26.ogg
new file mode 100644
index 0000000000..24b2a2f040
Binary files /dev/null and b/sound/machines/sm/accent/delam/26.ogg differ
diff --git a/sound/machines/sm/accent/delam/27.ogg b/sound/machines/sm/accent/delam/27.ogg
new file mode 100644
index 0000000000..4b4b145b7b
Binary files /dev/null and b/sound/machines/sm/accent/delam/27.ogg differ
diff --git a/sound/machines/sm/accent/delam/28.ogg b/sound/machines/sm/accent/delam/28.ogg
new file mode 100644
index 0000000000..7bc71bf0e6
Binary files /dev/null and b/sound/machines/sm/accent/delam/28.ogg differ
diff --git a/sound/machines/sm/accent/delam/29.ogg b/sound/machines/sm/accent/delam/29.ogg
new file mode 100644
index 0000000000..7fec2f271c
Binary files /dev/null and b/sound/machines/sm/accent/delam/29.ogg differ
diff --git a/sound/machines/sm/accent/delam/3.ogg b/sound/machines/sm/accent/delam/3.ogg
new file mode 100644
index 0000000000..5b57cc2707
Binary files /dev/null and b/sound/machines/sm/accent/delam/3.ogg differ
diff --git a/sound/machines/sm/accent/delam/30.ogg b/sound/machines/sm/accent/delam/30.ogg
new file mode 100644
index 0000000000..ed1ec7d89f
Binary files /dev/null and b/sound/machines/sm/accent/delam/30.ogg differ
diff --git a/sound/machines/sm/accent/delam/31.ogg b/sound/machines/sm/accent/delam/31.ogg
new file mode 100644
index 0000000000..0baa82e246
Binary files /dev/null and b/sound/machines/sm/accent/delam/31.ogg differ
diff --git a/sound/machines/sm/accent/delam/32.ogg b/sound/machines/sm/accent/delam/32.ogg
new file mode 100644
index 0000000000..e925b32d67
Binary files /dev/null and b/sound/machines/sm/accent/delam/32.ogg differ
diff --git a/sound/machines/sm/accent/delam/33.ogg b/sound/machines/sm/accent/delam/33.ogg
new file mode 100644
index 0000000000..9ddec0e84a
Binary files /dev/null and b/sound/machines/sm/accent/delam/33.ogg differ
diff --git a/sound/machines/sm/accent/delam/4.ogg b/sound/machines/sm/accent/delam/4.ogg
new file mode 100644
index 0000000000..aa4f4da071
Binary files /dev/null and b/sound/machines/sm/accent/delam/4.ogg differ
diff --git a/sound/machines/sm/accent/delam/5.ogg b/sound/machines/sm/accent/delam/5.ogg
new file mode 100644
index 0000000000..be438f6f15
Binary files /dev/null and b/sound/machines/sm/accent/delam/5.ogg differ
diff --git a/sound/machines/sm/accent/delam/6.ogg b/sound/machines/sm/accent/delam/6.ogg
new file mode 100644
index 0000000000..b89d52a564
Binary files /dev/null and b/sound/machines/sm/accent/delam/6.ogg differ
diff --git a/sound/machines/sm/accent/delam/7.ogg b/sound/machines/sm/accent/delam/7.ogg
new file mode 100644
index 0000000000..3a9cfc62ca
Binary files /dev/null and b/sound/machines/sm/accent/delam/7.ogg differ
diff --git a/sound/machines/sm/accent/delam/8.ogg b/sound/machines/sm/accent/delam/8.ogg
new file mode 100644
index 0000000000..7bc0a727fa
Binary files /dev/null and b/sound/machines/sm/accent/delam/8.ogg differ
diff --git a/sound/machines/sm/accent/delam/9.ogg b/sound/machines/sm/accent/delam/9.ogg
new file mode 100644
index 0000000000..5c1bd37405
Binary files /dev/null and b/sound/machines/sm/accent/delam/9.ogg differ
diff --git a/sound/machines/sm/accent/normal/1.ogg b/sound/machines/sm/accent/normal/1.ogg
new file mode 100644
index 0000000000..e92beed7fe
Binary files /dev/null and b/sound/machines/sm/accent/normal/1.ogg differ
diff --git a/sound/machines/sm/accent/normal/10.ogg b/sound/machines/sm/accent/normal/10.ogg
new file mode 100644
index 0000000000..9efb616f0b
Binary files /dev/null and b/sound/machines/sm/accent/normal/10.ogg differ
diff --git a/sound/machines/sm/accent/normal/11.ogg b/sound/machines/sm/accent/normal/11.ogg
new file mode 100644
index 0000000000..2af0981ef1
Binary files /dev/null and b/sound/machines/sm/accent/normal/11.ogg differ
diff --git a/sound/machines/sm/accent/normal/12.ogg b/sound/machines/sm/accent/normal/12.ogg
new file mode 100644
index 0000000000..2fab78b02d
Binary files /dev/null and b/sound/machines/sm/accent/normal/12.ogg differ
diff --git a/sound/machines/sm/accent/normal/13.ogg b/sound/machines/sm/accent/normal/13.ogg
new file mode 100644
index 0000000000..784e84e4a8
Binary files /dev/null and b/sound/machines/sm/accent/normal/13.ogg differ
diff --git a/sound/machines/sm/accent/normal/14.ogg b/sound/machines/sm/accent/normal/14.ogg
new file mode 100644
index 0000000000..af170394dd
Binary files /dev/null and b/sound/machines/sm/accent/normal/14.ogg differ
diff --git a/sound/machines/sm/accent/normal/15.ogg b/sound/machines/sm/accent/normal/15.ogg
new file mode 100644
index 0000000000..05c88c6b29
Binary files /dev/null and b/sound/machines/sm/accent/normal/15.ogg differ
diff --git a/sound/machines/sm/accent/normal/16.ogg b/sound/machines/sm/accent/normal/16.ogg
new file mode 100644
index 0000000000..46b0e33980
Binary files /dev/null and b/sound/machines/sm/accent/normal/16.ogg differ
diff --git a/sound/machines/sm/accent/normal/17.ogg b/sound/machines/sm/accent/normal/17.ogg
new file mode 100644
index 0000000000..e432b2ee02
Binary files /dev/null and b/sound/machines/sm/accent/normal/17.ogg differ
diff --git a/sound/machines/sm/accent/normal/18.ogg b/sound/machines/sm/accent/normal/18.ogg
new file mode 100644
index 0000000000..1e0e91abc8
Binary files /dev/null and b/sound/machines/sm/accent/normal/18.ogg differ
diff --git a/sound/machines/sm/accent/normal/19.ogg b/sound/machines/sm/accent/normal/19.ogg
new file mode 100644
index 0000000000..31de063e02
Binary files /dev/null and b/sound/machines/sm/accent/normal/19.ogg differ
diff --git a/sound/machines/sm/accent/normal/2.ogg b/sound/machines/sm/accent/normal/2.ogg
new file mode 100644
index 0000000000..05e3c9ff17
Binary files /dev/null and b/sound/machines/sm/accent/normal/2.ogg differ
diff --git a/sound/machines/sm/accent/normal/20.ogg b/sound/machines/sm/accent/normal/20.ogg
new file mode 100644
index 0000000000..36810bd8f1
Binary files /dev/null and b/sound/machines/sm/accent/normal/20.ogg differ
diff --git a/sound/machines/sm/accent/normal/21.ogg b/sound/machines/sm/accent/normal/21.ogg
new file mode 100644
index 0000000000..306e8856e5
Binary files /dev/null and b/sound/machines/sm/accent/normal/21.ogg differ
diff --git a/sound/machines/sm/accent/normal/22.ogg b/sound/machines/sm/accent/normal/22.ogg
new file mode 100644
index 0000000000..38286aa98b
Binary files /dev/null and b/sound/machines/sm/accent/normal/22.ogg differ
diff --git a/sound/machines/sm/accent/normal/23.ogg b/sound/machines/sm/accent/normal/23.ogg
new file mode 100644
index 0000000000..89f85fed91
Binary files /dev/null and b/sound/machines/sm/accent/normal/23.ogg differ
diff --git a/sound/machines/sm/accent/normal/24.ogg b/sound/machines/sm/accent/normal/24.ogg
new file mode 100644
index 0000000000..7c12a3e768
Binary files /dev/null and b/sound/machines/sm/accent/normal/24.ogg differ
diff --git a/sound/machines/sm/accent/normal/25.ogg b/sound/machines/sm/accent/normal/25.ogg
new file mode 100644
index 0000000000..f89175ceb1
Binary files /dev/null and b/sound/machines/sm/accent/normal/25.ogg differ
diff --git a/sound/machines/sm/accent/normal/26.ogg b/sound/machines/sm/accent/normal/26.ogg
new file mode 100644
index 0000000000..9efd1d8ef3
Binary files /dev/null and b/sound/machines/sm/accent/normal/26.ogg differ
diff --git a/sound/machines/sm/accent/normal/27.ogg b/sound/machines/sm/accent/normal/27.ogg
new file mode 100644
index 0000000000..1fb1edbb5a
Binary files /dev/null and b/sound/machines/sm/accent/normal/27.ogg differ
diff --git a/sound/machines/sm/accent/normal/28.ogg b/sound/machines/sm/accent/normal/28.ogg
new file mode 100644
index 0000000000..890c5ea429
Binary files /dev/null and b/sound/machines/sm/accent/normal/28.ogg differ
diff --git a/sound/machines/sm/accent/normal/29.ogg b/sound/machines/sm/accent/normal/29.ogg
new file mode 100644
index 0000000000..cd2aa40714
Binary files /dev/null and b/sound/machines/sm/accent/normal/29.ogg differ
diff --git a/sound/machines/sm/accent/normal/3.ogg b/sound/machines/sm/accent/normal/3.ogg
new file mode 100644
index 0000000000..38de5571a4
Binary files /dev/null and b/sound/machines/sm/accent/normal/3.ogg differ
diff --git a/sound/machines/sm/accent/normal/30.ogg b/sound/machines/sm/accent/normal/30.ogg
new file mode 100644
index 0000000000..87d1782768
Binary files /dev/null and b/sound/machines/sm/accent/normal/30.ogg differ
diff --git a/sound/machines/sm/accent/normal/31.ogg b/sound/machines/sm/accent/normal/31.ogg
new file mode 100644
index 0000000000..9ce3eeb72e
Binary files /dev/null and b/sound/machines/sm/accent/normal/31.ogg differ
diff --git a/sound/machines/sm/accent/normal/32.ogg b/sound/machines/sm/accent/normal/32.ogg
new file mode 100644
index 0000000000..26ca056142
Binary files /dev/null and b/sound/machines/sm/accent/normal/32.ogg differ
diff --git a/sound/machines/sm/accent/normal/33.ogg b/sound/machines/sm/accent/normal/33.ogg
new file mode 100644
index 0000000000..24964c1ce9
Binary files /dev/null and b/sound/machines/sm/accent/normal/33.ogg differ
diff --git a/sound/machines/sm/accent/normal/4.ogg b/sound/machines/sm/accent/normal/4.ogg
new file mode 100644
index 0000000000..2e71e976e8
Binary files /dev/null and b/sound/machines/sm/accent/normal/4.ogg differ
diff --git a/sound/machines/sm/accent/normal/5.ogg b/sound/machines/sm/accent/normal/5.ogg
new file mode 100644
index 0000000000..04852e10f2
Binary files /dev/null and b/sound/machines/sm/accent/normal/5.ogg differ
diff --git a/sound/machines/sm/accent/normal/6.ogg b/sound/machines/sm/accent/normal/6.ogg
new file mode 100644
index 0000000000..bf06c06bbe
Binary files /dev/null and b/sound/machines/sm/accent/normal/6.ogg differ
diff --git a/sound/machines/sm/accent/normal/7.ogg b/sound/machines/sm/accent/normal/7.ogg
new file mode 100644
index 0000000000..d29821701f
Binary files /dev/null and b/sound/machines/sm/accent/normal/7.ogg differ
diff --git a/sound/machines/sm/accent/normal/8.ogg b/sound/machines/sm/accent/normal/8.ogg
new file mode 100644
index 0000000000..0b94b9dbe0
Binary files /dev/null and b/sound/machines/sm/accent/normal/8.ogg differ
diff --git a/sound/machines/sm/accent/normal/9.ogg b/sound/machines/sm/accent/normal/9.ogg
new file mode 100644
index 0000000000..545b038be1
Binary files /dev/null and b/sound/machines/sm/accent/normal/9.ogg differ
diff --git a/sound/machines/sm/loops/calm.ogg b/sound/machines/sm/loops/calm.ogg
new file mode 100644
index 0000000000..cee14fcd13
Binary files /dev/null and b/sound/machines/sm/loops/calm.ogg differ
diff --git a/sound/machines/sm/loops/delamming.ogg b/sound/machines/sm/loops/delamming.ogg
new file mode 100644
index 0000000000..7d79f0e3c4
Binary files /dev/null and b/sound/machines/sm/loops/delamming.ogg differ
diff --git a/sound/machines/sm/supermatter1.ogg b/sound/machines/sm/supermatter1.ogg
index 1860e78800..be5185009e 100644
Binary files a/sound/machines/sm/supermatter1.ogg and b/sound/machines/sm/supermatter1.ogg differ
diff --git a/sound/machines/sm/supermatter2.ogg b/sound/machines/sm/supermatter2.ogg
index fb2d39fe26..5c98d28ed1 100644
Binary files a/sound/machines/sm/supermatter2.ogg and b/sound/machines/sm/supermatter2.ogg differ
diff --git a/sound/machines/sm/supermatter3.ogg b/sound/machines/sm/supermatter3.ogg
index 93ac3d505b..fb8e09166c 100644
Binary files a/sound/machines/sm/supermatter3.ogg and b/sound/machines/sm/supermatter3.ogg differ
diff --git a/sound/machines/twobeep_high.ogg b/sound/machines/twobeep_high.ogg
new file mode 100644
index 0000000000..b97b39a4f0
Binary files /dev/null and b/sound/machines/twobeep_high.ogg differ
diff --git a/sound/music/twilight.ogg b/sound/music/twilight.ogg
new file mode 100644
index 0000000000..635663314d
Binary files /dev/null and b/sound/music/twilight.ogg differ
diff --git a/sound/roundend/CitadelStationHasSeenBetterDays.ogg b/sound/roundend/CitadelStationHasSeenBetterDays.ogg
new file mode 100644
index 0000000000..2fa0c5b33c
Binary files /dev/null and b/sound/roundend/CitadelStationHasSeenBetterDays.ogg differ
diff --git a/sound/voice/borg_deathsound.ogg b/sound/voice/borg_deathsound.ogg
new file mode 100644
index 0000000000..bb11022abe
Binary files /dev/null and b/sound/voice/borg_deathsound.ogg differ
diff --git a/sound/voice/medbot/owo.ogg b/sound/voice/medbot/owo.ogg
index 0fdaa9d483..5eeb502c88 100644
Binary files a/sound/voice/medbot/owo.ogg and b/sound/voice/medbot/owo.ogg differ
diff --git a/sound/voice/scream/android_scream.ogg b/sound/voice/scream/android_scream.ogg
new file mode 100644
index 0000000000..d3f179a84a
Binary files /dev/null and b/sound/voice/scream/android_scream.ogg differ
diff --git a/sound/weapons/etherealhit.ogg b/sound/weapons/etherealhit.ogg
new file mode 100644
index 0000000000..19da870961
Binary files /dev/null and b/sound/weapons/etherealhit.ogg differ
diff --git a/sound/weapons/etherealmiss.ogg b/sound/weapons/etherealmiss.ogg
new file mode 100644
index 0000000000..8feb7cdc91
Binary files /dev/null and b/sound/weapons/etherealmiss.ogg differ
diff --git a/sound/weapons/shot.ogg b/sound/weapons/shot.ogg
new file mode 100644
index 0000000000..6c57482973
Binary files /dev/null and b/sound/weapons/shot.ogg differ
diff --git a/strings/abductee_objectives.txt b/strings/abductee_objectives.txt
index 93fd946818..135bb7673b 100644
--- a/strings/abductee_objectives.txt
+++ b/strings/abductee_objectives.txt
@@ -31,7 +31,7 @@ Flood the station's powernet with as much electricity as you can.
Replace all the floor tiles with wood, carpeting, grass or bling.
You must escape the station! Get the shuttle called!
Don't allow anyone to be cloned.
-The oxygen is killing them all and they don't even know it. Make sure no oxygen is on the station.
+Their cold, mindless gaze pierce your soul. They see your every movements, it's merely a question of time before they try to control us further. Deactivate all cameras!
Your body must be improved. Ingest as many drugs as you can.
You are hungry. Eat as much food as you can find.
You see you see what they cannot you see the open door you seeE you SEeEe you SEe yOU seEee SHOW THEM ALL
diff --git a/strings/names/ethereal.txt b/strings/names/ethereal.txt
new file mode 100644
index 0000000000..d3e6a26e6e
--- /dev/null
+++ b/strings/names/ethereal.txt
@@ -0,0 +1,38 @@
+Aten
+Apollo
+Arche
+Atlas
+Eos
+Halo
+Kale
+Nysa
+Orion
+Pallas
+Rigel
+Themis
+Aurora
+Andromeda
+Lyra
+Saggitarius
+Crux
+Canis
+Cygnus
+Corvus
+Cepheus
+Auriga
+Corona
+Aquilla
+Serpens
+Cetus
+Puppis
+Ophiuchus
+Carina
+Cassiopeia
+Canes
+Fornax
+Berenices
+Coma
+Vela
+Triangulum
+Tau
+Ceti
\ No newline at end of file
diff --git a/strings/round_start_sounds.txt b/strings/round_start_sounds.txt
index c67bf6b4a6..177a3ea0a8 100644
--- a/strings/round_start_sounds.txt
+++ b/strings/round_start_sounds.txt
@@ -25,3 +25,4 @@ sound/music/rocketridersprayer.ogg
sound/music/theend.ogg
sound/music/flyinghigh.ogg
sound/music/samsara.ogg
+sound/music/twilight.ogg
\ No newline at end of file
diff --git a/strings/sillytips.txt b/strings/sillytips.txt
index bc59a109f0..e6710de95e 100644
--- a/strings/sillytips.txt
+++ b/strings/sillytips.txt
@@ -25,6 +25,7 @@ This game is older than most of the people playing it.
Do not go gentle into that good night.
Flashbangs can weaken blob tiles, allowing for you and the crew to easily destroy them.
Just the tip?
+You can grab someone by clicking on them with the grab intent, then upgrade the grab by clicking on them once more. An aggressive grab will momentarily stun someone, allow you to place Mekhi on a table by clicking on it, or throw them by toggling on throwing.
Some people are unable to read text on a game where half of it is based on text.
As the Captain, you can use a whetstone to sharpen your fancy fountain pen for extra robustness.
As the Lawyer, you are the last bastion of roleplay-focused jobs. Even the curator got a whip to go fight people with, that sellout!
@@ -43,5 +44,8 @@ Plasma men are a powerful race with many perks! No really, I swear! So what if t
As a Cargo Tech make sure to always buy a tesla to sell back to CC. They love those those. Trust me!
Help.
Maints.
-BZ stops or slows down Lings chem regeneration drastically, make sure to BZ flood the station when lings are confirmed!
Admins always regret meme options in their polls.
+Putting cat ears on securitrons makes them table people and nya. Mekhi isn't a cat, but he still goes on the table, just roll with it.
+As a Changeling, you can live without a head as they are merely vestigal to you, now, finally, you can be a Dullahan without it being Halloween.
+People actually have fictional sex between fictional characters in this game.
+When in doubt, take a break. A long break, preferably. If the game is wearing down your mental state and it's starting to lose any semblance of fun value, go and do something else for a month or two. By the time you come back, everything you liked will have been changed anyways.
diff --git a/strings/tips.txt b/strings/tips.txt
index 043405c7a0..5dc4e1b985 100644
--- a/strings/tips.txt
+++ b/strings/tips.txt
@@ -1,83 +1,91 @@
Where the space map levels connect is randomized every round, but are otherwise kept consistent within rounds. Remember that they are not necessarily bidirectional!
You can catch thrown items by toggling on your throw mode with an empty hand active.
-To crack the safe in the vault, use a stethoscope or explosives on it.
+To crack the safe in the vault, have a stethoscope in one of your hands and fiddle with the tumbler or you can alternatively use several concentrated explosive charges on it. Remember that the latter may result in the contents of the safe becoming a pile of ash.
You can climb onto a table by dragging yourself onto one. This takes time and drops the items in your hands on the table. Clicking on a table that someone else is climbing onto will knock them down.
You can drag other players onto yourself to open the strip menu, letting you remove their equipment or force them to wear something. Note that exosuits or helmets will block your access to the clothing beneath them, and that certain items take longer to strip or put on than others.
Clicking on a windoor rather then bumping into it will keep it open, you can click it again to close it.
You can spray a fire extinguisher, throw items or fire a gun while floating through space to change your direction. Simply fire opposite to where you want to go.
You can change the control scheme by pressing tab. One is WASD, the other is the arrow keys. Keep in mind that hotkeys are also changed with this.
-All vending machines can be hacked to obtain some contraband items from them, and many can be fed with coins to gain access to premium items.
+All vending machines can be hacked to obtain some contraband items from them, and many may charge extra credits to give you premium items.
Firesuits and winter coats offer mild protection from the cold, allowing you to spend longer periods of time near breaches and space than if wearing nothing at all.
Glass shards can be welded to make glass, and metal rods can be welded to make metal. Ores can be welded too, but this takes a lot of fuel.
If you need to drag multiple people either to safety or to space, bring a locker or crate over and stuff them all in before hauling them off.
-You can grab someone by clicking on them with the grab intent, then upgrade the grab by clicking on them once more. An aggressive grab will momentarily stun someone, allow you to place Mekhi on a table by clicking on it, or throw them by toggling on throwing.
+You can grab someone by clicking on them with the grab intent, then upgrade the grab by clicking on them once more. An aggressive grab can temporarily stun someone depending on their luck with resisting out of it, allowing you to slam them on a table by clicking on it, or throw them by toggling on throwing.
Holding alt and left clicking a tile will allow you to see its contents in the top right window pane, which is much faster than right clicking.
The resist button will allow you to resist out of handcuffs, being buckled to a chair or bed, out of locked lockers and more. Whenever you're stuck, try resisting!
You can move an item out of the way by dragging it and then clicking on an adjacent tile with an empty hand.
-You can recolor certain items like jumpsuits and gloves in washing machines by also throwing in a crayon.
+You can recolor certain items like jumpsuits and gloves in washing machines by also throwing in a crayon. For more advanced fashion you can spray items with a spray can to tint its colors. Some items work better than others at displaying their tints, like sterile and paper masks, or darkly colored gloves.
Maintenance is full of equipment that is randomized every round. Look around and see if anything is worth using.
-Some roles cannot be antagonists by default, but antag selection is decided first. For instance, you can set Security Officer to High without affecting your chances of becoming an antag -- the game will just select a different role.
+Some roles cannot be antagonists by default, but antag selection is decided first. For instance, you can set Security Officer to High without affecting your chances of becoming an antag - the game will just assign you to your next preferred role - or in the case that you have no such preferences set, a random role entirely.
There are many places around the station to hide contraband. A few for starters: linen boxes, toilet cisterns, body bags. Experiment to find more!
On all maps, you can use a machine in the vault to deposit space cash for cargo points. Otherwise, use it to steal the station's cash and get out before the alarm goes off.
-As the Captain, you are one of the highest priority targets on the station. Everything from revolutions, to nuclear operatives, to traitors that need to rob you of your unique lasgun or your life are things to worry about.
-As the Captain, always take the nuclear disk and pinpointer with you every shift. It's a good idea to give one of these to another head you can trust with keeping it safe, such as the Head of Security.
+As the Captain, you are one of the highest priority targets on the station. Everything from revolutions looking to thwart your rule, to nuclear operatives seeking the disk, to traitors that need to rob you of your several high value items - or your life are all things to be concerned about.
+As the Captain, always take the nuclear disk and pinpointer with you every shift. It's a good idea to give one of these to another head you can trust with keeping it safe, such as the Head of Personnel.
As the Captain, you have absolute access and control over the station, but this does not mean that being a horrible person won't result in mutiny and a ban.
As the Captain, you have a fancy pen that can be used as a holdout dagger or even as a scalpel in surgery!
As the Captain, you can purchase a new emergency shuttle using a communications console. Some require credits, while others give you credits in exchange. Keep in mind that purchasing dangerous shuttles will incur the ire of your crew.
-As the Chief Medical Officer, your hypospray is like a refillable instant injection syringe that can hold 30 units as opposed to the standard 15.
-As the Chief Medical Officer, coordinate and communicate with your doctors, chemists, and geneticists during a nuclear emergency, blob infestation, or some other crisis to keep people alive and fighting.
-As a Medical Doctor, pester Research for improved surgical tools. They work faster, don't cost much and are typically more deadly.
+As the Chief Medical Officer, your hypospray is like the ones that your Medical Doctors can buy, except it comes in a fancy box that can hold several more hypovials than the standard, and already comes preloaded with specially-made high-capacity hypovials that hold double the reagents the standard ones do.
+As the Chief Medical Officer, coordinate and communicate with your doctors, chemists, and paramedics during a nuclear emergency, blob infestation, or some other crisis to keep people alive and fighting.
+As a Medical Doctor, pester Research for improved surgical tools. They work faster, combine the purposes of several tools in one (scalpel/saw, retractor/hemostat, drill/cautery), and don't cost many materials to boot!
+As a Medical Doctor, the surgical saw and drill are both powerful weapons, the saw is sharp and can slice and dice, while the drill can quickly blind someone if aimed for the eyes. The laser scalpel is an upgraded version producible with Research's aid, and it has the highest force of most common place weapons, while still remaining sharp.
As a Medical Doctor, your belt can hold a full set of surgical tools. Using sterilizine before each attempt during surgery will reduce your failure chance on tricky steps or when using less-than-optimal equipment.
As a Medical Doctor, you can attempt to drain blood from a husk with a syringe to determine the cause. If you can extract blood, it was caused by extreme temperatures or lasers, if there is no blood to extract, you have confirmed the presence of changelings.
As a Medical Doctor, while both heal toxin damage, the difference between charcoal and antitoxin is that charcoal will actively remove all other reagents from one's body, while antitoxin only removes various toxins - but can overdose.
-As a Medical Doctor, you can surgically implant or extract things from people's chests. This can range from putting in a bomb to pulling out an alien larva.
+As a Medical Doctor, you can surgically implant or extract things from people's chests by performing a cavity implant. This could range from inserting a suicide bomb to embedding the nuke disk into the Captain's chest.
+As a Medical Doctor, it's of utmost urgency that you tend to anyone who's been hugged by a facehugger. You only have a couple of minutes from the initial attachment to perform organ manipulation to their chest and remove the rapidly developing alien embryo before it bursts out and immediately kills your patient.
As a Medical Doctor, you must target the correct limb and be on help intent when trying to perform surgery on someone. Using disarm attempt will intentionally fail the surgery step.
-As a Medical Doctor, corpses with the "...and their soul has departed" description no longer have a ghost attached to them and aren't usually revivable or cloneable. However it may prove useful to be creative in your revivification techniques with these bodies.
-As a Medical Doctor, treating plasmamen is not impossible! Salbutamol stops them from suffocating and showers stop them from burning alive. You can even perform surgery on them by doing the procedure on a roller bed under a shower.
+As a Medical Doctor, corpses with the "...and their soul has departed" description no longer have a ghost attached to them and can't be revived. However it may prove useful to be creative in your revivification techniques with these bodies.
+As a Medical Doctor, treating plasmamen is not impossible! Salbutamol and epinephrine stops them from suffocating due to lack of internals and showers stop them from burning alive. You can even perform surgery on them by doing the procedure on a roller bed under a shower.
As a Medical Doctor, you can point your penlight at people to create a medical hologram. This lets them know that you're coming to treat them.
As a Medical Doctor, you can extract implants by holding an empty implant case in your offhand while performing the extraction step.
As a Medical Doctor, clone scanning people will implant them with a health tracker that displays their vitals in the clone records. Useful to check on crew members that didn't activate suit sensors!
-As a Medical Doctor, medical gauze stops bleeding as well as healing 5 brute damage, this even works on the dead! Make sure to always have some gauze on you to stop bleeding before dragging someone.
-As a Chemist, there are dozens of chemicals that can heal, and even more that can cause harm. Experiment!
+As a Medical Doctor, you can deal with patients who have absurd amounts of wounds by putting them in cryo. This will slowly treat all of their wounds simultaneously, but is much slower than direct treatment.
+As a Medical Doctor, Critical Slash wounds are one of the most dangerous conditions someone can have. Apply gauze, epipens, sutures, cauteries, whatever you can, as soon as possible!
+As a Medical Doctor, Saline-Glucose not only acts as a temporary boost to a patient's blood level, it also speeds regeneration! Perfect for drained patients!
+As a Medical Doctor, medical gauze is an incredibly underrated tool. It can be used to entirely halt a limb from bleeding or sling one that's been shattered until it can be given proper attention. This even works on the dead, too! Be sure to stop someone's bleeding whether they're in critical condition or a corpse, as dragging someone whom is bleeding will rapidly deplete them of all their blood.
+As a Chemist, there are dozens of chemicals that can heal, and even more that can cause harm. See which chemicals have the best synergy, both in healing, and in harming. Experiment!
As a Chemist, some chemicals can only be synthesized by heating up the contents in the chemical heater.
As a Chemist, you will be expected to supply crew with certain chemicals. For example, clonexadone and mannitol for the cryo tubes, unstable mutagen and saltpetre for botany as well as healing pills and patches for the front desk.
As a Chemist, you can make 100u bottles from plastic sheets. The ChemMaster can produce infinite 30u glass bottles as well.
+As a Chemist, be sure to stock up some hypovials with useful chemicals for any doctors looking to heal on the go, you can also print out the deluxe hypovials at an autolathe specifically for the CMO's special hypospray.
+As a Chemist, the reagent dartgun, while neutered in its ability to harm - can still be loaded up with morphine for a ghetto sedation weapon, and a quick shot of charcoal can make a slime hybrid regret their life choices in an instant.
As a Geneticist, you can eject someone from cloning early by clicking on the cloner pod with your ID. Note that they will suffer more genetic damage and may lose vital organs from this.
-As a Geneticist, becoming a hulk makes you capable of dealing high melee damage, stunlocking people, and punching through walls. However, you can't fire guns, will lose your hulk status if you take too much damage, and are not considered a human by the AI while you are a hulk.
+As a Geneticist, becoming a hulk makes you capable of dealing high melee damage, becoming immune to most traditional stuns, and punching through walls. However, you can't fire guns, and will lose your hulk status if you take too much damage.
As the Virologist, your viruses can range from healing powers so great that you can heal out of critical status, or diseases so dangerous they can kill the entire crew with airborne spontaneous combustion. Experiment!
As the Virologist, you only require small amounts of vaccine to heal a sick patient. Work with the Chemist to distribute your cures more efficiently.
As the Research Director, you can take AIs out of their cores by loading them into an intelliCard, and then from there into an AI system integrity restorer computer to revive and/or repair them.
As the Research Director, you can lock down cyborgs instead of blowing them up. Then you can have their laws reset or if that doesn't work, safely dismantled.
As the Research Director, you can upgrade your modular console with better computer parts to speed up its functions. This can be useful when using the AI system integrity restorer.
As the Research Director, your console's NTnet monitoring tool can be used to retrieve airlock passkeys, provided that someone used a door remote.
-As a Scientist, you can use the mutation toxin obtained from green slimes to turn yourself into a jelly mutant. Each subspecies has unique features - for example telepathic powers, duplicating bodies or integrating slime extracts!
+As a Scientist, you can use the mutation toxin obtained from green slimes to turn yourself into a jelly mutant. Each subspecies has unique features - for example telepathic powers, duplicating bodies or integrating slime extracts for several unique effects!
As a Scientist, you can maximize the number of uses you get out of a slime by feeding it slime steroid, created from purple slimes, while alive. You can then apply extract enhancer, created from cerulean slimes, on each extract.
-As a Scientist, you can disable anomalies by scanning them with an analyzer, then send a signal on the frequency it gives you with a remote signalling device. This will leave behind an anomaly core, which can be used to construct a Phazon mech, or be used in the destructive analyzer for a 10,000 point bonus!
+As a Scientist, you can disable anomalies by scanning them with an analyzer, and then sending a signal on the frequency it gives you with a remote signalling device. Alternatively, you can print out anomaly defusal tools which can instantly disable an anomaly at the protolathe with some research, both of these methods will leave behind an anomaly core, which can be used to construct a Phazon mech, or be used in the destructive analyzer for a 10,000 point bonus!
As a Scientist, researchable stock parts can seriously improve the efficiency and speed of machines around the station. In some cases, it can even unlock new functions.
As a Scientist, you can generate research points by letting the tachyon-doppler array record increasingly large explosions.
As a Scientist, getting drunk just enough will speed up research. Skol!
As a Scientist, you can get points by placing slime cores into the destructive analyzer! This even works with crossbred slime cores.
-As a Scientist, work with botanists to get different types of seeds, as each type of seed can be used in the destructive analyzer for a small amount of Rnd type points!
+As a Scientist, you can get a minuscule amount of points by sacrificing a packet of seeds from Hydroponics into the destructive analyzer! While each individual one may not yield many points per, you can quite easily amass a very large variety of seeds, which could add up over time for a couple extra minutes shaved off of maxing out RND.
As a Roboticist, keep an ear out for anomaly announcements. If you get your hands on an anomaly core, you can build a Phazon mech!
As a Roboticist, you can repair your cyborgs with a welding tool. If they have taken burn damage from lasers, you can remove their battery, expose the wiring with a screwdriver and replace their wires with a cable coil.
As a Roboticist, you can reset a cyborg's module by cutting and mending the reset wire with a wire cutter.
+As a Roboticist, pay mind when toying with a cyborg's wires. It's best to pulse wires before immediately cutting them, as cutting them right away without knowing what they do may sever them from the AI, or disable their camera.
As a Roboticist, you can greatly help out Shaft Miners by building a Firefighter APLU equipped with a hydraulic clamp and plasma cutter. The mech is ash storm proof and can even walk across lava!
-As a Roboticist, you can augment people with cyborg limbs. Augmented limbs can easily be repaired with cables and welders.
+As a Roboticist, you can augment people with cyborg limbs. Augmented limbs are immune to the vacuum of space and temperatures while they can very easily be repaired with welders (brute) and cable coils (burn).
As a Roboticist, you can use your printer that is linked to the ore silo to teleport mats into your work place!
As a Roboticist, you can upgrade cleanbots with adv mops and brooms to make them faster and better!
-As a Roboticist, you can upgrade medical bots with diamond-tipped syringes, MK.II Hypospray, dispenser-sleeper-chemheater boards to make them inject faster, harder and better chems!
-As the AI, you can click on people's names to look at them. This only works if there are cameras that can see them.
+As a Roboticist, you can upgrade medical bots with diamond-tipped syringes, hyposprays, and chemistry machine boards to make their injections pierce hardsuits, work faster, and inject higher quality medicines!
+As the AI, you can click on people's names when they speak over the radio to jump your eye to them. This only works if there are cameras that can see them and are not wearing anything which would obsfuscate their face or tracking capabilities.
As the AI, you can quickly open and close doors by holding shift while clicking them, bolt them when holding ctrl, and even shock them while holding alt.
-As the AI, you can take pictures with your camera and upload them to newscasters.
-As a Cyborg, choose your module carefully, as only cutting and mending your reset wire will let you repick it. If possible, refrain from choosing a module until a situation that requires one occurs.
-As a Cyborg, you are immune to most forms of stunning, and excel at almost everything far better than humans. However, flashes can easily stunlock you and you cannot do any precision work as you lack hands.
+As the AI, you can take pictures with your camera and upload them to newscasters. Cyborgs also share from this pool of pictures.
+As a Cyborg, choose your module carefully, as only having your reset wire cut and mended by someone capable of manipulation will let you repick it. If possible, refrain from choosing a module until a situation that requires one occurs.
+As a Cyborg, you are immune to most forms of stunning, and excel at almost everything far better than humans. However, flashes and EMPs can easily stunlock you and you fall short in performing any tasks which require hands.
As a Cyborg, you are impervious to fires and heat. If you are rogue, you can release plasma fires everywhere and walk through them without a care in the world!
As a Cyborg, you are extremely vulnerable to EMPs as EMPs both stun you and damage you. The ion rifle in the armory or a traitor with an EMP kit can kill you in seconds.
As a Service Cyborg, your spray can knocks people down. However, it is blocked by gas masks.
-As an Engineering Cyborg, you can attach air alarm/fire alarm/APC frames to walls by placing them on the floor and using a screwdriver on them.
-As a Medical Cyborg, you can fully perform surgery and even augment people. Best of all, they have a 0% failure chance.
-As a Janitor Cyborg, you are the bane of all slaughter demons and even Bubblegum himself. Cleaning up blood stains will severely gimp them.
-As a Janitor Cyborg, you get a fancy bottle of drying agent! If you want to be nice, spray the janitor boots with them to magically upgrade them to absorbent galoshes.
+As an Engineering Cyborg, you can attach air alarm/fire alarm/APC frames to walls by placing them on the floor and using a screwdriver on them. Alternatively, you can use your in-built pseudo-hand manipulator to show those organics who's boss! It can even perform complex tasks such as removing cells from APCs, or inserting plasma canisters into radiation collectors.
+As a Medical Cyborg, you can fully perform surgery and even augment people. Best of all, they have a 0% failure chance, even if done on the floor.
+As a Janitor Cyborg, you are the bane of all slaughter demons and can even foil Bubblegum himself. Cleaning up blood stains will severely gimp them, although the latter may just turn you into robotic paste.
+As a Janitor Cyborg, you get a fancy bottle of drying agent! If you want to be nice, spray the janitor's galoshes with them to magically upgrade them to absorbent galoshes which automatically dry tiles.
As the Chief Engineer, you can rename areas or create entirely new ones using your station blueprints.
As the Chief Engineer, your hardsuit is significantly better than everybody else's. It has the best features of both engineering and atmospherics hardsuits - boasting nigh-invulnerability to radiation and all atmospheric conditions.
As the Chief Engineer, you can spy on and even forge PDA communications with the message monitor console! The key is in your office.
@@ -94,14 +102,14 @@ As an Engineer, you can convert tesla coils into corona analyzers by using a scr
As an Engineer, you can use radiation collectors to generate research points. Load them with a 50/50 oxygen/tritium tank and use a multitool to switch them to research mode.
As an Engineer, don't underestimate the humble P.A.C.M.A.N. generators. With upgraded parts, a couple units working in tandem are sufficient to take over for an exploded engine or shattered solars.
As an Engineer, your departmental protolathe and circuit printer can manufacture the necessary circuit boards and components to build just about anything. Make extra medical machinery everywhere! Build a gibber for security! Set up an array of emitters pointing down the hall! The possibilities are endless!
-As an Engineer, you can pry open secure storage by disabling the engine room APC's main breaker. This is obviously a bad idea if the engine is running.
-Don't forget that Cargo has access to a meteor defense satellite that can be ordered BEFORE meteors hit the station. Any idle Engineers should have this on their to-do list.
-As an Engineer, your RCD can be reloaded with mineral sheets instead of just compressed matter cartridges.
+As an Engineer, you can pry open secure storage by disabling the engine room APC's environmental breaker. This is obviously a bad idea if the engine is running.
+As an Engineer, don't forget that Cargo has access to a meteor defense satellite that can be ordered BEFORE meteors hit the station. Any idle Engineers should have this on their to-do list.
+As an Engineer, your RCD can be reloaded with mineral sheets instead of just compressed matter cartridges. Materials which are combined alloys of other materials (such as reinforced glass and plasteel) provide more matter per sheet to the RCD.
As an Atmospheric Technician, you can unwrench a pipe regardless of the pressures of the gases inside, but if they're too high they can burst out and injure you!
As an Atmospheric Technician, look into replacing your gas pumps with volumetric gas pumps, as those move air in flat numerical amounts, rather than percentages which leave trace gases.
-As an Atmospheric Technician, you are better suited to fighting fires than anyone else. As such, you have access to better firesuits, backpack firefighter tanks, and a completely heat and fire proof rigsuit.
+As an Atmospheric Technician, you are better suited to fighting fires than anyone else. As such, you have access to better firesuits, backpack firefighter tanks, and a completely heat and fire proof hardsuit.
As an Atmospheric Technician, your backpack firefighter tank can launch resin. This resin will extinguish fires and replace any gases with a safe, room-temperature airmix.
-As an Atmospheric Technician, your ATMOS holofan projector blocks gases while allowing objects to pass through. With it, you can quickly contain gas spills, fires and hull breaches. Or, use it to seal a plasmaman cloning room.
+As an Atmospheric Technician, your ATMOS holofan projectors can blocks gases and heat while allowing objects to pass through. With it, you can quickly contain gas spills, fires and hull breaches. Or, use it to create a plasmaman friendly lounge.
As an Atmospheric Technician, burning a plasma/oxygen mix inside the incinerator will not only produce power, but also gases such as tritium and water vapor.
As an Atmospheric Technician, you can change the layer of a pipe by clicking with it on a wrenched pipe or other atmos component of the desired layer.
As an Atmospheric Technician, you can take a few cans worth of N2/N2O and cool it down at local freezers. This is a good idea when dealing with (or preparing for) a supermatter meltdown.
@@ -111,65 +119,70 @@ As the Head of Security, don't let the power go to your head. You may have high
As the Warden, your duty is to be the watchdog of the brig and handler of prisoners when little is happening, and to hand out equipment and weapons to the security officers when a crisis strikes.
As the Warden, keep a close eye on the armory at all times, as it is a favored strike point of nuclear operatives and cocky traitors.
As the Warden, if a prisoner's crimes are heinous enough you can put them in permabrig or the gulag. Make sure to check on them once in a while!
-As the Warden, never underestimate the power of tech slugs! Scattershot fires a cone of weaker lasers, Ion slugs fires EMPs that only effect the tiles they hit, and Pulse slugs fire a singular laser that can one-hit almost any wall!
-As the Warden, you can use a surgical saw on riot shotguns to shorten the barrel, making them able to fit in your backpack.
+As the Warden, never underestimate the power of tech slugs! Scattershot fires a cone of weaker lasers with little damage fall off, Ion slugs fires EMPs that only effect the tiles they hit, and Pulse slugs fire a singular laser that can one-hit almost any wall!
+As the Warden, you can use a surgical saw on riot shotguns to shorten the barrel, making them able to fit in your backpack. Make sure to empty them prior lest you blast yourself in the face!
As the Warden, you can implant criminals you suspect might re-offend with devices that will track their location and allow you to remotely inject them with disabling chemicals.
As the Warden, you can use handcuffs on orange prisoner shoes to turn them into cuffed shoes, forcing prisoners to walk and potentially thwarting an escape.
-As the Warden, tracker implants can be used on sec officers. Doing this will let you track their corpse even without suits, though the implant will biodegrade after 5 minutes.
-As the Warden, cryostasis shotgun darts hold 10u of chemicals that will not react untill it hits someone.
-As the Warden, chemical implants can be loaded with a cocktail of healing or combat chems, perfect for the Hos or other sec officers to use. Be sure to keep a eye on them though, it will not auto inject! EMPs or starvation mite lead to the chemical implant to go off as well.
-As the Warden, tracker implants can be used on sec officers. Doing this will let you be able to message them when telecoms are out, or when you suspect coms are compromised. This is also good against rogue AIs as the prisoner tracker doesn't leave logs or alarms for the AI.
+As the Warden, tracker implants can be used on crewmembers. Doing this will let you track their person even without suit sensors and even instantly teleport to them at the local teleporter, although the implant will biodegrade after 5 minutes if its holder ever expires.
+As the Warden, cryostasis shotgun darts hold 10u of chemicals that will not react until it hits someone.
+As the Warden, chemical implants can be loaded with a cocktail of healing or combat chems, perfect for the HoS or other security officers to make use of in a pinch. Be sure to keep a eye on them though, as they cannot be injected without the prisoner management console! EMPs or starvation might lead to the chemical implant going off preemptively.
+As the Warden, tracker implants can be used on your security officers. Doing this will let you be able to message them when telecomms are out, or when you suspect comms are compromised. This is also good against rogue AIs as the prisoner tracker doesn't leave logs or alarms for the AI.
As a Security Officer, remember that correlation does not equal causation. Someone may have just been at the wrong place at the wrong time!
-As a Security Officer, remember that your belt can hold more then one stun baton.
-As a Security Officer, remember harm battoning someone in the head can deconvert them form a being a rev! This sadly doesn't work against the cult, nor does this protect them from getting reconverted.
-As a Security Officer, remember that you can attach a sec-lite to your taser or your helmet!
+As a Security Officer, remember that your belt can hold more than one stun baton.
+As a Security Officer, remember harm beating someone in the head with a blunt object can deconvert them form a being a revolutionary! This sadly doesn't work against either cult, nor does this protect them from getting reconverted unlike a mindshield implant.
+As a Security Officer, remember that you can attach a seclite to your taser or your helmet!
As a Security Officer, communicate and coordinate with your fellow officers using the security channel (:s) to avoid confusion.
-As a Security Officer, your sechuds or HUDsunglasses can not only see crewmates' job assignments and criminal status, but also if they are mindshield implanted. Use this to your advantage in a revolution to definitively tell who is on your side!
+As a Security Officer, your security HUDglasses can not only see crewmates' job assignments and criminal status, but also if they are mindshield implanted. Use this to your advantage in a revolution to definitively tell who is on your side!
As a Security Officer, mindshield implants can only prevent someone from being turned into a cultist: unlike revolutionaries, it will not de-cult them if they have already been converted.
-As a Security Officer, examining someone while wearing sechuds or HUDsunglasses will let you set their arrest level, which will cause Beepsky and other security bots to chase after them.
-As a Security Officer, you can take out the power cell on your baton to replace it with a better or fully charged one. Just use a screwdriver on your baton to remove the old cell
-As a Security Officer, you can place riot shotguns on your armor, this even works with winter sec coats!
+As a Security Officer, examining someone while wearing your security HUDglasses can allow you to swiftly edit their records and criminal status. Be sure to set someone to WANTED if you can't catch up to them, as it'll alert other officers of who's the bad guy, and cause the little security droids to chase after them for you.
+As a Security Officer, you can take out the power cell on your baton to replace it with a better or fully charged one. Just use a screwdriver on your baton to remove the old cell.
+As a Security Officer, you can just about any firearm on your vest, this even works with other non-standard armor-substitutes like security winter coats!
As the Detective, people leave fingerprints everywhere and on everything. With the exception of white latex, gloves will hide them. All is not lost, however, as gloves leave fibers specific to their kind such as black or nitrile, pointing to a general department.
-As the Detective, you can use your forensics scanner from a distance.
-As the Detective, your revolver can be loaded with .357 ammunition obtained from a hacked autolathe. Firing it has a decent chance to blow up your revolver.
+As the Detective, you can use your forensics scanner from a distance. Use this to scan boxes or other storage containers.
+As the Detective, your revolver can be loaded with .357 ammunition. Use a screwdriver to permanently modify your revolver into using this type of ammunition, be warned however, firing it has a decent chance to cause the revolver to misfire and shoot you in the foot.
As the Lawyer, try to negotiate with the Warden if sentences seem too high for the crime.
-As the Lawyer, you can try to convince the captain and Head of Security to hold trials for prisoners in the courtroom.
+As the Lawyer, you can try to convince the Captain and Head of Security to hold trials for prisoners in the courtroom.
As the Head of Personnel, you are not higher ranking than other heads of staff, even though you are expected to take the Captain's place first should he go missing. If the situation seems too rough for you, consider allowing another head to become temporary Captain.
-As the Head of Personnel, you are just as large a target as the Captain because of the potential power your ID and computer can hand out.
+As the Head of Personnel, you are just as large a target as the Captain because of the potential power your ID and computer can hand out and your comparative vulnerability.
As the Mime, your invisible wall power blocks people as well as projectiles. You can use it in a pinch to delay your pursuer.
-As the Mime, you can use :r and :l to speak through your ventriloquist dummy.
+As the Mime, you can use :r and :l to speak through your ventriloquist dummy. Sadly, this only works if your vow is broken, but at least you don't have to sacrifice your dignity by actually talking.
As the Mime, your oath of silence is your source of power. Breaking it robs you of your powers and of your honor.
+As the Mime, breaking your vow of silence is seen as incredibly dishonorable. Most people will seek to trouble and generally ignore a talking Mime.
As the Clown, if you lose your banana peel, you can still slip people with your PDA! Honk!
As the Clown, eating bananas heals you slightly. Honk!
As the Clown, your Holy Grail is the mineral bananium, which can be given to the Roboticist to build you a fun and robust mech beloved by everyone.
-As the Clown, you can use your stamp on a sheet of cardboard as the first step of making a honkbot. Fun for the whole crew!
-As the Chaplain, your null rod has a lot of functions: it can convert water into holy water, which if spread on the ground prevents wizards from jaunting away, can destroy cultist runes by hitting them, and is a very powerful weapon to boot!
-The Chaplain can bless any container with water by hitting it with their bible. Holy water has a myriad of uses against both cults and large amounts of it are a great contributor to success against them.
-The Chaplain's holy weapon will kill clockwork marauders in two hits.
-As the Chaplain, your bible is also a container that can store small items. Depending on your god, your starting bible may come with a surprise!
-As the Chaplain, you are much more likely to get a response by praying to the gods than most people. To boost your chances, make altars with colorful crayon runes, lit candles, and wire art.
+As the Clown, you can use your stamp on a sheet of flattened cardboard as the first step of making a honkbot. Fun for the whole crew!
+As the Clown, your number one way to win over the crew's favor is by telling jokes and putting forth effort into being comedic. Everyone loves a good clown, but everyone despises a bad one.
+As the Chaplain, your null rod has a lot of functions: while being an incredibly powerful weapon with an array of potential utilities depending upon the skin you chose for it, it also nulls cultist and wizard magic entirely, making you immune to them both and in some cases even harming the caster so long as you keep it in a pocket or in your hands.
+As the Chaplain, you can bless any water container by hitting it with your bible to turn it into holy water. Holy water has a myriad of uses against both cults and large amounts of it are a great contributor to success against them.
+As the Chaplain, your null rod will kill clockwork marauders in two hits while actively hindering their overall combat capabilities just by being nearby to them.
+As the Chaplain, your bible is also a container that can store a singular small item. Depending on your God, your starting bible may come with a surprise!
+As the Chaplain, you are much more likely to get a response by praying to the Gods than most people as your prayers will send a special noise cue directly to them! To further your chances of getting a response even further, pretty up your altar with crayon runes and wire art, and be sure to put a decent amount of effort into your prayers themselves. The Gods don't like lazy bums.
As a Botanist, you can hack the MegaSeed Vendor to get access to more exotic seeds. These seeds can alternatively be ordered from cargo.
As a Botanist, you can mutate the plants growing in your hydroponics trays with unstable mutagen or, as an alternative, crude radioactives from chemistry to get special variations.
-As a Botanist, you should look into increasing the potency of your plants. This increases the size, amount of chemicals, points gained from grinding them in the biogenerator, and lets people know you are a proficient botanist.
-As a Botanist, you can combine production trait chemicals just like a Chemist. Chlorine (blumpkin) + radium and phosphorus (glowshrooms) equals unstable mutagen!
+As a Botanist, you should look into increasing the potency of your plants. This is shown by the size of the plant's sprite, and can increase the amount of chemicals, points gained from grinding them in the biogenerator, and lets people know you are a proficient botanist.
+As a Botanist, you can combine production trait chemicals and mix your own complex chemicals inside of the plants themselves using precursors. Chlorine (blumpkin) + radium and phosphorus (glowshrooms) equals unstable mutagen!
+As a Botanist, earthsblood is an incredibly powerful chemical found in Ambrosia Gaia, it heals all types of damages very rapidly but causes lingering brain damage and has a nasty overdose. You can combine the chemicals from watermelons (water), grass (hydrogen), and cherries (sugar) to mix mannitol in with your earthsblood to completely counteract its main drawback!
+As a Botanist, Ambrosia Gaia is a plant mutated from Ambrosia Deus, which is a plant mutated from Ambrosia Vulgaris. The reagent contained within this plant known as earthsblood can make your trays and soil plots completely self sufficient when a plant containing such reagent is composted into them, meaning they won't need nutrients or water, and they'll automatically kill their own weeds and pests.
As a Cook, you can load your food into snack vending machines.
As a Cook, you can rename your custom made food with a pen.
As a Cook, any food you make will be much healthier than the junk food found in vendors. Having the crew routinely eating from you will provide minor buffs.
-As a Cook, being in the kitchen will make you remember the basics of Close Quarters Cooking. It is highly effective at removing Assistants from your workplace.
+As a Cook, being in the kitchen will make you remember the basics of Close Quarters Cooking (CQC). It is highly effective at removing Assistants from your workplace.
As a Cook, your Kitchenmate can vend out trays that fit on your belt slot. These trays pick up 7 food items at a time and are a quick way to transport large meals.
As a Cook, the advanced roasting stick is used to cook food at a distance, and can be used on SME, singularity, and other objects that cook food normally.
+As a Cook, the deep frier is a tool which can turn very large quantities of seemingly useless objects into food, albeit nutritionally poor and awful tasting food, but hey, food is food.
As the Bartender, the drinks you start with only give you the basics. If you want more advanced mixtures, look into working with chemistry, hydroponics, or even mining for things to grind up and throw in!
-As the Bartender, you can use a circular saw on your shotgun to make it easier to store.
-As a Janitor, if someone steals your janicart, you can instead use your space cleaner spray, grenades, water sprayer, exact bloody revenge or order another from Cargo.
+As the Bartender, you can use a circular saw on your shotgun to make it easier to store. Make sure to empty them prior lest you blast yourself in the face!
+As a Janitor, if someone steals your janicart, you can instead use your spray bottles, soap, and arsenal of slippery objects to exact your bloody revenge.. ..or just order another one from Cargo.
As a Janitor, the trash bag can be used to hold more than trash. Tools, medical equipment, smuggled nuclear disks... You name it!
-As a Janitor, mousetraps can be used to create bombs or booby-trap containers.
-Beware the Curator, for they are not completely defenseless. The curator's whip always disarms people, their laser pointer can blind humans and cyborgs, and can hide items in wirecut books.
+As a Janitor, mousetraps can be used as bomb triggers to booby-trap containers.
+As the Curator, for what it's worth, your toys and position are fairly robust. You can order a claymore, a whip, or a free space suit all at roundstart. The claymore is fairly underwhelming, however the whip is an incredibly robust weapon capable of always disarming, and that space suit is also better than the ones in EVA.
As the Curator, be sure to keep the shelves stocked and the library clean for crew.
As a Cargo Technician, you can hack MULEbots to make them faster, run over people in their way, and even let you ride them!
As a Cargo Technician, you can order contraband items from the supply shuttle console by de-constructing it and using a multitool on the circuit board, the re-assembling it.
As a Cargo Technician, you can earn more cargo points by shipping back crates from maintenance, liquid containers, plasma sheets, rare seeds from hydroponics, and more!
As a Cargo Technician, you get 400 points per packet! Stamp the manifest and sending back the crate will give you 200 points for the paperwork and 200 points for the crate!
-As a Cargo Technician, paperwork is an alternative option to shipping off plasma sheets and other goods. Order Paperwork crates and go into the crafting menu to turn pens and undone paper work into completed grant paper work to get 50 points per sheet!
+As a Cargo Technician, paperwork and glass blowing are alternative options to shipping off plasma sheets and other goods. Order their respective kits and get to work! Paperwork can be done quickly via the crafting menu for a quick buck, while glass blowing is much more lucrative, but may require some more effort and time.
As the Quartermaster, be sure to check the manifests on crates you receive to make sure all the info is correct. If there's a mistake, stamp the manifest DENIED and send it back in a crate with the items untouched for a refund!
As the Quartermaster, you can construct an express supply console that instantly delivers crates by drop pod. The impact will cause a small explosion as well.
As a Shaft Miner, the northern side of Lavaland has a lot more rare minerals than on the south.
@@ -177,25 +190,25 @@ As a Shaft Miner, every monster on Lavaland has a pattern you can exploit to min
As a Shaft Miner, you can harvest goliath plates from goliaths and upgrade your explorer's suit, mining hardsuits as well as Firefighter APLUs with them, greatly reducing incoming melee damage.
As a Shaft Miner, always have a GPS on you, so a fellow miner or cyborg can come to save you if you die.
As a Shaft Miner, you can craft a variety of equipment from the local fauna. Bone axes, lava boats and ash drake armour are just a few of them!
-As a Traitor, the cryptographic sequencer (emag) can not only open doors, but also lockers, crates, APCs and more. It can hack cyborgs, and even cause bots to go berserk. Use it on the right machines, and you can even order more traitor gear or contact the Syndicate. Experiment!
+As a Traitor, the cryptographic sequencer (emag) can not only open lockers, crates, APCs and more. It can also do things like hack cyborgs, and even cause bots to go berserk. Use it on the right machines, and you can even contact the Syndicate. Experiment!
As a Traitor, subverting the AI to serve you can make it an extremely powerful ally. However, be careful of the wording in the laws you give it, as it may use your poorly written laws against you!
As a Traitor, the Captain and the Head of Security are two of the most difficult to kill targets on the station. If either one is your target, plan carefully.
-As a Traitor, you can manufacture and recycle revolver bullets at a hacked autolathe, making the revolver an extremely powerful tool.
+As a Traitor, you can manufacture and recycle revolver bullets at a hacked autolathe, making the revolver an extremely powerful tool if you manage to nab an autolathe for yourself.
As a Traitor, you may sometimes be assigned to hunt other traitors, and in turn be hunted by others.
As a Traitor, the syndicate encryption key is very useful for coordinating plans with your fellow traitors -- or, of course, betraying them.
As a Traitor, plasma can be injected into many things to sabotage them. Power cells, light bulbs, cigars and e-cigs will all explode when used.
As a Nuclear Operative, communication is key! Use :t or :h to speak to your fellow operatives and coordinate an attack plan.
-As a Nuclear Operative, you should look into purchasing a syndicate cyborg, as they can provide heavy fire support, full access, are immune to conventional stuns, and can easily take down the AI.
+As a Nuclear Operative, you should look into purchasing one of the three Syndicate cyborgs in your uplink, as they can provide useful tactical support, function as walking access machines, are immune to conventional stuns, and can easily take down the AI.
As a Nuclear Operative, stick together! While your equipment is robust, your fellow operatives are much better at saving your life: they can drag you away from danger while stunned and provide cover fire.
As a Nuclear Operative, you might end up in a situation where the AI has bolted you into a room. Having some spare C4 in your pocket can save your life.
As a Monkey, you can crawl through air or scrubber vents by alt+left clicking them. You must drop everything you are wearing and holding to do this, however.
As a Monkey, you can still wear a few human items, such as backpacks, gas masks and hats, and still have two free hands.
As the Malfunctioning AI, you can shunt to an APC if the situation gets bad. This disables your doomsday device if it is active.
-As the Malfunctioning AI, you should either order your cyborgs to dismantle the robotics console or blow it up yourself in order to protect them.
+As the Malfunctioning AI, you should either order your cyborgs to dismantle the robotics console or blow it up yourself in order to protect them. Do note that this will prevent you from hacking any cyborg made in the future.
As the Malfunctioning AI, look into flooding the station with plasma fires to kill off large portions of the crew, letting you pick off the remaining few with space suits who escaped.
-Xenomorphs? Science can craft deadly tech shells like pulse slugs and laser scatter shot that are highly effective against any alien threat.
-When fighting aliens, it can be a good idea to turn off the gravity due to the alien's lack of zero-gravity control.
-When fighting xenomorph aliens, consider a shield. Shields can block their pounces and be worn on the back, but beware of neurotoxin.
+Xenomorphs? Any source of burn damage severely harms them. Science can craft deadly tech shells like pulse slugs and laser scatter shot that are highly effective against any alien threat.
+When fighting Aliens, it can be a good idea to turn off the gravity due to the every caste of alien's lack of zero-gravity control, especially Hunters and Drones, which are completely and utterly helpless.
+When fighting Aliens, consider a shield. A raised shield can halt their attempts to slash at you, and their disarms will always remove an item in your hand before knocking you over, always having something in your hand, no matter how small or worthless, can save your life.
As an Alien, your melee prowess is unmatched, but your ranged abilities are sorely lacking. Make use of corners to force a melee confrontation!
As an Alien, you take double damage from all burn attacks, such as lasers, welding tools, and fires. Furthermore, fire can destroy your resin and eggs. Expose areas to space to starve away any flamethrower fires before they can do damage!
As an Alien, resin floors not only regenerate your plasma supply, but also passively heal you. Fight on resin floors to gain a home turf advantage!
@@ -212,6 +225,7 @@ As the Blob, you can produce a Blobbernaut from a factory for 40 resources. Blob
As the Blob, you can expand by clicking, create strong blobs with ctrl-click, rally spores with middle-click, and remove blobs with alt-click. You do not need to have your camera over the tile to do this.
As the Blob, removing strong blobs, resource nodes, factories, and nodes will give you 4, 15, 25, and 25 resources back, respectively.
As the Blob, talking will send a message to all other overminds and all Blobbernauts, allowing you to direct attacks and coordinate.
+As the Blob, always make sure where you land is where you want to be, as it is very unlikely you will be getting too far away from it. Land in key points like the armory or the medical bay to immediately cripple the crew before they even find out you exist. Of course, always take into mind if being immediately discovered may outweight the benefits, and stick to maintenance close to these key points of interest to you.
As a Blobbernaut, you can communicate with overminds and other Blobbernauts via :b.
As a Blobbernaut, your HUD shows your health and the core health of the overmind that created you.
As a Revolutionary, you cannot convert a head of staff or someone who has a mindshield implant, such as a security officer or those they implant. Implants can however be surgically removed, and do not carry over with cloning. Take control of medbay to keep control of conversions!
@@ -220,6 +234,11 @@ As a Revolutionary, cargo can be your best friend or your worst nightmare. In th
As a Revolutionary, your main power comes from how quickly you spread. Convert people as fast as you can and overwhelm the heads of staff before security can arm up.
As a Changeling, the Extract DNA sting counts for your genome absorb objective, but does not let you respec your powers.
As a Changeling, you can absorb someone by strangling them and using the Absorb verb; this gives you the ability to rechoose your powers, the DNA of whoever you absorbed, the memory of the absorbed, and some samples of things the absorbed said.
+As a Changeling, absorbing someone will give you their full memory. This can include things such as a Traitor's uplink, thus absorbing one will allow you to access the Traitor uplink and buy toys for your Changeling self to abuse.
+As a Changeling, absorbing another Changeling will permanently boost your chemical reserve, allow you to pick more abilities, and make the victim unable to revive. Be careful when exposing your identity to other Changelings, as they may be out of those wonderful benefits.
+As a Changeling, BZ gas will dramatically slow down or even halt your natural chemical regeneration, be sure to avoid it at all costs as some lunatics may try and flood portions of the station to deal with you.
+As a Changeling, death is not the end for you! You can revive after two minutes from being dead by triggering your stasis ability, and then waiting for the prompt to resurrect yourself to show up.
+As a Changeling, your Regenerate Limbs power will quickly heal all of your wounds, but they'll still leave scars. Changelings can use Fleshmend to get rid of scars, or you can ingest Carpotoxin to get rid of them like a normal person.
As a Cultist, do not cause too much chaos before your objective is completed. If the shuttle gets called too soon, you may not have enough time to win.
As a Cultist, your team starts off very weak, but if necessary can quickly convert everything they have into raw power. Make sure you have the numbers and equipment to support going loud, or the cult will fall flat on its face.
As a Cultist, the Blood Boil rune will deal massive amounts of brute damage to non-cultists, stamina damage to Ratvarian scum, and some damage to fellow cultists of Nar-Sie nearby, but will create a fire where the rune stands on use.
@@ -239,9 +258,10 @@ You can deconvert Cultists of Nar-Sie and Servants of Ratvar by feeding them lar
Tiles sprayed with holy water will permanently block Servants of Ratvar from teleporting onto them.
As a Wizard, you can turn people to stone, then animate the resulting statue with a staff of animation to create an extremely powerful minion, for all of 5 minutes at least.
As a Wizard, the fireball spell performs very poorly at close range, as it can easily catch you in the blast. It is best used as a form of artillery down long hallways.
-As a Wizard, summoning guns will turn a large portion of the crew against themselves, but will also give everyone anything from a pea shooter to a BFG 9000. Use at your own risk!
+As a Wizard, summoning guns will turn a large portion of the crew against themselves, but will also give everyone anything from a energy pistol to a pulse rifle. Use at your own risk!
As a Wizard, the staff of chaos can fire any type of bolts from the magical wands. This can range from bolts of instant death to healing or reviving someone.
As a Wizard, most spells become unusable if you are not wearing your robe, hat, and sandals.
+As a Wizard, it's advisable that you don't dump all of your limited spell points into solely offensive spells, if you can't defend yourself then you're sure to get dunked.
As an Abductor, you can select where your victims will be sent on the ship control console.
As an Abductor Agent, the combat mode vest has much higher resistance to every kind of weapon, and your helmet prevents the AI from tracking you.
As an Abductor, the baton can cycle between four modes: stun, sleep, cuff and probe.
@@ -260,17 +280,24 @@ As a Drone, you can ping other drones to alert them of areas in the station in n
As a Drone, you can repair yourself by using a screwdriver on yourself and standing still!
As a Ghost, you can see the inside of a container on the ground by clicking on it.
As a Ghost, you can double click on just about anything to follow it. Or just warp around!
+As a Ghost, there's a button in the OOC tab labeled Observe, it lets you see through someone's eyes as if you were the one who's playing them.
As a Devil, you gain power for every three souls you control, however you also become more obvious.
As a Devil, as long as you control at least one other soul, you will automatically resurrect, as long as a banishment ritual is not performed.
At which time a Devil's nameth is spake on the tongue of man, the Devil may appeareth.
You can swap floor tiles by holding a crowbar in one hand and a stack of tiles in the other.
-When hacking doors, cutting and mending the "test light wire" will restore power to the door.
-When hacking, remote singulars pulse when attached to a wire and pinged. This can allow you to hack things or set traps from far away.
+When hacking doors, cutting and mending a "test light wire" will restore power to the door.
When crafting most items, you can either manually combine parts or use the crafting menu.
Suit storage units not only remove blood and dirt from clothing, but also radiation!
Remote devices will work when used through cameras. For example: Bluespace RPEDs and door remotes.
+You can light a cigar on a supermatter crystal.
+Using sticky tape on items can make them stick to people and walls! Be careful, grenades might stick to your hand during the moment of truth!
+In a pinch, stripping yourself naked will give you a sizeable resistance to being tackled. What do you value more, your freedom or your dignity?
+Wearing riot armor makes you significantly more effective at performing tackle takedowns, but will use extra stamina with each leap! It will also significantly protect you from other tackles!
+Epipens contain a powerful coagulant that drastically reduces bleeding on all bleeding wounds. If you don't have time to properly treat someone with lots of slashes or piercings, stick them with a pen to buy some time!
+Anything you can light a cigarette with, you can use to cauterize a bleeding wound. Technically, that includes the supermatter.
+Suit storage units entirely purge radiation from any carbon mob put inside of them when cycling, at the cost of some horrific burns, this is a very effective strategy to clean someone up after they bathed in the engine.
Laser pointers can be upgraded by replacing its micro laser with a better one from RnD! Use a screwdriver on it to remove the old laser. Upgrading the laser pointer gives you better odds of stunning a cyborg, and even blinding people with sunglasses.
-Being out of combat mode makes makes you deal less damage to people and objects when attacking.
-Resting makes you deal less damage to people and objects when attacking.
+Being out of combat mode makes makes you deal less damage to people and objects when attacking. This stacks with the penalty incurred by resting.
+Resting makes you deal less damage to people and objects when attacking. This stacks with the penalty incurred by being out of combat mode.
You do not regenerate as much stamina while in combat mode. Resting (being on the ground) makes you regenerate stamina faster.
Remember to be in combat mode while in combat, as otherwise you will be penalized by taking more incoming damage and dealing less damage to your adversary.
diff --git a/strings/traumas.json b/strings/traumas.json
index f461c5f5fd..e5048d41b2 100644
--- a/strings/traumas.json
+++ b/strings/traumas.json
@@ -125,13 +125,15 @@
";chemist can u @pick(create_verbs) holy @pick(mellens) for @pick(s_roles)???!!",
"@pick(semicolon) LIZZARRD SPEAKIGN IN EVIL BULL LANGUAGE SCI!!",
"@pick(semicolon)POST REBOOT MESSAGE LOLOL FUCK FUCK FUCK YOU",
- "@pick(semicolon)so, i was trying to talk to someone on rp today, and then a mime walks up and pies them in the face along with some other prankster--i thought that mimes and clowns are supposed to be hired to entertain not to be a nuisance, and that if entertainment comes at someone elses expense then it's not supposed to be done. is that enough to like submit a player complaint or some shit or am i just being petty?",
"@pick(semicolon)*nya",
"@pick(semicolon)*awoo",
"@pick(semicolon)*merp",
"@pick(semicolon)*weh",
"@pick(semicolon)My balls finally feel full, again.",
- "@pick(semicolon)Assaltign a sec osficer aren't crime if ur @pick(roles)"
+ "@pick(semicolon)Assaltign a sec osficer aren't crime if ur @pick(roles)",
+ ";SEC I SPILED MU JICE HELELPH HELPJ JLEP HELP",
+ "@pick(semicolon) atmos is chemistyr is radation fast air is FASTER cheemsitry and FASTER RADIATION AND FASTER DEATH!!!",
+ "@pick(semicolon) FUC'KING MuNKEY WAch TALKN SHIT"
],
"mutations": [
@@ -199,7 +201,7 @@
"abdoocters",
"revinent"
],
-
+
"bug": [
"",
"IS TIS A BUG??",
@@ -207,7 +209,7 @@
"BUG!!!",
"HUE, FEATURE!!"
],
-
+
"semicolon": [
"",
";",
@@ -271,7 +273,7 @@
"arrdee",
"sek"
],
-
+
"cargo": [
"GUNS",
"HATS",
@@ -279,7 +281,7 @@
"MEMES",
"GLOWY CYSTAL"
],
-
+
"s_roles": [
"ert",
"shadowlig",
diff --git a/strings/wounds/bone_scar_desc.json b/strings/wounds/bone_scar_desc.json
new file mode 100644
index 0000000000..3540547c4a
--- /dev/null
+++ b/strings/wounds/bone_scar_desc.json
@@ -0,0 +1,26 @@
+{
+ "generic": ["general disfigurement"],
+
+ "bluntmoderate": [
+ "the bone equivalent of a faded bruise",
+ "a series of tiny chip marks"
+ ],
+
+ "bluntsevere": [
+ "a series of faded hairline cracks",
+ "a small bone dent"
+ ],
+
+ "bluntcritical": [
+ "large streaks of refilled cracks",
+ "a fractal of reformed stress marks",
+ "a cluster of calluses"
+ ],
+
+ "dismember": [
+ "is slightly misaligned",
+ "has clearly been dropped recently",
+ "has a damaged socket"
+ ]
+
+}
\ No newline at end of file
diff --git a/strings/wounds/flesh_scar_desc.json b/strings/wounds/flesh_scar_desc.json
new file mode 100644
index 0000000000..fb2b927a30
--- /dev/null
+++ b/strings/wounds/flesh_scar_desc.json
@@ -0,0 +1,86 @@
+{
+ "generic": ["general disfigurement"],
+
+ "bluntmoderate": [
+ "light discoloring",
+ "a slight blue tint"
+ ],
+
+ "bluntsevere": [
+ "a faded, fist-sized bruise",
+ "a vaguely triangular peel scar"
+ ],
+
+ "bluntcritical": [
+ "a section of janky skin lines and badly healed scars",
+ "a large patch of uneven skin tone",
+ "a cluster of calluses"
+ ],
+
+
+
+ "slashmoderate": [
+ "light, faded lines",
+ "minor cut marks",
+ "a small faded slit",
+ "a series of small scars"
+ ],
+
+ "slashsevere": [
+ "a twisted line of faded gashes",
+ "a gnarled sickle-shaped slice scar"
+ ],
+
+ "slashcritical": [
+ "a winding path of very badly healed scar tissue",
+ "a series of peaks and valleys along a gruesome line of cut scar tissue",
+ "a grotesque snake of indentations and stitching scars"
+ ],
+
+
+
+ "piercemoderate": [
+ "a small, faded bruise",
+ "a small twist of reformed skin",
+ "a thumb-sized puncture scar"
+ ],
+
+ "piercesevere": [
+ "an ink-splat shaped pocket of scar tissue",
+ "a long-faded puncture wound",
+ "a tumbling puncture hole with evidence of faded stitching"
+ ],
+
+ "piercecritical": [
+ "a rippling shockwave of scar tissue",
+ "a wide, scattered cloud of shrapnel marks",
+ "a gruesome multi-pronged puncture scar"
+ ],
+
+
+
+ "burnmoderate": [
+ "small amoeba-shaped skinmarks",
+ "a faded streak of depressed skin"
+ ],
+
+ "burnsevere": [
+ "a large, jagged patch of faded skin",
+ "random spots of shiny, smooth skin",
+ "spots of taut, leathery skin"
+ ],
+
+ "burncritical": [
+ "massive, disfiguring keloid scars",
+ "several long streaks of badly discolored and malformed skin",
+ "unmistakeable splotches of dead tissue from serious burns"
+ ],
+
+
+ "dismember": [
+ "is several skintone shades paler than the rest of the body",
+ "is a gruesome patchwork of artificial flesh",
+ "has a large series of attachment scars at the articulation points"
+ ]
+
+}
\ No newline at end of file
diff --git a/strings/wounds/scar_loc.json b/strings/wounds/scar_loc.json
new file mode 100644
index 0000000000..f721294925
--- /dev/null
+++ b/strings/wounds/scar_loc.json
@@ -0,0 +1,52 @@
+{
+ "": ["general area"],
+
+ "head": [
+ "left eyebrow",
+ "cheekbone",
+ "neck",
+ "throat",
+ "jawline",
+ "entire face"
+ ],
+
+ "chest": [
+ "upper chest",
+ "lower abdomen",
+ "midsection",
+ "collarbone",
+ "lower back"
+ ],
+
+ "l_arm": [
+ "outer left forearm",
+ "inner left wrist",
+ "left elbow",
+ "left bicep",
+ "left shoulder"
+ ],
+
+ "r_arm": [
+ "outer right forearm",
+ "inner right wrist",
+ "right elbow",
+ "right bicep",
+ "right shoulder"
+ ],
+
+ "l_leg": [
+ "inner left thigh",
+ "outer left calf",
+ "outer left hip",
+ "left kneecap",
+ "lower left shin"
+ ],
+
+ "r_leg": [
+ "inner right thigh",
+ "outer right calf",
+ "outer right hip",
+ "right kneecap",
+ "lower right shin"
+ ]
+}
\ No newline at end of file
diff --git a/tgstation.dme b/tgstation.dme
index b51c21fe2d..294b31f6fb 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -17,6 +17,7 @@
#include "_maps\_basemap.dm"
#include "code\_compile_options.dm"
#include "code\world.dm"
+#include "code\__DEFINES\_extools.dm"
#include "code\__DEFINES\_globals.dm"
#include "code\__DEFINES\_protect.dm"
#include "code\__DEFINES\_tick.dm"
@@ -47,6 +48,7 @@
#include "code\__DEFINES\dynamic.dm"
#include "code\__DEFINES\economy.dm"
#include "code\__DEFINES\events.dm"
+#include "code\__DEFINES\exosuit_fabs.dm"
#include "code\__DEFINES\exports.dm"
#include "code\__DEFINES\fantasy_affixes.dm"
#include "code\__DEFINES\food.dm"
@@ -61,6 +63,7 @@
#include "code\__DEFINES\language.dm"
#include "code\__DEFINES\layers_planes.dm"
#include "code\__DEFINES\lighting.dm"
+#include "code\__DEFINES\loadout.dm"
#include "code\__DEFINES\logging.dm"
#include "code\__DEFINES\machines.dm"
#include "code\__DEFINES\maps.dm"
@@ -79,6 +82,7 @@
#include "code\__DEFINES\networks.dm"
#include "code\__DEFINES\pinpointers.dm"
#include "code\__DEFINES\pipe_construction.dm"
+#include "code\__DEFINES\plumbing.dm"
#include "code\__DEFINES\pool.dm"
#include "code\__DEFINES\power.dm"
#include "code\__DEFINES\preferences.dm"
@@ -93,6 +97,7 @@
#include "code\__DEFINES\reagents_specific_heat.dm"
#include "code\__DEFINES\research.dm"
#include "code\__DEFINES\robots.dm"
+#include "code\__DEFINES\rockpaperscissors.dm"
#include "code\__DEFINES\role_preferences.dm"
#include "code\__DEFINES\rust_g.dm"
#include "code\__DEFINES\say.dm"
@@ -138,12 +143,14 @@
#include "code\__DEFINES\mapping\maploader.dm"
#include "code\__DEFINES\material\worth.dm"
#include "code\__DEFINES\mobs\slowdowns.dm"
+#include "code\__DEFINES\research\anomalies.dm"
#include "code\__DEFINES\research\stock_parts.dm"
#include "code\__DEFINES\skills\defines.dm"
#include "code\__DEFINES\skills\helpers.dm"
#include "code\__DEFINES\storage\_storage.dm"
#include "code\__DEFINES\storage\volumetrics.dm"
#include "code\__HELPERS\_cit_helpers.dm"
+#include "code\__HELPERS\_extools_api.dm"
#include "code\__HELPERS\_lists.dm"
#include "code\__HELPERS\_logging.dm"
#include "code\__HELPERS\_string_lists.dm"
@@ -201,6 +208,7 @@
#include "code\_globalvars\lists\client.dm"
#include "code\_globalvars\lists\flavor_misc.dm"
#include "code\_globalvars\lists\keybindings.dm"
+#include "code\_globalvars\lists\loadout_categories.dm"
#include "code\_globalvars\lists\maintenance_loot.dm"
#include "code\_globalvars\lists\mapping.dm"
#include "code\_globalvars\lists\medals.dm"
@@ -281,6 +289,7 @@
#include "code\controllers\configuration\entries\general.dm"
#include "code\controllers\configuration\entries\plushies.dm"
#include "code\controllers\configuration\entries\policy.dm"
+#include "code\controllers\configuration\entries\resources.dm"
#include "code\controllers\subsystem\acid.dm"
#include "code\controllers\subsystem\adjacent_air.dm"
#include "code\controllers\subsystem\air.dm"
@@ -299,6 +308,7 @@
#include "code\controllers\subsystem\events.dm"
#include "code\controllers\subsystem\fail2topic.dm"
#include "code\controllers\subsystem\fire_burning.dm"
+#include "code\controllers\subsystem\fluid.dm"
#include "code\controllers\subsystem\garbage.dm"
#include "code\controllers\subsystem\holodeck.dm"
#include "code\controllers\subsystem\icon_smooth.dm"
@@ -323,7 +333,6 @@
#include "code\controllers\subsystem\parallax.dm"
#include "code\controllers\subsystem\pathfinder.dm"
#include "code\controllers\subsystem\persistence.dm"
-#include "code\controllers\subsystem\ping.dm"
#include "code\controllers\subsystem\profiler.dm"
#include "code\controllers\subsystem\radiation.dm"
#include "code\controllers\subsystem\radio.dm"
@@ -359,6 +368,7 @@
#include "code\controllers\subsystem\processing\status_effects.dm"
#include "code\controllers\subsystem\processing\weather.dm"
#include "code\controllers\subsystem\processing\wet_floors.dm"
+#include "code\datums\accents.dm"
#include "code\datums\action.dm"
#include "code\datums\ai_laws.dm"
#include "code\datums\armor.dm"
@@ -374,6 +384,7 @@
#include "code\datums\datumvars.dm"
#include "code\datums\dna.dm"
#include "code\datums\dog_fashion.dm"
+#include "code\datums\ductnet.dm"
#include "code\datums\emotes.dm"
#include "code\datums\ert.dm"
#include "code\datums\explosion.dm"
@@ -429,12 +440,16 @@
#include "code\datums\components\field_of_vision.dm"
#include "code\datums\components\footstep.dm"
#include "code\datums\components\fried.dm"
+#include "code\datums\components\gps.dm"
+#include "code\datums\components\honkspam.dm"
#include "code\datums\components\identification.dm"
#include "code\datums\components\igniter.dm"
#include "code\datums\components\infective.dm"
#include "code\datums\components\jousting.dm"
+#include "code\datums\components\killerqueen.dm"
#include "code\datums\components\knockback.dm"
#include "code\datums\components\knockoff.dm"
+#include "code\datums\components\label.dm"
#include "code\datums\components\lifesteal.dm"
#include "code\datums\components\lockon_aiming.dm"
#include "code\datums\components\magnetic_catch.dm"
@@ -485,6 +500,11 @@
#include "code\datums\components\fantasy\affix.dm"
#include "code\datums\components\fantasy\prefixes.dm"
#include "code\datums\components\fantasy\suffixes.dm"
+#include "code\datums\components\plumbing\_plumbing.dm"
+#include "code\datums\components\plumbing\chemical_acclimator.dm"
+#include "code\datums\components\plumbing\filter.dm"
+#include "code\datums\components\plumbing\reaction_chamber.dm"
+#include "code\datums\components\plumbing\splitter.dm"
#include "code\datums\components\storage\storage.dm"
#include "code\datums\components\storage\ui.dm"
#include "code\datums\components\storage\concrete\_concrete.dm"
@@ -554,6 +574,7 @@
#include "code\datums\elements\_element.dm"
#include "code\datums\elements\art.dm"
#include "code\datums\elements\beauty.dm"
+#include "code\datums\elements\bsa_blocker.dm"
#include "code\datums\elements\cleaning.dm"
#include "code\datums\elements\decal.dm"
#include "code\datums\elements\dusts_on_catatonia.dm"
@@ -629,6 +650,7 @@
#include "code\datums\skills\_skill.dm"
#include "code\datums\skills\_skill_holder.dm"
#include "code\datums\skills\_skill_modifier.dm"
+#include "code\datums\skills\blacksmithing.dm"
#include "code\datums\skills\engineering.dm"
#include "code\datums\skills\medical.dm"
#include "code\datums\skills\modifiers\job.dm"
@@ -671,7 +693,9 @@
#include "code\datums\wounds\_wounds.dm"
#include "code\datums\wounds\bones.dm"
#include "code\datums\wounds\burns.dm"
-#include "code\datums\wounds\cuts.dm"
+#include "code\datums\wounds\loss.dm"
+#include "code\datums\wounds\pierce.dm"
+#include "code\datums\wounds\slash.dm"
#include "code\game\alternate_appearance.dm"
#include "code\game\atoms.dm"
#include "code\game\atoms_movable.dm"
@@ -716,11 +740,11 @@
#include "code\game\gamemodes\devil\devil agent\devil_agent.dm"
#include "code\game\gamemodes\dynamic\dynamic.dm"
#include "code\game\gamemodes\dynamic\dynamic_rulesets.dm"
-#include "code\game\gamemodes\dynamic\dynamic_rulesets_events.dm"
#include "code\game\gamemodes\dynamic\dynamic_rulesets_latejoin.dm"
#include "code\game\gamemodes\dynamic\dynamic_rulesets_midround.dm"
#include "code\game\gamemodes\dynamic\dynamic_rulesets_roundstart.dm"
#include "code\game\gamemodes\dynamic\dynamic_storytellers.dm"
+#include "code\game\gamemodes\eldritch_cult\eldritch_cult.dm"
#include "code\game\gamemodes\extended\extended.dm"
#include "code\game\gamemodes\gangs\dominator.dm"
#include "code\game\gamemodes\gangs\dominator_countdown.dm"
@@ -1079,6 +1103,7 @@
#include "code\game\objects\items\devices\paicard.dm"
#include "code\game\objects\items\devices\pipe_painter.dm"
#include "code\game\objects\items\devices\polycircuit.dm"
+#include "code\game\objects\items\devices\portable_chem_mixer.dm"
#include "code\game\objects\items\devices\powersink.dm"
#include "code\game\objects\items\devices\pressureplates.dm"
#include "code\game\objects\items\devices\quantum_keycard.dm"
@@ -1274,6 +1299,7 @@
#include "code\game\objects\structures\crates_lockers\crates\secure.dm"
#include "code\game\objects\structures\crates_lockers\crates\wooden.dm"
#include "code\game\objects\structures\icemoon\cave_entrance.dm"
+#include "code\game\objects\structures\lavaland\geyser.dm"
#include "code\game\objects\structures\lavaland\necropolis_tendril.dm"
#include "code\game\objects\structures\signs\_signs.dm"
#include "code\game\objects\structures\signs\signs_departments.dm"
@@ -1290,6 +1316,7 @@
#include "code\game\turfs\open.dm"
#include "code\game\turfs\turf.dm"
#include "code\game\turfs\openspace\openspace.dm"
+#include "code\game\turfs\openspace\transparent.dm"
#include "code\game\turfs\simulated\chasm.dm"
#include "code\game\turfs\simulated\dirtystation.dm"
#include "code\game\turfs\simulated\floor.dm"
@@ -1385,6 +1412,7 @@
#include "code\modules\admin\view_variables\mark_datum.dm"
#include "code\modules\admin\view_variables\mass_edit_variables.dm"
#include "code\modules\admin\view_variables\modify_variables.dm"
+#include "code\modules\admin\view_variables\reference_tracking.dm"
#include "code\modules\admin\view_variables\topic.dm"
#include "code\modules\admin\view_variables\topic_basic.dm"
#include "code\modules\admin\view_variables\topic_list.dm"
@@ -1403,6 +1431,7 @@
#include "code\modules\antagonists\abductor\equipment\abduction_outfits.dm"
#include "code\modules\antagonists\abductor\equipment\abduction_surgery.dm"
#include "code\modules\antagonists\abductor\equipment\gland.dm"
+#include "code\modules\antagonists\abductor\equipment\orderable_gear.dm"
#include "code\modules\antagonists\abductor\equipment\glands\access.dm"
#include "code\modules\antagonists\abductor\equipment\glands\blood.dm"
#include "code\modules\antagonists\abductor\equipment\glands\chem.dm"
@@ -1520,6 +1549,7 @@
#include "code\modules\antagonists\clockcult\clock_effects\servant_blocker.dm"
#include "code\modules\antagonists\clockcult\clock_effects\spatial_gateway.dm"
#include "code\modules\antagonists\clockcult\clock_helpers\clock_powerdrain.dm"
+#include "code\modules\antagonists\clockcult\clock_helpers\clock_rites.dm"
#include "code\modules\antagonists\clockcult\clock_helpers\component_helpers.dm"
#include "code\modules\antagonists\clockcult\clock_helpers\fabrication_helpers.dm"
#include "code\modules\antagonists\clockcult\clock_helpers\hierophant_network.dm"
@@ -1527,6 +1557,7 @@
#include "code\modules\antagonists\clockcult\clock_helpers\ratvarian_language.dm"
#include "code\modules\antagonists\clockcult\clock_helpers\scripture_checks.dm"
#include "code\modules\antagonists\clockcult\clock_helpers\slab_abilities.dm"
+#include "code\modules\antagonists\clockcult\clock_items\clock_augments.dm"
#include "code\modules\antagonists\clockcult\clock_items\clock_components.dm"
#include "code\modules\antagonists\clockcult\clock_items\clockwork_armor.dm"
#include "code\modules\antagonists\clockcult\clock_items\clockwork_slab.dm"
@@ -1538,6 +1569,7 @@
#include "code\modules\antagonists\clockcult\clock_items\soul_vessel.dm"
#include "code\modules\antagonists\clockcult\clock_items\wraith_spectacles.dm"
#include "code\modules\antagonists\clockcult\clock_items\clock_weapons\_call_weapon.dm"
+#include "code\modules\antagonists\clockcult\clock_items\clock_weapons\brass_claw.dm"
#include "code\modules\antagonists\clockcult\clock_items\clock_weapons\ratvarian_shield.dm"
#include "code\modules\antagonists\clockcult\clock_items\clock_weapons\ratvarian_spear.dm"
#include "code\modules\antagonists\clockcult\clock_mobs\_eminence.dm"
@@ -1588,6 +1620,16 @@
#include "code\modules\antagonists\disease\disease_disease.dm"
#include "code\modules\antagonists\disease\disease_event.dm"
#include "code\modules\antagonists\disease\disease_mob.dm"
+#include "code\modules\antagonists\eldritch_cult\eldritch_antag.dm"
+#include "code\modules\antagonists\eldritch_cult\eldritch_book.dm"
+#include "code\modules\antagonists\eldritch_cult\eldritch_effects.dm"
+#include "code\modules\antagonists\eldritch_cult\eldritch_items.dm"
+#include "code\modules\antagonists\eldritch_cult\eldritch_knowledge.dm"
+#include "code\modules\antagonists\eldritch_cult\eldritch_magic.dm"
+#include "code\modules\antagonists\eldritch_cult\eldritch_monster_antag.dm"
+#include "code\modules\antagonists\eldritch_cult\knowledge\ash_lore.dm"
+#include "code\modules\antagonists\eldritch_cult\knowledge\flesh_lore.dm"
+#include "code\modules\antagonists\eldritch_cult\knowledge\rust_lore.dm"
#include "code\modules\antagonists\ert\ert.dm"
#include "code\modules\antagonists\fugitive\fugitive.dm"
#include "code\modules\antagonists\fugitive\fugitive_outfits.dm"
@@ -1673,11 +1715,12 @@
#include "code\modules\assembly\signaler.dm"
#include "code\modules\assembly\timer.dm"
#include "code\modules\assembly\voice.dm"
-#include "code\modules\asset_cache\asset_cache.dm"
#include "code\modules\asset_cache\asset_cache_client.dm"
#include "code\modules\asset_cache\asset_cache_item.dm"
#include "code\modules\asset_cache\asset_list.dm"
#include "code\modules\asset_cache\asset_list_items.dm"
+#include "code\modules\asset_cache\transports\asset_transport.dm"
+#include "code\modules\asset_cache\transports\webroot_transport.dm"
#include "code\modules\atmospherics\multiz.dm"
#include "code\modules\atmospherics\environmental\LINDA_fire.dm"
#include "code\modules\atmospherics\environmental\LINDA_system.dm"
@@ -1824,7 +1867,6 @@
#include "code\modules\client\client_colour.dm"
#include "code\modules\client\client_defines.dm"
#include "code\modules\client\client_procs.dm"
-#include "code\modules\client\darkmode.dm"
#include "code\modules\client\message.dm"
#include "code\modules\client\player_details.dm"
#include "code\modules\client\preferences.dm"
@@ -2072,7 +2114,6 @@
#include "code\modules\food_and_drinks\recipes\tablecraft\recipes_spaghetti.dm"
#include "code\modules\games\cas.dm"
#include "code\modules\games\unum.dm"
-#include "code\modules\goonchat\browserOutput.dm"
#include "code\modules\holiday\easter.dm"
#include "code\modules\holiday\holidays.dm"
#include "code\modules\holiday\halloween\bartholomew.dm"
@@ -2264,6 +2305,7 @@
#include "code\modules\language\swarmer.dm"
#include "code\modules\language\sylvan.dm"
#include "code\modules\language\vampiric.dm"
+#include "code\modules\language\voltaic.dm"
#include "code\modules\language\xenocommon.dm"
#include "code\modules\library\lib_codex_gigas.dm"
#include "code\modules\library\lib_items.dm"
@@ -2278,6 +2320,11 @@
#include "code\modules\lighting\lighting_setup.dm"
#include "code\modules\lighting\lighting_source.dm"
#include "code\modules\lighting\lighting_turf.dm"
+#include "code\modules\mafia\_defines.dm"
+#include "code\modules\mafia\controller.dm"
+#include "code\modules\mafia\map_pieces.dm"
+#include "code\modules\mafia\outfits.dm"
+#include "code\modules\mafia\roles.dm"
#include "code\modules\mapping\map_config.dm"
#include "code\modules\mapping\map_orientation_pattern.dm"
#include "code\modules\mapping\map_template.dm"
@@ -2493,6 +2540,7 @@
#include "code\modules\mob\living\carbon\human\species_types\corporate.dm"
#include "code\modules\mob\living\carbon\human\species_types\dullahan.dm"
#include "code\modules\mob\living\carbon\human\species_types\dwarves.dm"
+#include "code\modules\mob\living\carbon\human\species_types\ethereal.dm"
#include "code\modules\mob\living\carbon\human\species_types\felinid.dm"
#include "code\modules\mob\living\carbon\human\species_types\flypeople.dm"
#include "code\modules\mob\living\carbon\human\species_types\furrypeople.dm"
@@ -2574,6 +2622,7 @@
#include "code\modules\mob\living\simple_animal\constructs.dm"
#include "code\modules\mob\living\simple_animal\corpse.dm"
#include "code\modules\mob\living\simple_animal\damage_procs.dm"
+#include "code\modules\mob\living\simple_animal\eldritch_demons.dm"
#include "code\modules\mob\living\simple_animal\parrot.dm"
#include "code\modules\mob\living\simple_animal\pickle.dm"
#include "code\modules\mob\living\simple_animal\shade.dm"
@@ -2607,6 +2656,7 @@
#include "code\modules\mob\living\simple_animal\friendly\penguin.dm"
#include "code\modules\mob\living\simple_animal\friendly\pet.dm"
#include "code\modules\mob\living\simple_animal\friendly\plushie.dm"
+#include "code\modules\mob\living\simple_animal\friendly\possum.dm"
#include "code\modules\mob\living\simple_animal\friendly\sloth.dm"
#include "code\modules\mob\living\simple_animal\friendly\snake.dm"
#include "code\modules\mob\living\simple_animal\friendly\drone\_drone.dm"
@@ -2714,6 +2764,7 @@
#include "code\modules\mob\living\simple_animal\slime\slime_mobility.dm"
#include "code\modules\mob\living\simple_animal\slime\subtypes.dm"
#include "code\modules\modular_computers\laptop_vendor.dm"
+#include "code\modules\modular_computers\computers\_modular_computer_shared.dm"
#include "code\modules\modular_computers\computers\item\computer.dm"
#include "code\modules\modular_computers\computers\item\computer_components.dm"
#include "code\modules\modular_computers\computers\item\computer_damage.dm"
@@ -2734,13 +2785,17 @@
#include "code\modules\modular_computers\file_system\programs\airestorer.dm"
#include "code\modules\modular_computers\file_system\programs\alarm.dm"
#include "code\modules\modular_computers\file_system\programs\arcade.dm"
+#include "code\modules\modular_computers\file_system\programs\atmosscan.dm"
+#include "code\modules\modular_computers\file_system\programs\borg_monitor.dm"
#include "code\modules\modular_computers\file_system\programs\card.dm"
+#include "code\modules\modular_computers\file_system\programs\cargobounty.dm"
#include "code\modules\modular_computers\file_system\programs\configurator.dm"
+#include "code\modules\modular_computers\file_system\programs\crewmanifest.dm"
#include "code\modules\modular_computers\file_system\programs\file_browser.dm"
+#include "code\modules\modular_computers\file_system\programs\jobmanagement.dm"
#include "code\modules\modular_computers\file_system\programs\ntdownloader.dm"
#include "code\modules\modular_computers\file_system\programs\ntmonitor.dm"
#include "code\modules\modular_computers\file_system\programs\ntnrc_client.dm"
-#include "code\modules\modular_computers\file_system\programs\nttransfer.dm"
#include "code\modules\modular_computers\file_system\programs\powermonitor.dm"
#include "code\modules\modular_computers\file_system\programs\radar.dm"
#include "code\modules\modular_computers\file_system\programs\robocontrol.dm"
@@ -2758,6 +2813,7 @@
#include "code\modules\modular_computers\hardware\portable_disk.dm"
#include "code\modules\modular_computers\hardware\printer.dm"
#include "code\modules\modular_computers\hardware\recharger.dm"
+#include "code\modules\modular_computers\hardware\sensor_package.dm"
#include "code\modules\modular_computers\NTNet\NTNRC\conversation.dm"
#include "code\modules\movespeed\_movespeed_modifier.dm"
#include "code\modules\movespeed\modifiers\components.dm"
@@ -2802,6 +2858,7 @@
#include "code\modules\NTNet\network.dm"
#include "code\modules\NTNet\relays.dm"
#include "code\modules\NTNet\services\_service.dm"
+#include "code\modules\paperwork\carbonpaper.dm"
#include "code\modules\paperwork\clipboard.dm"
#include "code\modules\paperwork\contract.dm"
#include "code\modules\paperwork\filingcabinet.dm"
@@ -2815,6 +2872,7 @@
#include "code\modules\paperwork\pen.dm"
#include "code\modules\paperwork\photocopier.dm"
#include "code\modules\paperwork\stamps.dm"
+#include "code\modules\paperwork\ticketmachine.dm"
#include "code\modules\photography\_pictures.dm"
#include "code\modules\photography\camera\camera.dm"
#include "code\modules\photography\camera\camera_image_capturing.dm"
@@ -2824,6 +2882,21 @@
#include "code\modules\photography\photos\album.dm"
#include "code\modules\photography\photos\frame.dm"
#include "code\modules\photography\photos\photo.dm"
+#include "code\modules\plumbing\ducts.dm"
+#include "code\modules\plumbing\plumbers\_plumb_machinery.dm"
+#include "code\modules\plumbing\plumbers\acclimator.dm"
+#include "code\modules\plumbing\plumbers\autohydro.dm"
+#include "code\modules\plumbing\plumbers\bottler.dm"
+#include "code\modules\plumbing\plumbers\destroyer.dm"
+#include "code\modules\plumbing\plumbers\fermenter.dm"
+#include "code\modules\plumbing\plumbers\filter.dm"
+#include "code\modules\plumbing\plumbers\grinder_chemical.dm"
+#include "code\modules\plumbing\plumbers\medipenrefill.dm"
+#include "code\modules\plumbing\plumbers\pill_press.dm"
+#include "code\modules\plumbing\plumbers\pumps.dm"
+#include "code\modules\plumbing\plumbers\reaction_chamber.dm"
+#include "code\modules\plumbing\plumbers\splitters.dm"
+#include "code\modules\plumbing\plumbers\synthesizer.dm"
#include "code\modules\pool\pool_controller.dm"
#include "code\modules\pool\pool_drain.dm"
#include "code\modules\pool\pool_effects.dm"
@@ -2956,6 +3029,7 @@
#include "code\modules\projectiles\guns\energy\pulse.dm"
#include "code\modules\projectiles\guns\energy\special.dm"
#include "code\modules\projectiles\guns\energy\stun.dm"
+#include "code\modules\projectiles\guns\magic\spell_book.dm"
#include "code\modules\projectiles\guns\magic\staff.dm"
#include "code\modules\projectiles\guns\magic\wand.dm"
#include "code\modules\projectiles\guns\misc\beam_rifle.dm"
@@ -3073,6 +3147,9 @@
#include "code\modules\research\research_disk.dm"
#include "code\modules\research\server.dm"
#include "code\modules\research\stock_parts.dm"
+#include "code\modules\research\anomaly\anomaly_core.dm"
+#include "code\modules\research\anomaly\explosive_compressor.dm"
+#include "code\modules\research\anomaly\raw_anomaly.dm"
#include "code\modules\research\designs\AI_module_designs.dm"
#include "code\modules\research\designs\autobotter_designs.dm"
#include "code\modules\research\designs\autoylathe_designs.dm"
@@ -3213,6 +3290,7 @@
#include "code\modules\ruins\spaceruin_code\whiteshipruin_box.dm"
#include "code\modules\security_levels\keycard_authentication.dm"
#include "code\modules\security_levels\security_levels.dm"
+#include "code\modules\shielding\helpers.dm"
#include "code\modules\shuttle\arrivals.dm"
#include "code\modules\shuttle\assault_pod.dm"
#include "code\modules\shuttle\computer.dm"
@@ -3239,12 +3317,16 @@
#include "code\modules\shuttle\shuttle_creation\shuttle_creator_eye.dm"
#include "code\modules\shuttle\shuttle_creation\shuttle_creator_overlay.dm"
#include "code\modules\shuttle\shuttle_creation\shuttle_upgrades.dm"
+#include "code\modules\smithing\anvil.dm"
+#include "code\modules\smithing\finished_items.dm"
+#include "code\modules\smithing\furnace.dm"
+#include "code\modules\smithing\smithed_items.dm"
#include "code\modules\spells\spell.dm"
#include "code\modules\spells\spell_types\aimed.dm"
#include "code\modules\spells\spell_types\area_teleport.dm"
-#include "code\modules\spells\spell_types\barnyard.dm"
#include "code\modules\spells\spell_types\bloodcrawl.dm"
#include "code\modules\spells\spell_types\charge.dm"
+#include "code\modules\spells\spell_types\cone_spells.dm"
#include "code\modules\spells\spell_types\conjure.dm"
#include "code\modules\spells\spell_types\construct_spells.dm"
#include "code\modules\spells\spell_types\curse.dm"
@@ -3263,7 +3345,6 @@
#include "code\modules\spells\spell_types\lichdom.dm"
#include "code\modules\spells\spell_types\lightning.dm"
#include "code\modules\spells\spell_types\mime.dm"
-#include "code\modules\spells\spell_types\mind_transfer.dm"
#include "code\modules\spells\spell_types\projectile.dm"
#include "code\modules\spells\spell_types\rightandwrong.dm"
#include "code\modules\spells\spell_types\rod_form.dm"
@@ -3280,6 +3361,10 @@
#include "code\modules\spells\spell_types\turf_teleport.dm"
#include "code\modules\spells\spell_types\voice_of_god.dm"
#include "code\modules\spells\spell_types\wizard.dm"
+#include "code\modules\spells\spell_types\pointed\barnyard.dm"
+#include "code\modules\spells\spell_types\pointed\blind.dm"
+#include "code\modules\spells\spell_types\pointed\mind_transfer.dm"
+#include "code\modules\spells\spell_types\pointed\pointed.dm"
#include "code\modules\station_goals\bsa.dm"
#include "code\modules\station_goals\dna_vault.dm"
#include "code\modules\station_goals\shield.dm"
@@ -3310,6 +3395,7 @@
#include "code\modules\surgery\plastic_surgery.dm"
#include "code\modules\surgery\prosthetic_replacement.dm"
#include "code\modules\surgery\remove_embedded_object.dm"
+#include "code\modules\surgery\repair_puncture.dm"
#include "code\modules\surgery\surgery.dm"
#include "code\modules\surgery\surgery_step.dm"
#include "code\modules\surgery\tools.dm"
@@ -3354,15 +3440,15 @@
#include "code\modules\tgs\includes.dm"
#include "code\modules\tgui\external.dm"
#include "code\modules\tgui\states.dm"
-#include "code\modules\tgui\subsystem.dm"
#include "code\modules\tgui\tgui.dm"
+#include "code\modules\tgui\tgui_window.dm"
#include "code\modules\tgui\states\admin.dm"
#include "code\modules\tgui\states\always.dm"
#include "code\modules\tgui\states\conscious.dm"
#include "code\modules\tgui\states\contained.dm"
+#include "code\modules\tgui\states\debug.dm"
#include "code\modules\tgui\states\deep_inventory.dm"
#include "code\modules\tgui\states\default.dm"
-#include "code\modules\tgui\states\default_contained.dm"
#include "code\modules\tgui\states\hands.dm"
#include "code\modules\tgui\states\human_adjacent.dm"
#include "code\modules\tgui\states\inventory.dm"
@@ -3373,6 +3459,11 @@
#include "code\modules\tgui\states\physical.dm"
#include "code\modules\tgui\states\self.dm"
#include "code\modules\tgui\states\zlevel.dm"
+#include "code\modules\tgui_panel\audio.dm"
+#include "code\modules\tgui_panel\external.dm"
+#include "code\modules\tgui_panel\telemetry.dm"
+#include "code\modules\tgui_panel\tgui_panel.dm"
+#include "code\modules\tgui_panel\to_chat.dm"
#include "code\modules\tooltip\tooltip.dm"
#include "code\modules\unit_tests\_unit_tests.dm"
#include "code\modules\uplink\uplink_devices.dm"
diff --git a/tgui/.eslintrc.yml b/tgui/.eslintrc.yml
index 67e74085c7..e3c3144e5b 100644
--- a/tgui/.eslintrc.yml
+++ b/tgui/.eslintrc.yml
@@ -8,6 +8,8 @@ env:
es6: true
browser: true
node: true
+globals:
+ Byond: readonly
plugins:
- react
settings:
@@ -112,7 +114,7 @@ rules:
## Require return statements to either always or never specify values
# consistent-return: error
## Enforce consistent brace style for all control statements
- curly: [error, all]
+ curly: [error, multi-line]
## Require default cases in switch statements
# default-case: error
## Enforce default parameters to be last
@@ -372,6 +374,7 @@ rules:
ignorePattern: '^(import\s.+\sfrom\s|.*require\()',
ignoreUrls: true,
ignoreRegExpLiterals: true,
+ ignoreStrings: true,
}]
## Enforce a maximum number of lines per file
# max-lines: error
@@ -388,7 +391,7 @@ rules:
## Enforce a particular style for multiline comments
# multiline-comment-style: error
## Enforce newlines between operands of ternary expressions
- multiline-ternary: [error, always-multiline]
+ # multiline-ternary: [error, always-multiline]
## Require constructor names to begin with a capital letter
# new-cap: error
## Enforce or disallow parentheses when invoking a constructor with no
diff --git a/tgui/.gitattributes b/tgui/.gitattributes
index 0016cc3bf6..d9cdc20fbd 100644
--- a/tgui/.gitattributes
+++ b/tgui/.gitattributes
@@ -2,9 +2,19 @@
## Enforce text mode and LF line breaks
*.js text eol=lf
+*.jsx text eol=lf
+*.ts text eol=lf
+*.tsx text eol=lf
*.css text eol=lf
+*.scss text eol=lf
*.html text eol=lf
*.json text eol=lf
+*.yml text eol=lf
+*.md text eol=lf
+*.bat text eol=lf
+yarn.lock text eol=lf
+bin/tgui text eol=lf
## Treat bundles as binary and ignore them during conflicts
*.bundle.* binary merge=tgui-merge-bundle
+*.chunk.* binary merge=tgui-merge-bundle
diff --git a/tgui/README.md b/tgui/README.md
index 5ddeb18fdd..9eab0196de 100644
--- a/tgui/README.md
+++ b/tgui/README.md
@@ -67,8 +67,9 @@ Run one of the following:
game as you code it. Very useful, highly recommended.
- In order to use it, you should start the game server first, connect to it
and wait until the world has been properly loaded and you are no longer
- in the lobby. Start tgui dev server. You'll know that it's hooked correctly
- if data gets dumped to the log when tgui windows are opened.
+ in the lobby. Start tgui dev server, and once it has finished building,
+ press F5 on any tgui window. You'll know that it's hooked correctly if
+ you see a green bug icon in titlebar and data gets dumped to the console.
- `bin/tgui --dev --reload` - reload byond cache once.
- `bin/tgui --dev --debug` - run server with debug logging enabled.
- `bin/tgui --dev --no-hot` - disable hot module replacement (helps when
@@ -134,11 +135,11 @@ logs and time spent on rendering. Use this information to optimize your
code, and try to keep re-renders below 16ms.
**Kitchen Sink.**
-Press `Ctrl+Alt+=` to open the KitchenSink interface. This interface is a
+Press `F12` to open the KitchenSink interface. This interface is a
playground to test various tgui components.
**Layout Debugger.**
-Press `Ctrl+Alt+-` to toggle the *layout debugger*. It will show outlines of
+Press `F11` to toggle the *layout debugger*. It will show outlines of
all tgui elements, which makes it easy to understand how everything comes
together, and can reveal certain layout bugs which are not normally visible.
@@ -180,8 +181,11 @@ See: [Component Reference](docs/component-reference.md).
## License
-All code is licensed with the parent license of *tgstation*, **AGPL-3.0**.
+Source code is covered by /tg/station's parent license - **AGPL-3.0**
+(see the main [README](../README.md)), unless otherwise indicated.
-See the main [README](../README.md) for more details.
+Some files are annotated with a copyright header, which explicitly states
+the copyright holder and license of the file. Most of the core tgui
+source code is available under the **MIT** license.
The Authors retain all copyright to their respective work here submitted.
diff --git a/tgui/bin/tgui b/tgui/bin/tgui
index eb1f200b31..5627b40413 100755
--- a/tgui/bin/tgui
+++ b/tgui/bin/tgui
@@ -52,6 +52,7 @@ task-dev-server() {
task-eslint() {
cd "${base_dir}"
eslint ./packages "${@}"
+ echo "tgui: eslint check passed"
}
## Mr. Proper
@@ -66,7 +67,7 @@ task-clean() {
task-validate-build() {
cd "${base_dir}"
local diff
- diff="$(git diff packages/tgui/public/tgui.bundle.*)"
+ diff="$(git diff packages/tgui/public/*)"
if [[ -n ${diff} ]]; then
echo "Error: our build differs from the build committed into git."
echo "Please rebuild tgui."
@@ -153,6 +154,13 @@ if [[ ${1} == '--lint-harder' ]]; then
exit 0
fi
+if [[ ${1} == '--fix' ]]; then
+ shift 1
+ task-install
+ task-eslint --fix "${@}"
+ exit 0
+fi
+
## Analyze the bundle
if [[ ${1} == '--analyze' ]]; then
task-install
diff --git a/tgui/docs/component-reference.md b/tgui/docs/component-reference.md
index a2a0066a70..23909c639f 100644
--- a/tgui/docs/component-reference.md
+++ b/tgui/docs/component-reference.md
@@ -30,6 +30,8 @@ Make sure to add new items to this list if you document new components.
- [`Icon`](#icon)
- [`Input`](#input)
- [`Knob`](#knob)
+ - [`LabeledControls`](#labeledcontrols)
+ - [`LabeledControls.Item`](#labeledcontrolsitem)
- [`LabeledList`](#labeledlist)
- [`LabeledList.Item`](#labeledlistitem)
- [`LabeledList.Divider`](#labeledlistdivider)
@@ -225,7 +227,7 @@ be truncated with an ellipsis. Be careful however, because this prop breaks
the baseline alignment.
- `title: string` - A native browser tooltip, which appears when hovering
over the button.
-- `content/children: any` - Content to render inside the button.
+- `children: any` - Content to render inside the button.
- `onClick: function` - Called when element is clicked.
### `Button.Checkbox`
@@ -239,7 +241,7 @@ A ghetto checkbox, made entirely using existing Button API.
### `Button.Confirm`
-A button with a an extra confirmation step, using native button component.
+A button with an extra confirmation step, using native button component.
**Props:**
@@ -273,11 +275,11 @@ interface.
Example (button):
-```
+```jsx
@@ -285,11 +287,10 @@ Example (button):
Example (map):
-```
+```jsx
```
@@ -388,7 +389,9 @@ to the left, and certain elements to the right:
-
+
```
@@ -584,6 +587,24 @@ the input, or successfully enter a number.
- `onDrag: (e, value) => void` - An event, which fires about every 500ms
when you drag the input up and down, on release and on manual editing.
+### `LabeledControls`
+
+LabeledControls is a horizontal grid, that is designed to hold various
+controls, like [Knobs](#knob) or small [Buttons](#button). Every item in
+this grid is labeled at the bottom.
+
+**Props:**
+
+- See inherited props: [Box](#box)
+- `children: LabeledControls.Item` - Items to render.
+
+### `LabeledControls.Item`
+
+**Props:**
+
+- See inherited props: [Box](#box)
+- `label: string` - Item label.
+
### `LabeledList`
LabeledList is a continuous, vertical list of text and other content, where
@@ -606,7 +627,9 @@ to perform some sort of action), there is a way to do that:
+
)}>
Content
@@ -624,7 +647,7 @@ to perform some sort of action), there is a way to do that:
- `label: string` - Item label.
- `color: string` - Sets the color of the text.
- `buttons: any` - Buttons to render aside the content.
-- `content/children: any` - Content of this labeled item.
+- `children: any` - Content of this labeled item.
### `LabeledList.Divider`
@@ -729,7 +752,7 @@ percentage and how filled the bar is.
- `ranges: { color: [from, to] }` - Applies a `color` to the progress bar
based on whether the value lands in the range between `from` and `to`.
- `color: string` - Color of the progress bar.
-- `content/children: any` - Content to render inside the progress bar.
+- `children: any` - Content to render inside the progress bar.
### `Section`
@@ -754,7 +777,9 @@ If you want to have a button on the right side of an section title
+
)}>
Here you can order supply crates.
@@ -765,7 +790,10 @@ If you want to have a button on the right side of an section title
- `level: number` - Section level in hierarchy. Default is 1, higher number
means deeper level of nesting. Must be an integer number.
- `buttons: any` - Buttons to render aside the section title.
-- `content/children: any` - Content of this section.
+- `fill: boolean` - If true, fills all available vertical space.
+- `fitted: boolean` - If true, removes all section padding.
+- `scrollable: boolean` - Shows or hides the scrollbar.
+- `children: any` - Content of this section.
### `Slider`
@@ -934,7 +962,7 @@ Usage:
**Props:**
- `position: string` - Tooltip position.
-- `content/children: string` - Content of the tooltip. Must be a plain string.
+- `content: string` - Content of the tooltip. Must be a plain string.
Fragments or other elements are **not** supported.
## `tgui/layouts`
@@ -959,9 +987,11 @@ Example:
**Props:**
+- See inherited props: [Box](#box)
- `className: string` - Applies a CSS class to the element.
- `theme: string` - A name of the theme.
- For a list of themes, see `packages/tgui/styles/themes`.
+- `title: string` - Window title.
- `resizable: boolean` - Controls resizability of the window.
- `children: any` - Child elements, which are rendered directly inside the
window. If you use a [Dimmer](#dimmer) or [Modal](#modal) in your UI,
@@ -975,6 +1005,8 @@ Can be scrollable.
**Props:**
+- See inherited props: [Box](#box)
- `className: string` - Applies a CSS class to the element.
+- `fitted: boolean` - If true, removes all padding.
- `scrollable: boolean` - Shows or hides the scrollbar.
- `children: any` - Main content of your window.
diff --git a/tgui/docs/migration-to-v4-from-v3.md b/tgui/docs/migration-to-v4-from-v3.md
new file mode 100644
index 0000000000..6f047a6cc7
--- /dev/null
+++ b/tgui/docs/migration-to-v4-from-v3.md
@@ -0,0 +1,91 @@
+# tgui Migration Guide to v4 from v3
+
+## The Easy Part
+
+- Copy and replace the following files in bulk:
+ - `code/__DEFINES/tgui.dm`
+ - `code/controllers/subsystem/tgui.dm`
+ - `code/modules/tgui/**`
+ - `tgui/**`
+ - Except: `tgui/packages/tgui/interfaces`
+ - Manually resolve conflicts for files that were touched outside the
+ `interfaces` folder.
+- Copy the updated `log_tgui` proc from:
+ - `code/__HELPERS/_logging.dm`
+
+If you have a dual nano/tgui setup, then make sure to rename all ui procs
+on `/datum`, such as `ui_interact` to `tgui_interact`, to avoid namespace
+clashing. Usual stuff.
+
+## Update `ui_interact` proc signatures
+
+First of all, tgui states need to move from `ui_interact` to `ui_state`.
+
+One way of doing it, is to just cherry pick those procs from upstream.
+
+If you want to search and replace manually, search for `state = GLOB`, and
+extract those things into `ui_state` procs like so:
+
+```dm
+.../ui_state(mob/user)
+ return GLOB.default_state
+```
+
+Then reduce `ui_interact` until you finish with something like this:
+
+```dm
+.../ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "FooBar", "Foo Bar UI", 600, 400)
+ ui.open()
+```
+
+## Update asset delivery code
+
+Remove all asset code that injects stylesheets by modifying tgui's `basehtml`.
+You no longer need to do that.
+
+Find all occurences of `asset.send(user)` in `ui_interact`, and refactor those
+snippets to the following proc:
+
+```dm
+.../ui_assets(mob/user)
+ return list(
+ get_asset_datum(/datum/asset/simple/foobar),
+ )
+```
+
+## Check `ui_act` for new bugs
+
+Code behind `ui_act` was recently refactored to use JSON-encoded payloads
+instead of just strings. Since it can now carry numbers and other complex
+types, you should check that the code is type agnostic and does not break
+due to an assumption that every parameter is a string.
+
+One of such offenders is the personal crafting interface, where it needlessly
+compares parameters to `""` and `"0"`. You can now replace this code with
+simple assignments, because an empty category will now be properly `null`
+instead of an empty string.
+
+## Backend data changes
+
+Interfaces that relied on `config.window.id`, must now use
+`window.__windowId__`, which is a global constant unique for the page
+the script is running on (so you can be sure it never changes).
+
+In case of `ByondUi`, this parameter can be completely omitted, because
+parent will always default to the current window id.
+
+Affected interfaces:
+
+- `CameraConsole`
+- Any interface that uses the `ByondUi` component
+
+---
+
+That's all folks!
+
+There is a lot of stuff that was refactored under the hood, but normal UI
+stuff wouldn't and shouldn't touch it, so you should be good with just
+going through the checklist above.
diff --git a/tgui/docs/tutorial-and-examples.md b/tgui/docs/tutorial-and-examples.md
index 683f819a8c..bf220f575b 100644
--- a/tgui/docs/tutorial-and-examples.md
+++ b/tgui/docs/tutorial-and-examples.md
@@ -4,11 +4,11 @@
Basic tgui backend code consists of the following vars and procs:
-```
-ui_interact(mob/user, ui_key, datum/tgui/ui, force_open,
- datum/tgui/master_ui, datum/ui_state/state)
+```dm
+ui_interact(mob/user, datum/tgui/ui)
ui_data(mob/user)
ui_act(action, params)
+ui_state()
```
- `src_object` - The atom, which UI corresponds to in the game world.
@@ -19,9 +19,9 @@ or set up a new instance of UI by calling the `SStgui` subsystem.
has into an associative list, which will then be sent to UI as a JSON string.
- `ui_act` - This proc receives user actions and reacts to them by changing
the state of the game.
-- `ui_state` (set in `ui_interact`) - This var dictates under what conditions
-a UI may be interacted with. This may be the standard checks that check if
-you are in range and conscious, or more.
+- `ui_state` - This proc dictates under what conditions a UI may be interacted
+with. This may be the standard checks that check if you are in range and
+conscious, or more.
Once backend is complete, you create an new interface component on the
frontend, which will receive this JSON data and render it on screen.
@@ -37,10 +37,10 @@ powerful interactions for embedded objects or remote access.
Let's start with a very basic hello world.
```dm
-/obj/machinery/my_machine/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, datum/tgui/master_ui = null, datum/ui_state/state = default_state)
- ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+/obj/machinery/my_machine/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
- ui = new(user, src, ui_key, "my_machine", name, 300, 300, master_ui, state)
+ ui = new(user, src, "MyMachine")
ui.open()
```
@@ -48,9 +48,7 @@ This is the proc that defines our interface. There's a bit going on here, so
let's break it down. First, we override the ui_interact proc on our object. This
will be called by `interact` for you, which is in turn called by `attack_hand`
(or `attack_self` for items). `ui_interact` is also called to update a UI (hence
-the `try_update_ui`), so we accept an existing UI to update. The `state` is a
-default argument so that a caller can overload it with named arguments
-(`ui_interact(state = overloaded_state)`) if needed.
+the `try_update_ui`), so we accept an existing UI to update.
Inside the `if(!ui)` block (which means we are creating a new UI), we choose our
template, title, and size; we can also set various options like `style` (for
@@ -142,7 +140,7 @@ export const SampleInterface = (props, context) => {
@@ -294,10 +292,10 @@ here's what you need (note that you'll probably be forced to clean your shit up
upon code review):
```dm
-/obj/copypasta/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, datum/tgui/master_ui = null, datum/ui_state/state = default_state) // Remember to use the appropriate state.
- ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+/obj/copypasta/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
- ui = new(user, src, ui_key, "copypasta", name, 300, 300, master_ui, state)
+ ui = new(user, src, "copypasta")
ui.open()
/obj/copypasta/ui_data(mob/user)
@@ -345,7 +343,7 @@ export const SampleInterface = (props, context) => {
diff --git a/tgui/package.json b/tgui/package.json
index bfceeec49e..d0ef805c47 100644
--- a/tgui/package.json
+++ b/tgui/package.json
@@ -1,7 +1,7 @@
{
"private": true,
"name": "tgui",
- "version": "3.0.0",
+ "version": "4.1.0",
"workspaces": [
"packages/*"
],
@@ -13,7 +13,7 @@
},
"dependencies": {
"babel-eslint": "^10.0.3",
- "eslint": "^6.7.2",
+ "eslint": "^7.4.0",
"eslint-plugin-react": "^7.17.0"
}
}
diff --git a/tgui/packages/common/collections.js b/tgui/packages/common/collections.js
index 1e5c978942..b542967d47 100644
--- a/tgui/packages/common/collections.js
+++ b/tgui/packages/common/collections.js
@@ -1,3 +1,9 @@
+/**
+ * @file
+ * @copyright 2020 Aleksej Komarov
+ * @license MIT
+ */
+
/**
* Converts a given collection to an array.
*
diff --git a/tgui/packages/common/color.js b/tgui/packages/common/color.js
new file mode 100644
index 0000000000..510579bc26
--- /dev/null
+++ b/tgui/packages/common/color.js
@@ -0,0 +1,62 @@
+/**
+ * @file
+ * @copyright 2020 Aleksej Komarov
+ * @license MIT
+ */
+
+const EPSILON = 0.0001;
+
+export class Color {
+ constructor(r = 0, g = 0, b = 0, a = 1) {
+ this.r = r;
+ this.g = g;
+ this.b = b;
+ this.a = a;
+ }
+
+ toString() {
+ return `rgba(${this.r | 0}, ${this.g | 0}, ${this.b | 0}, ${this.a | 0})`;
+ }
+}
+
+/**
+ * Creates a color from the CSS hex color notation.
+ */
+Color.fromHex = hex => (
+ new Color(
+ parseInt(hex.substr(1, 2), 16),
+ parseInt(hex.substr(3, 2), 16),
+ parseInt(hex.substr(5, 2), 16))
+);
+
+/**
+ * Linear interpolation of two colors.
+ */
+Color.lerp = (c1, c2, n) => (
+ new Color(
+ (c2.r - c1.r) * n + c1.r,
+ (c2.g - c1.g) * n + c1.g,
+ (c2.b - c1.b) * n + c1.b,
+ (c2.a - c1.a) * n + c1.a)
+);
+
+/**
+ * Loops up the color in the provided list of colors
+ * with linear interpolation.
+ */
+Color.lookup = (value, colors = []) => {
+ const len = colors.length;
+ if (len < 2) {
+ throw new Error('Needs at least two colors!');
+ }
+ const scaled = value * (len - 1);
+ if (value < EPSILON) {
+ return colors[0];
+ }
+ if (value >= 1 - EPSILON) {
+ return colors[len - 1];
+ }
+ const ratio = scaled % 1;
+ const index = scaled | 0;
+ return Color.lerp(colors[index], colors[index + 1], ratio);
+};
diff --git a/tgui/packages/common/events.js b/tgui/packages/common/events.js
new file mode 100644
index 0000000000..6d590a3445
--- /dev/null
+++ b/tgui/packages/common/events.js
@@ -0,0 +1,42 @@
+/**
+ * @file
+ * @copyright 2020 Aleksej Komarov
+ * @license MIT
+ */
+
+export class EventEmitter {
+ constructor() {
+ this.listeners = {};
+ }
+
+ on(name, listener) {
+ this.listeners[name] = this.listeners[name] || [];
+ this.listeners[name].push(listener);
+ }
+
+ off(name, listener) {
+ const listeners = this.listeners[name];
+ if (!listeners) {
+ throw new Error(`There is no listeners for "${name}"`);
+ }
+ this.listeners[name] = listeners
+ .filter(existingListener => {
+ return existingListener !== listener;
+ });
+ }
+
+ emit(name, ...params) {
+ const listeners = this.listeners[name];
+ if (!listeners) {
+ return;
+ }
+ for (let i = 0, len = listeners.length; i < len; i += 1) {
+ const listener = listeners[i];
+ listener(...params);
+ }
+ }
+
+ clear() {
+ this.listeners = {};
+ }
+}
diff --git a/tgui/packages/common/fp.js b/tgui/packages/common/fp.js
index 4be45877e2..7aa00a00f3 100644
--- a/tgui/packages/common/fp.js
+++ b/tgui/packages/common/fp.js
@@ -1,3 +1,9 @@
+/**
+ * @file
+ * @copyright 2020 Aleksej Komarov
+ * @license MIT
+ */
+
/**
* Creates a function that returns the result of invoking the given
* functions, where each successive invocation is supplied the return
diff --git a/tgui/packages/common/keycodes.js b/tgui/packages/common/keycodes.js
new file mode 100644
index 0000000000..8f18b154b5
--- /dev/null
+++ b/tgui/packages/common/keycodes.js
@@ -0,0 +1,86 @@
+/**
+ * All possible browser keycodes, in one file.
+ *
+ * @file
+ * @copyright 2020 Aleksej Komarov
+ * @license MIT
+ */
+
+export const KEY_BACKSPACE = 8;
+export const KEY_TAB = 9;
+export const KEY_ENTER = 13;
+export const KEY_SHIFT = 16;
+export const KEY_CTRL = 17;
+export const KEY_ALT = 18;
+export const KEY_PAUSE = 19;
+export const KEY_CAPSLOCK = 20;
+export const KEY_ESCAPE = 27;
+export const KEY_SPACE = 32;
+export const KEY_PAGEUP = 33;
+export const KEY_PAGEDOWN = 34;
+export const KEY_END = 35;
+export const KEY_HOME = 36;
+export const KEY_LEFT = 37;
+export const KEY_UP = 38;
+export const KEY_RIGHT = 39;
+export const KEY_DOWN = 40;
+export const KEY_INSERT = 45;
+export const KEY_DELETE = 46;
+export const KEY_0 = 48;
+export const KEY_1 = 49;
+export const KEY_2 = 50;
+export const KEY_3 = 51;
+export const KEY_4 = 52;
+export const KEY_5 = 53;
+export const KEY_6 = 54;
+export const KEY_7 = 55;
+export const KEY_8 = 56;
+export const KEY_9 = 57;
+export const KEY_A = 65;
+export const KEY_B = 66;
+export const KEY_C = 67;
+export const KEY_D = 68;
+export const KEY_E = 69;
+export const KEY_F = 70;
+export const KEY_G = 71;
+export const KEY_H = 72;
+export const KEY_I = 73;
+export const KEY_J = 74;
+export const KEY_K = 75;
+export const KEY_L = 76;
+export const KEY_M = 77;
+export const KEY_N = 78;
+export const KEY_O = 79;
+export const KEY_P = 80;
+export const KEY_Q = 81;
+export const KEY_R = 82;
+export const KEY_S = 83;
+export const KEY_T = 84;
+export const KEY_U = 85;
+export const KEY_V = 86;
+export const KEY_W = 87;
+export const KEY_X = 88;
+export const KEY_Y = 89;
+export const KEY_Z = 90;
+export const KEY_F1 = 112;
+export const KEY_F2 = 113;
+export const KEY_F3 = 114;
+export const KEY_F4 = 115;
+export const KEY_F5 = 116;
+export const KEY_F6 = 117;
+export const KEY_F7 = 118;
+export const KEY_F8 = 119;
+export const KEY_F9 = 120;
+export const KEY_F10 = 121;
+export const KEY_F11 = 122;
+export const KEY_F12 = 123;
+export const KEY_SEMICOLON = 186;
+export const KEY_EQUAL = 187;
+export const KEY_COMMA = 188;
+export const KEY_MINUS = 189;
+export const KEY_PERIOD = 190;
+export const KEY_SLASH = 191;
+export const KEY_LEFT_BRACKET = 219;
+export const KEY_BACKSLASH = 220;
+export const KEY_RIGHT_BRACKET = 221;
+export const KEY_QUOTE = 222;
diff --git a/tgui/packages/common/logging.js b/tgui/packages/common/logging.js
index 4ae1855fda..0dc222ae3d 100644
--- a/tgui/packages/common/logging.js
+++ b/tgui/packages/common/logging.js
@@ -1,3 +1,9 @@
+/**
+ * @file
+ * @copyright 2020 Aleksej Komarov
+ * @license MIT
+ */
+
const inception = Date.now();
// Runtime detection
diff --git a/tgui/packages/common/math.js b/tgui/packages/common/math.js
index cc7a309563..c3013b5a32 100644
--- a/tgui/packages/common/math.js
+++ b/tgui/packages/common/math.js
@@ -1,3 +1,9 @@
+/**
+ * @file
+ * @copyright 2020 Aleksej Komarov
+ * @license MIT
+ */
+
/**
* Limits a number to the range between 'min' and 'max'.
*/
diff --git a/tgui/packages/common/package.json b/tgui/packages/common/package.json
index fbd255abab..22560d92f9 100644
--- a/tgui/packages/common/package.json
+++ b/tgui/packages/common/package.json
@@ -1,6 +1,6 @@
{
"private": true,
"name": "common",
- "version": "3.0.0",
+ "version": "4.1.0",
"type": "module"
}
diff --git a/tgui/packages/common/perf.js b/tgui/packages/common/perf.js
new file mode 100644
index 0000000000..8414971f93
--- /dev/null
+++ b/tgui/packages/common/perf.js
@@ -0,0 +1,60 @@
+/**
+ * Ghetto performance measurement tools.
+ *
+ * Uses NODE_ENV to remove itself from production builds.
+ *
+ * @file
+ * @copyright 2020 Aleksej Komarov
+ * @license MIT
+ */
+
+const FPS = 60;
+const FRAME_DURATION = 1000 / FPS;
+
+// True if Performance API is supported
+const supportsPerf = !!window.performance?.now;
+// High precision markers
+let hpMarkersByName = {};
+// Low precision markers
+let lpMarkersByName = {};
+
+/**
+ * Marks a certain spot in the code for later measurements.
+ */
+const mark = (name, timestamp) => {
+ if (process.env.NODE_ENV !== 'production') {
+ if (supportsPerf && !timestamp) {
+ hpMarkersByName[name] = performance.now();
+ }
+ lpMarkersByName[name] = timestamp || Date.now();
+ }
+};
+
+/**
+ * Calculates and returns the difference between two markers as a string.
+ *
+ * Use logger.log() to print the measurement.
+ */
+const measure = (markerNameA, markerNameB) => {
+ if (process.env.NODE_ENV !== 'production') {
+ let markerA = hpMarkersByName[markerNameA];
+ let markerB = hpMarkersByName[markerNameB];
+ if (!markerA || !markerB) {
+ markerA = lpMarkersByName[markerNameA];
+ markerB = lpMarkersByName[markerNameB];
+ }
+ const duration = Math.abs(markerB - markerA);
+ return formatDuration(duration);
+ }
+};
+
+const formatDuration = duration => {
+ const durationInFrames = duration / FRAME_DURATION;
+ return duration.toFixed(duration < 10 ? 1 : 0) + 'ms '
+ + '(' + durationInFrames.toFixed(2) + ' frames)';
+};
+
+export const perf = {
+ mark,
+ measure,
+};
diff --git a/tgui/packages/common/react.js b/tgui/packages/common/react.js
index 0828bb8864..c0b24563f1 100644
--- a/tgui/packages/common/react.js
+++ b/tgui/packages/common/react.js
@@ -1,3 +1,9 @@
+/**
+ * @file
+ * @copyright 2020 Aleksej Komarov
+ * @license MIT
+ */
+
/**
* Helper for conditionally adding/removing classes in React
*
@@ -58,10 +64,10 @@ export const pureComponentHooks = {
};
/**
- * A helper to determine whether to render an item.
+ * A helper to determine whether the object is renderable by React.
*/
-export const isFalsy = value => {
- return value === undefined
- || value === null
- || value === false;
+export const canRender = value => {
+ return value !== undefined
+ && value !== null
+ && typeof value !== 'boolean';
};
diff --git a/tgui/packages/common/redux.js b/tgui/packages/common/redux.js
index 257a9eebf5..12aacadb5f 100644
--- a/tgui/packages/common/redux.js
+++ b/tgui/packages/common/redux.js
@@ -1,3 +1,10 @@
+/**
+ * @file
+ * @copyright 2020 Aleksej Komarov
+ * @license MIT
+ */
+
+import { Component } from 'inferno';
import { compose } from './fp';
/**
@@ -20,7 +27,9 @@ export const createStore = (reducer, enhancer) => {
const dispatch = action => {
currentState = reducer(currentState, action);
- listeners.forEach(fn => fn());
+ for (let i = 0; i < listeners.length; i++) {
+ listeners[i]();
+ }
};
// This creates the initial store by causing each reducer to be called
@@ -63,3 +72,83 @@ export const applyMiddleware = (...middlewares) => {
};
};
};
+
+/**
+ * Combines reducers by running them in their own object namespaces as
+ * defined in reducersObj paramter.
+ *
+ * Main difference from redux/combineReducers is that it preserves keys
+ * in the state that are not present in the reducers object. This function
+ * is also more flexible than the redux counterpart.
+ */
+export const combineReducers = reducersObj => {
+ const keys = Object.keys(reducersObj);
+ let hasChanged = false;
+ return (prevState = {}, action) => {
+ const nextState = { ...prevState };
+ for (let key of keys) {
+ const reducer = reducersObj[key];
+ const prevDomainState = prevState[key];
+ const nextDomainState = reducer(prevDomainState, action);
+ if (prevDomainState !== nextDomainState) {
+ hasChanged = true;
+ nextState[key] = nextDomainState;
+ }
+ }
+ return hasChanged
+ ? nextState
+ : prevState;
+ };
+};
+
+/**
+ * A utility function to create an action creator for the given action
+ * type string. The action creator accepts a single argument, which will
+ * be included in the action object as a field called payload. The action
+ * creator function will also have its toString() overriden so that it
+ * returns the action type, allowing it to be used in reducer logic that
+ * is looking for that action type.
+ *
+ * @param type The action type to use for created actions.
+ * @param prepare (optional) a method that takes any number of arguments
+ * and returns { payload } or { payload, meta }. If this is given, the
+ * resulting action creator will pass it's arguments to this method to
+ * calculate payload & meta.
+ *
+ * @public
+ */
+export const createAction = (type, prepare) => {
+ const actionCreator = (...args) => {
+ if (!prepare) {
+ return { type, payload: args[0] };
+ }
+ const prepared = prepare(...args);
+ if (!prepared) {
+ throw new Error('prepare function did not return an object');
+ }
+ const action = { type };
+ if ('payload' in prepared) {
+ action.payload = prepared.payload;
+ }
+ if ('meta' in prepared) {
+ action.meta = prepared.meta;
+ }
+ return action;
+ };
+ actionCreator.toString = () => '' + type;
+ actionCreator.type = type;
+ actionCreator.match = action => action.type === type;
+ return actionCreator;
+};
+
+
+// Implementation specific
+// --------------------------------------------------------
+
+export const useDispatch = context => {
+ return context.store.dispatch;
+};
+
+export const useSelector = (context, selector) => {
+ return selector(context.store.getState());
+};
diff --git a/tgui/packages/common/storage.js b/tgui/packages/common/storage.js
new file mode 100644
index 0000000000..115a7ff0ff
--- /dev/null
+++ b/tgui/packages/common/storage.js
@@ -0,0 +1,196 @@
+/**
+ * Browser-agnostic abstraction of key-value web storage.
+ *
+ * @file
+ * @copyright 2020 Aleksej Komarov
+ * @license MIT
+ */
+
+export const IMPL_MEMORY = 0;
+export const IMPL_LOCAL_STORAGE = 1;
+export const IMPL_INDEXED_DB = 2;
+
+const INDEXED_DB_VERSION = 1;
+const INDEXED_DB_NAME = 'tgui';
+const INDEXED_DB_STORE_NAME = 'storage-v1';
+
+const READ_ONLY = 'readonly';
+const READ_WRITE = 'readwrite';
+
+const testGeneric = testFn => () => {
+ try {
+ return Boolean(testFn());
+ }
+ catch {
+ return false;
+ }
+};
+
+// Localstorage can sometimes throw an error, even if DOM storage is not
+// disabled in IE11 settings.
+// See: https://superuser.com/questions/1080011
+const testLocalStorage = testGeneric(() => (
+ window.localStorage && window.localStorage.getItem
+));
+
+const testIndexedDb = testGeneric(() => (
+ (window.indexedDB || window.msIndexedDB)
+ && (window.IDBTransaction || window.msIDBTransaction)
+));
+
+class MemoryBackend {
+ constructor() {
+ this.impl = IMPL_MEMORY;
+ this.store = {};
+ }
+
+ get(key) {
+ return this.store[key];
+ }
+
+ set(key, value) {
+ this.store[key] = value;
+ }
+
+ remove(key) {
+ this.store[key] = undefined;
+ }
+
+ clear() {
+ this.store = {};
+ }
+}
+
+class LocalStorageBackend {
+ constructor() {
+ this.impl = IMPL_LOCAL_STORAGE;
+ this.store = {};
+ }
+
+ get(key) {
+ const value = localStorage.getItem(key);
+ if (typeof value === 'string') {
+ return JSON.parse(value);
+ }
+ }
+
+ set(key, value) {
+ localStorage.setItem(key, JSON.stringify(value));
+ }
+
+ remove(key) {
+ localStorage.removeItem(key);
+ }
+
+ clear() {
+ localStorage.clear();
+ }
+}
+
+class IndexedDbBackend {
+ constructor() {
+ this.impl = IMPL_INDEXED_DB;
+ /** @type {Promise} */
+ this.dbPromise = new Promise((resolve, reject) => {
+ const indexedDB = window.indexedDB || window.msIndexedDB;
+ const req = indexedDB.open(INDEXED_DB_NAME, INDEXED_DB_VERSION);
+ req.onupgradeneeded = () => {
+ try {
+ req.result.createObjectStore(INDEXED_DB_STORE_NAME);
+ }
+ catch (err) {
+ reject(new Error('Failed to upgrade IDB: ' + req.error));
+ }
+ };
+ req.onsuccess = () => resolve(req.result);
+ req.onerror = () => {
+ reject(new Error('Failed to open IDB: ' + req.error));
+ };
+ });
+ }
+
+ getStore(mode) {
+ return this.dbPromise.then(db => db
+ .transaction(INDEXED_DB_STORE_NAME, mode)
+ .objectStore(INDEXED_DB_STORE_NAME));
+ }
+
+ async get(key) {
+ const store = await this.getStore(READ_ONLY);
+ return new Promise((resolve, reject) => {
+ const req = store.get(key);
+ req.onsuccess = () => resolve(req.result);
+ req.onerror = () => reject(req.error);
+ });
+ }
+
+ async set(key, value) {
+ // The reason we don't _save_ null is because IE 10 does
+ // not support saving the `null` type in IndexedDB. How
+ // ironic, given the bug below!
+ // See: https://github.com/mozilla/localForage/issues/161
+ if (value === null) {
+ value = undefined;
+ }
+ // NOTE: We deliberately make this operation transactionless
+ const store = await this.getStore(READ_WRITE);
+ store.put(value, key);
+ }
+
+ async remove(key) {
+ // NOTE: We deliberately make this operation transactionless
+ const store = await this.getStore(READ_WRITE);
+ store.delete(key);
+ }
+
+ async clear() {
+ // NOTE: We deliberately make this operation transactionless
+ const store = await this.getStore(READ_WRITE);
+ store.clear();
+ }
+}
+
+/**
+ * Web Storage Proxy object, which selects the best backend available
+ * depending on the environment.
+ */
+class StorageProxy {
+ constructor() {
+ this.backendPromise = (async () => {
+ if (testIndexedDb()) {
+ try {
+ const backend = new IndexedDbBackend();
+ await backend.dbPromise;
+ return backend;
+ }
+ catch {}
+ }
+ if (testLocalStorage()) {
+ return new LocalStorageBackend();
+ }
+ return new MemoryBackend();
+ })();
+ }
+
+ async get(key) {
+ const backend = await this.backendPromise;
+ return backend.get(key);
+ }
+
+ async set(key, value) {
+ const backend = await this.backendPromise;
+ return backend.set(key, value);
+ }
+
+ async remove(key) {
+ const backend = await this.backendPromise;
+ return backend.remove(key);
+ }
+
+ async clear() {
+ const backend = await this.backendPromise;
+ return backend.clear();
+ }
+}
+
+export const storage = new StorageProxy();
diff --git a/tgui/packages/common/string.babel-plugin.cjs b/tgui/packages/common/string.babel-plugin.cjs
index ba25e3f91d..68295aefcf 100644
--- a/tgui/packages/common/string.babel-plugin.cjs
+++ b/tgui/packages/common/string.babel-plugin.cjs
@@ -1,5 +1,7 @@
/**
- * @file
+ * This plugin saves overall about 10KB on the final bundle size, so it's
+ * sort of worth it.
+ *
* We are using a .cjs extension because:
*
* 1. Webpack CLI only supports CommonJS modules;
@@ -9,8 +11,9 @@
* We need to copy-paste the whole "multiline" function because we can't
* synchronously import an ES module from a CommonJS module.
*
- * This plugin saves overall about 10KB on the final bundle size, so it's
- * sort of worth it.
+ * @file
+ * @copyright 2020 Aleksej Komarov
+ * @license MIT
*/
/**
diff --git a/tgui/packages/common/string.js b/tgui/packages/common/string.js
index d05dbfb6fc..16a0921a25 100644
--- a/tgui/packages/common/string.js
+++ b/tgui/packages/common/string.js
@@ -1,3 +1,9 @@
+/**
+ * @file
+ * @copyright 2020 Aleksej Komarov
+ * @license MIT
+ */
+
/**
* Removes excess whitespace and indentation from the string.
*/
diff --git a/tgui/packages/common/timer.js b/tgui/packages/common/timer.js
index e3feb69ca9..1177071b9c 100644
--- a/tgui/packages/common/timer.js
+++ b/tgui/packages/common/timer.js
@@ -1,3 +1,9 @@
+/**
+ * @file
+ * @copyright 2020 Aleksej Komarov
+ * @license MIT
+ */
+
/**
* Returns a function, that, as long as it continues to be invoked, will
* not be triggered. The function will be called after it stops being
@@ -21,3 +27,12 @@ export const debounce = (fn, time, immediate = false) => {
}
};
};
+
+/**
+ * Suspends an asynchronous function for N milliseconds.
+ *
+ * @param {number} time
+ */
+export const sleep = time => (
+ new Promise(resolve => setTimeout(resolve, time))
+);
diff --git a/tgui/packages/common/uuid.js b/tgui/packages/common/uuid.js
new file mode 100644
index 0000000000..7721af6494
--- /dev/null
+++ b/tgui/packages/common/uuid.js
@@ -0,0 +1,19 @@
+/**
+ * @file
+ * @copyright 2020 Aleksej Komarov
+ * @license MIT
+ */
+
+/**
+ * Creates a UUID v4 string
+ *
+ * @return {string}
+ */
+export const createUuid = () => {
+ let d = new Date().getTime();
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
+ const r = (d + Math.random() * 16) % 16 | 0;
+ d = Math.floor(d / 16);
+ return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
+ });
+};
diff --git a/tgui/packages/common/vector.js b/tgui/packages/common/vector.js
index fa98597896..c3ac350a4e 100644
--- a/tgui/packages/common/vector.js
+++ b/tgui/packages/common/vector.js
@@ -1,14 +1,14 @@
-import { map, reduce, zipWith } from './collections';
-
/**
- * Creates a vector, with as many dimensions are there are arguments.
+ * N-dimensional vector manipulation functions.
+ *
+ * Vectors are plain number arrays, i.e. [x, y, z].
+ *
+ * @file
+ * @copyright 2020 Aleksej Komarov
+ * @license MIT
*/
-export const vecCreate = (...components) => {
- if (Array.isArray(components[0])) {
- return [...components[0]];
- }
- return components;
-};
+
+import { map, reduce, zipWith } from './collections';
const ADD = (a, b) => a + b;
const SUB = (a, b) => a - b;
diff --git a/tgui/packages/tgui-dev-server/dreamseeker.js b/tgui/packages/tgui-dev-server/dreamseeker.js
new file mode 100644
index 0000000000..c36fe55f28
--- /dev/null
+++ b/tgui/packages/tgui-dev-server/dreamseeker.js
@@ -0,0 +1,87 @@
+/**
+ * @file
+ * @copyright 2020 Aleksej Komarov
+ * @license MIT
+ */
+
+import axios from 'axios';
+import { exec } from 'child_process';
+import { createLogger } from 'common/logging.js';
+import { promisify } from 'util';
+
+const logger = createLogger('dreamseeker');
+
+const instanceByPid = new Map();
+
+export class DreamSeeker {
+ constructor(pid, addr) {
+ this.pid = pid;
+ this.addr = addr;
+ this.client = axios.create({
+ baseURL: `http://${addr}/`,
+ });
+ }
+
+ topic(params = {}) {
+ const query = Object.keys(params)
+ .map(key => encodeURIComponent(key)
+ + '=' + encodeURIComponent(params[key]))
+ .join('&');
+ return this.client.get('/dummy?' + query);
+ }
+}
+
+/**
+ * @param {number[]} pids
+ * @returns {DreamSeeker[]}
+ */
+DreamSeeker.getInstancesByPids = async pids => {
+ if (process.platform !== 'win32') {
+ return [];
+ }
+ const instances = [];
+ const pidsToResolve = [];
+ for (let pid of pids) {
+ const instance = instanceByPid.get(pid);
+ if (instance) {
+ instances.push(instance);
+ }
+ else {
+ pidsToResolve.push(pid);
+ }
+ }
+ if (pidsToResolve.length > 0) {
+ try {
+ const command = 'netstat -a -n -o';
+ const { stdout } = await promisify(exec)(command);
+ // Line format:
+ // proto addr mask mode pid
+ const entries = stdout
+ .split('\r\n')
+ .filter(line => line.includes('LISTENING'))
+ .map(line => {
+ const words = line.match(/\S+/g);
+ return {
+ addr: words[1],
+ pid: parseInt(words[4], 10),
+ };
+ })
+ .filter(entry => pidsToResolve.includes(entry.pid));
+ const len = entries.length;
+ logger.log('found', len, plural('instance', len));
+ for (let entry of entries) {
+ const { pid, addr } = entry;
+ const instance = new DreamSeeker(pid, addr);
+ instances.push(instance);
+ instanceByPid.set(pid, instance);
+ }
+ }
+ catch (err) {
+ logger.error(err);
+ return [];
+ }
+ }
+ return instances;
+};
+
+const plural = (word, n) => n !== 1 ? word + 's' : word;
diff --git a/tgui/packages/tgui-dev-server/index.js b/tgui/packages/tgui-dev-server/index.js
index 1e7683080c..43acd87474 100644
--- a/tgui/packages/tgui-dev-server/index.js
+++ b/tgui/packages/tgui-dev-server/index.js
@@ -1,13 +1,22 @@
+/**
+ * @file
+ * @copyright 2020 Aleksej Komarov
+ * @license MIT
+ */
+
import { setupWebpack, getWebpackConfig } from './webpack.js';
import { reloadByondCache } from './reloader.js';
const noHot = process.argv.includes('--no-hot');
+const noTmp = process.argv.includes('--no-tmp');
const reloadOnce = process.argv.includes('--reload');
const setupServer = async () => {
const config = await getWebpackConfig({
mode: 'development',
hot: !noHot,
+ devServer: true,
+ useTmpFolder: !noTmp,
});
// Reload cache once
if (reloadOnce) {
diff --git a/tgui/packages/tgui-dev-server/link/client.js b/tgui/packages/tgui-dev-server/link/client.js
index 774b7ed6db..5a4d2f6112 100644
--- a/tgui/packages/tgui-dev-server/link/client.js
+++ b/tgui/packages/tgui-dev-server/link/client.js
@@ -1,3 +1,9 @@
+/**
+ * @file
+ * @copyright 2020 Aleksej Komarov
+ * @license MIT
+ */
+
let socket;
const queue = [];
const subscribers = [];
@@ -31,7 +37,7 @@ if (process.env.NODE_ENV !== 'production') {
window.onunload = () => socket && socket.close();
}
-const subscribe = fn => subscribers.push(fn);
+export const subscribe = fn => subscribers.push(fn);
/**
* A json serializer which handles circular references and other junk.
@@ -62,7 +68,10 @@ const serializeObject = obj => {
}
refs.push(value);
// Error object
- if (value instanceof Error) {
+ const isError = value instanceof Error || (
+ value.code && value.message && value.message.includes('Error')
+ );
+ if (isError) {
return {
__error__: true,
string: String(value),
@@ -82,7 +91,7 @@ const serializeObject = obj => {
return json;
};
-const sendRawMessage = msg => {
+export const sendMessage = msg => {
if (process.env.NODE_ENV !== 'production') {
const json = serializeObject(msg);
// Send message using WebSocket
@@ -92,8 +101,8 @@ const sendRawMessage = msg => {
socket.send(json);
}
else {
- // Keep only 10 latest messages in the queue
- if (queue.length > 10) {
+ // Keep only 100 latest messages in the queue
+ if (queue.length > 100) {
queue.shift();
}
queue.push(json);
@@ -103,8 +112,8 @@ const sendRawMessage = msg => {
else {
const DEV_SERVER_IP = process.env.DEV_SERVER_IP || '127.0.0.1';
const req = new XMLHttpRequest();
- req.open('POST', `http://${DEV_SERVER_IP}:3001`);
- req.timeout = 500;
+ req.open('POST', `http://${DEV_SERVER_IP}:3001`, true);
+ req.timeout = 250;
req.send(json);
}
}
@@ -113,7 +122,7 @@ const sendRawMessage = msg => {
export const sendLogEntry = (level, ns, ...args) => {
if (process.env.NODE_ENV !== 'production') {
try {
- sendRawMessage({
+ sendMessage({
type: 'log',
payload: {
level,
diff --git a/tgui/packages/tgui-dev-server/link/retrace.js b/tgui/packages/tgui-dev-server/link/retrace.js
index 74c87e6a55..4bcb8e40c6 100644
--- a/tgui/packages/tgui-dev-server/link/retrace.js
+++ b/tgui/packages/tgui-dev-server/link/retrace.js
@@ -1,3 +1,9 @@
+/**
+ * @file
+ * @copyright 2020 Aleksej Komarov
+ * @license MIT
+ */
+
import { createLogger } from 'common/logging.js';
import fs from 'fs';
import { basename } from 'path';
@@ -33,6 +39,10 @@ export const loadSourceMaps = async bundleDir => {
};
export const retrace = stack => {
+ if (typeof stack !== 'string') {
+ logger.log('ERROR: Stack is not a string!', stack);
+ return stack;
+ }
const header = stack.split(/\n\s.*at/)[0];
const mappedStack = StackTraceParser.parse(stack)
.map(frame => {
diff --git a/tgui/packages/tgui-dev-server/link/server.js b/tgui/packages/tgui-dev-server/link/server.js
index 84f3700048..2586d77779 100644
--- a/tgui/packages/tgui-dev-server/link/server.js
+++ b/tgui/packages/tgui-dev-server/link/server.js
@@ -1,3 +1,9 @@
+/**
+ * @file
+ * @copyright 2020 Aleksej Komarov
+ * @license MIT
+ */
+
import { createLogger, directLog } from 'common/logging.js';
import http from 'http';
import { inspect } from 'util';
@@ -10,37 +16,108 @@ const DEBUG = process.argv.includes('--debug');
export { loadSourceMaps };
-export const setupLink = () => {
- logger.log('setting up');
- const wss = setupWebSocketLink();
- setupHttpLink();
- return {
- wss,
- };
-};
+export const setupLink = () => new LinkServer();
-export const broadcastMessage = (link, msg) => {
- const { wss } = link;
- const clients = [...wss.clients];
- logger.log(`broadcasting ${msg.type} to ${clients.length} clients`);
- for (let client of clients) {
- const json = JSON.stringify(msg);
- client.send(json);
+class LinkServer {
+ constructor() {
+ logger.log('setting up');
+ this.wss = null;
+ this.setupWebSocketLink();
+ this.setupHttpLink();
}
-};
+
+ // WebSocket-based client link
+ setupWebSocketLink() {
+ const port = 3000;
+ this.wss = new WebSocket.Server({ port });
+ this.wss.on('connection', ws => {
+ logger.log('client connected');
+ ws.on('message', json => {
+ const msg = deserializeObject(json);
+ this.handleLinkMessage(ws, msg);
+ });
+ ws.on('close', () => {
+ logger.log('client disconnected');
+ });
+ });
+ logger.log(`listening on port ${port} (WebSocket)`);
+ }
+
+ // One way HTTP-based client link for IE8
+ setupHttpLink() {
+ const port = 3001;
+ this.httpServer = http.createServer((req, res) => {
+ if (req.method === 'POST') {
+ let body = '';
+ req.on('data', chunk => {
+ body += chunk.toString();
+ });
+ req.on('end', () => {
+ const msg = deserializeObject(body);
+ this.handleLinkMessage(null, msg);
+ res.end();
+ });
+ return;
+ }
+ res.write('Hello');
+ res.end();
+ });
+ this.httpServer.listen(port);
+ logger.log(`listening on port ${port} (HTTP)`);
+ }
+
+ handleLinkMessage(ws, msg) {
+ const { type, payload } = msg;
+ if (type === 'log') {
+ const { level, ns, args } = payload;
+ // Skip debug messages
+ if (level <= 0 && !DEBUG) {
+ return;
+ }
+ directLog(ns, ...args.map(arg => {
+ if (typeof arg === 'object') {
+ return inspect(arg, {
+ depth: Infinity,
+ colors: true,
+ compact: 8,
+ });
+ }
+ return arg;
+ }));
+ return;
+ }
+ if (type === 'relay') {
+ for (let client of this.wss.clients) {
+ if (client === ws) {
+ continue;
+ }
+ this.sendMessage(client, msg);
+ }
+ return;
+ }
+ logger.log('unhandled message', msg);
+ }
+
+ sendMessage(ws, msg) {
+ ws.send(JSON.stringify(msg));
+ }
+
+ broadcastMessage(msg) {
+ const clients = [...this.wss.clients];
+ if (clients.length === 0) {
+ return;
+ }
+ logger.log(`broadcasting ${msg.type} to ${clients.length} clients`);
+ for (let client of clients) {
+ const json = JSON.stringify(msg);
+ client.send(json);
+ }
+ }
+}
const deserializeObject = str => {
return JSON.parse(str, (key, value) => {
if (typeof value === 'object' && value !== null) {
- if (value.__error__) {
- if (!value.stack) {
- return value.string;
- }
- return retrace(value.stack);
- }
- if (value.__number__) {
- return parseFloat(value.__number__);
- }
if (value.__undefined__) {
// NOTE: You should not rely on deserialized object's undefined,
// this is purely for inspection purposes.
@@ -48,80 +125,17 @@ const deserializeObject = str => {
[inspect.custom]: () => undefined,
};
}
+ if (value.__number__) {
+ return parseFloat(value.__number__);
+ }
+ if (value.__error__) {
+ if (!value.stack) {
+ return value.string;
+ }
+ return retrace(value.stack);
+ }
return value;
}
return value;
});
};
-
-const handleLinkMessage = msg => {
- const { type, payload } = msg;
-
- if (type === 'log') {
- const { level, ns, args } = payload;
- // Skip debug messages
- if (level <= 0 && !DEBUG) {
- return;
- }
- directLog(ns, ...args.map(arg => {
- if (typeof arg === 'object') {
- return inspect(arg, {
- depth: Infinity,
- colors: true,
- compact: 8,
- });
- }
- return arg;
- }));
- return;
- }
-
- logger.log('unhandled message', msg);
-};
-
-// WebSocket-based client link
-const setupWebSocketLink = () => {
- const port = 3000;
- const wss = new WebSocket.Server({ port });
-
- wss.on('connection', ws => {
- logger.log('client connected');
-
- ws.on('message', json => {
- const msg = deserializeObject(json);
- handleLinkMessage(msg);
- });
-
- ws.on('close', () => {
- logger.log('client disconnected');
- });
- });
-
- logger.log(`listening on port ${port} (WebSocket)`);
- return wss;
-};
-
-// One way HTTP-based client link for IE8
-const setupHttpLink = () => {
- const port = 3001;
-
- const server = http.createServer((req, res) => {
- if (req.method === 'POST') {
- let body = '';
- req.on('data', chunk => {
- body += chunk.toString();
- });
- req.on('end', () => {
- const msg = deserializeObject(body);
- handleLinkMessage(msg);
- res.end();
- });
- return;
- }
- res.write('Hello');
- res.end();
- });
-
- server.listen(port);
- logger.log(`listening on port ${port} (HTTP)`);
-};
diff --git a/tgui/packages/tgui-dev-server/package.json b/tgui/packages/tgui-dev-server/package.json
index e397fe3e0b..3e50a97f0e 100644
--- a/tgui/packages/tgui-dev-server/package.json
+++ b/tgui/packages/tgui-dev-server/package.json
@@ -1,9 +1,10 @@
{
"private": true,
"name": "tgui-dev-server",
- "version": "3.0.0",
+ "version": "4.1.0",
"type": "module",
"dependencies": {
+ "axios": "^0.19.2",
"glob": "^7.1.4",
"source-map": "^0.7.3",
"stacktrace-parser": "^0.1.7",
diff --git a/tgui/packages/tgui-dev-server/reloader.js b/tgui/packages/tgui-dev-server/reloader.js
index 394e55afdb..1271e67d2a 100644
--- a/tgui/packages/tgui-dev-server/reloader.js
+++ b/tgui/packages/tgui-dev-server/reloader.js
@@ -1,9 +1,17 @@
+/**
+ * @file
+ * @copyright 2020 Aleksej Komarov
+ * @license MIT
+ */
+
import { createLogger } from 'common/logging.js';
import fs from 'fs';
import os from 'os';
import { basename } from 'path';
import { promisify } from 'util';
import { resolveGlob, resolvePath } from './util.js';
+import { regQuery } from './winreg.js';
+import { DreamSeeker } from './dreamseeker.js';
const logger = createLogger('reloader');
@@ -36,13 +44,34 @@ export const findCacheRoot = async () => {
const paths = await resolveGlob(pattern);
if (paths.length > 0) {
cacheRoot = paths[0];
- logger.log(`found cache at '${cacheRoot}'`);
+ onCacheRootFound(cacheRoot);
+ return cacheRoot;
+ }
+ }
+ // Query the Windows Registry
+ if (process.platform === 'win32') {
+ logger.log('querying windows registry');
+ let userpath = await regQuery(
+ 'HKCU\\Software\\Dantom\\BYOND',
+ 'userpath');
+ if (userpath) {
+ cacheRoot = userpath
+ .replace(/\\$/, '')
+ .replace(/\\/g, '/')
+ + '/cache';
+ onCacheRootFound(cacheRoot);
return cacheRoot;
}
}
logger.log('found no cache directories');
};
+const onCacheRootFound = cacheRoot => {
+ logger.log(`found cache at '${cacheRoot}'`);
+ // Plant dummy
+ fs.closeSync(fs.openSync(cacheRoot + '/dummy', 'w'));
+};
+
export const reloadByondCache = async bundleDir => {
const cacheRoot = await findCacheRoot();
if (!cacheRoot) {
@@ -54,10 +83,16 @@ export const reloadByondCache = async bundleDir => {
logger.log('found no tmp folder in cache');
return;
}
- const assets = await resolveGlob(bundleDir, './*.+(bundle|hot-update).*');
+ // Get dreamseeker instances
+ const pids = cacheDirs.map(cacheDir => (
+ parseInt(cacheDir.split('/cache/tmp').pop(), 10)
+ ));
+ const dssPromise = DreamSeeker.getInstancesByPids(pids);
+ // Copy assets
+ const assets = await resolveGlob(bundleDir, './*.+(bundle|chunk|hot-update).*');
for (let cacheDir of cacheDirs) {
// Clear garbage
- const garbage = await resolveGlob(cacheDir, './*.+(bundle|hot-update).*');
+ const garbage = await resolveGlob(cacheDir, './*.+(bundle|chunk|hot-update).*');
for (let file of garbage) {
await promisify(fs.unlink)(file);
}
@@ -68,4 +103,15 @@ export const reloadByondCache = async bundleDir => {
}
logger.log(`copied ${assets.length} files to '${cacheDir}'`);
}
+ // Notify dreamseeker
+ const dss = await dssPromise;
+ if (dss.length > 0) {
+ logger.log(`notifying dreamseeker`);
+ for (let dreamseeker of dss) {
+ dreamseeker.topic({
+ tgui: 1,
+ type: 'cacheReloaded',
+ });
+ }
+ }
};
diff --git a/tgui/packages/tgui-dev-server/util.js b/tgui/packages/tgui-dev-server/util.js
index db34626721..50c0baad5a 100644
--- a/tgui/packages/tgui-dev-server/util.js
+++ b/tgui/packages/tgui-dev-server/util.js
@@ -1,3 +1,9 @@
+/**
+ * @file
+ * @copyright 2020 Aleksej Komarov
+ * @license MIT
+ */
+
import glob from 'glob';
import { resolve as resolvePath } from 'path';
import fs from 'fs';
diff --git a/tgui/packages/tgui-dev-server/webpack.js b/tgui/packages/tgui-dev-server/webpack.js
index 778469a15b..7a1f600253 100644
--- a/tgui/packages/tgui-dev-server/webpack.js
+++ b/tgui/packages/tgui-dev-server/webpack.js
@@ -1,9 +1,15 @@
+/**
+ * @file
+ * @copyright 2020 Aleksej Komarov
+ * @license MIT
+ */
+
import { createLogger } from 'common/logging.js';
import fs from 'fs';
import { createRequire } from 'module';
import { promisify } from 'util';
import webpack from 'webpack';
-import { broadcastMessage, loadSourceMaps, setupLink } from './link/server.js';
+import { loadSourceMaps, setupLink } from './link/server.js';
import { reloadByondCache } from './reloader.js';
import { resolveGlob } from './util.js';
@@ -38,7 +44,7 @@ export const setupWebpack = async config => {
// Reload cache
await reloadByondCache(bundleDir);
// Notify all clients that update has happened
- broadcastMessage(link, {
+ link.broadcastMessage({
type: 'hotUpdate',
});
});
@@ -49,6 +55,9 @@ export const setupWebpack = async config => {
logger.error('compilation error', err);
return;
}
- logger.log(stats.toString(config.devServer.stats));
+ stats
+ .toString(config.devServer.stats)
+ .split('\n')
+ .forEach(line => logger.log(line));
});
};
diff --git a/tgui/packages/tgui-dev-server/winreg.js b/tgui/packages/tgui-dev-server/winreg.js
new file mode 100644
index 0000000000..974135e76d
--- /dev/null
+++ b/tgui/packages/tgui-dev-server/winreg.js
@@ -0,0 +1,47 @@
+/**
+ * Tools for dealing with Windows Registry bullshit.
+ *
+ * @file
+ * @copyright 2020 Aleksej Komarov
+ * @license MIT
+ */
+
+import { exec } from 'child_process';
+import { createLogger } from 'common/logging.js';
+import { promisify } from 'util';
+
+const logger = createLogger('winreg');
+
+export const regQuery = async (path, key) => {
+ if (process.platform !== 'win32') {
+ return null;
+ }
+ try {
+ const command = `reg query "${path}" /v ${key}`;
+ const { stdout } = await promisify(exec)(command);
+ const keyPattern = ` ${key} `;
+ const indexOfKey = stdout.indexOf(keyPattern);
+ if (indexOfKey === -1) {
+ logger.error('could not find the registry key');
+ return null;
+ }
+ const indexOfEol = stdout.indexOf('\r\n', indexOfKey);
+ if (indexOfEol === -1) {
+ logger.error('could not find the end of the line');
+ return null;
+ }
+ const indexOfValue = stdout.indexOf(
+ ' ',
+ indexOfKey + keyPattern.length);
+ if (indexOfValue === -1) {
+ logger.error('could not find the start of the key value');
+ return null;
+ }
+ const value = stdout.substring(indexOfValue + 4, indexOfEol);
+ return value;
+ }
+ catch (err) {
+ logger.error(err);
+ return null;
+ }
+};
diff --git a/tgui/packages/tgui-panel/Notifications.js b/tgui/packages/tgui-panel/Notifications.js
new file mode 100644
index 0000000000..a64ddd8e30
--- /dev/null
+++ b/tgui/packages/tgui-panel/Notifications.js
@@ -0,0 +1,41 @@
+/**
+ * @file
+ * @copyright 2020 Aleksej Komarov
+ * @license MIT
+ */
+
+import { Flex } from 'tgui/components';
+
+export const Notifications = props => {
+ const { children } = props;
+ return (
+