"
- 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 += "
"
- 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/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..2ef0abb000 100644
--- a/code/modules/cargo/packs/armory.dm
+++ b/code/modules/cargo/packs/armory.dm
@@ -223,3 +223,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 86a7c73a34..5d07e85bac 100644
--- a/code/modules/cargo/packs/goodies.dm
+++ b/code/modules/cargo/packs/goodies.dm
@@ -76,12 +76,6 @@
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."
diff --git a/code/modules/cargo/packs/misc.dm b/code/modules/cargo/packs/misc.dm
index a84e22f6f9..c6728831eb 100644
--- a/code/modules/cargo/packs/misc.dm
+++ b/code/modules/cargo/packs/misc.dm
@@ -194,9 +194,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 +415,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 +455,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/client/client_procs.dm b/code/modules/client/client_procs.dm
index 5c9b1eec2e..9b7e928852 100644
--- a/code/modules/client/client_procs.dm
+++ b/code/modules/client/client_procs.dm
@@ -41,6 +41,7 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
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,6 +99,10 @@ 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)
diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm
index 739a5fa243..1b6e1f9748 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
@@ -230,7 +229,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
@@ -1059,58 +1059,83 @@ GLOBAL_LIST_EMPTY(preferences_datums)
dat += " "
if(3)
- if(!gear_tab)
- gear_tab = GLOB.loadout_items[1]
dat += "
"
- 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/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 += "
Message from [ID.registered_name] to [R] -- \"[message]\" ")
+ to_chat(R.connected_ai, "
Message from [ID] to [R] -- \"[message]\" ")
SEND_SOUND(R.connected_ai, 'sound/machines/twobeep_high.ogg')
+ usr.log_talk(message, LOG_PDA, tag="Cyborg Monitor Program: ID name \"[ID]\" to [R]")
+
+///This proc is used to determin if a borg should be shown in the list (based on the borg's scrambledcodes var). Syndicate version overrides this to show only syndicate borgs.
+/datum/computer_file/program/borg_monitor/proc/evaluate_borg(mob/living/silicon/robot/R)
+ if((get_turf(computer)).z != (get_turf(R)).z)
+ return FALSE
+ if(R.scrambledcodes)
+ return FALSE
+ return TRUE
+
+///Gets the ID's name, if one is inserted into the device. This is a seperate proc solely to be overridden by the syndicate version of the app.
+/datum/computer_file/program/borg_monitor/proc/checkID()
+ var/obj/item/card/id/ID = computer.GetID()
+ if(!ID)
+ return FALSE
+ return ID.registered_name
+
+/datum/computer_file/program/borg_monitor/syndicate
+ filename = "scyborgmonitor"
+ filedesc = "Mission-Specific Cyborg Remote Monitoring"
+ ui_header = "borg_mon.gif"
+ program_icon_state = "generic"
+ extended_desc = "This program allows for remote monitoring of mission-assigned cyborgs."
+ requires_ntnet = FALSE
+ available_on_ntnet = FALSE
+ available_on_syndinet = TRUE
+ transfer_access = null
+ network_destination = "cyborg remote monitoring"
+ tgui_id = "NtosCyborgRemoteMonitorSyndicate"
+
+/datum/computer_file/program/borg_monitor/syndicate/evaluate_borg(mob/living/silicon/robot/R)
+ if((get_turf(computer)).z != (get_turf(R)).z)
+ return FALSE
+ if(!R.scrambledcodes)
+ return FALSE
+ return TRUE
+
+/datum/computer_file/program/borg_monitor/syndicate/checkID()
+ return "\[CLASSIFIED\]" //no ID is needed for the syndicate version's message function, and the borg will see "[CLASSIFIED]" as the message sender.
diff --git a/code/modules/modular_computers/file_system/programs/bounty_board.dm b/code/modules/modular_computers/file_system/programs/bounty_board.dm
new file mode 100644
index 0000000000..46fde84f65
--- /dev/null
+++ b/code/modules/modular_computers/file_system/programs/bounty_board.dm
@@ -0,0 +1,120 @@
+/datum/computer_file/program/bounty_board
+ filename = "bountyboard"
+ filedesc = "Bounty Board Request Network"
+ program_icon_state = "bountyboard"
+ extended_desc = "A multi-platform network for placing requests across the station, with payment across the network being possible.."
+ requires_ntnet = TRUE
+ network_destination = "bounty board interface"
+ size = 10
+ tgui_id = "NtosRequestKiosk"
+ ///Reference to the currently logged in user.
+ var/datum/bank_account/current_user
+ ///The station request datum being affected by UI actions.
+ var/datum/station_request/active_request
+ ///Value of the currently bounty input
+ var/bounty_value = 1
+ ///Text of the currently written bounty
+ var/bounty_text = ""
+ ///Has the app been added to the network yet?
+ var/networked = FALSE
+
+/datum/computer_file/program/bounty_board/ui_data(mob/user)
+ var/list/data = get_header_data()
+ var/list/formatted_requests = list()
+ var/list/formatted_applicants = list()
+ var/obj/item/computer_hardware/card_slot/card_slot = computer.all_components[MC_CARD]
+ if(!networked)
+ GLOB.allbountyboards += computer
+ networked = TRUE
+ if(card_slot && card_slot.stored_card && card_slot.stored_card.registered_account)
+ current_user = card_slot.stored_card.registered_account
+ for(var/i in GLOB.request_list)
+ if(!i)
+ continue
+ var/datum/station_request/request = i
+ formatted_requests += list(list("owner" = request.owner, "value" = request.value, "description" = request.description, "acc_number" = request.req_number))
+ if(request.applicants)
+ for(var/datum/bank_account/j in request.applicants)
+ formatted_applicants += list(list("name" = j.account_holder, "request_id" = request.owner_account.account_id, "requestee_id" = j.account_id))
+ if(current_user)
+ data["accountName"] = current_user.account_holder
+ data["requests"] = formatted_requests
+ data["applicants"] = formatted_applicants
+ data["bountyValue"] = bounty_value
+ data["bountyText"] = bounty_text
+ return data
+
+/datum/computer_file/program/bounty_board/ui_act(action, list/params)
+ if(..())
+ return
+ var/current_ref_num = params["request"]
+ var/current_app_num = params["applicant"]
+ var/datum/bank_account/request_target
+ if(current_ref_num)
+ for(var/datum/station_request/i in GLOB.request_list)
+ if("[i.req_number]" == "[current_ref_num]")
+ active_request = i
+ break
+ if(active_request)
+ for(var/datum/bank_account/j in active_request.applicants)
+ if("[j.account_id]" == "[current_app_num]")
+ request_target = j
+ break
+ switch(action)
+ if("createBounty")
+ if(!current_user || !bounty_text)
+ playsound(src, 'sound/machines/buzz-sigh.ogg', 20, TRUE)
+ return TRUE
+ for(var/datum/station_request/i in GLOB.request_list)
+ if("[i.req_number]" == "[current_user.account_id]")
+ computer.say("Account already has active bounty.")
+ return
+ var/datum/station_request/curr_request = new /datum/station_request(current_user.account_holder, bounty_value,bounty_text,current_user.account_id, current_user)
+ GLOB.request_list += list(curr_request)
+ for(var/obj/i in GLOB.allbountyboards)
+ i.say("New bounty has been added!")
+ playsound(i.loc, 'sound/effects/cashregister.ogg', 30, TRUE)
+ return TRUE
+ if("apply")
+ if(!current_user)
+ computer.say("Please swipe a valid ID first.")
+ return TRUE
+ if(current_user.account_holder == active_request.owner)
+ playsound(computer, 'sound/machines/buzz-sigh.ogg', 20, TRUE)
+ return TRUE
+ active_request.applicants += list(current_user)
+ if("payApplicant")
+ if(!current_user)
+ return
+ if(!current_user.has_money(active_request.value) || (current_user.account_holder != active_request.owner))
+ playsound(computer, 'sound/machines/buzz-sigh.ogg', 30, TRUE)
+ return
+ request_target.transfer_money(current_user, active_request.value)
+ computer.say("Paid out [active_request.value] credits.")
+ return TRUE
+ if("clear")
+ if(current_user)
+ current_user = null
+ computer.say("Account Reset.")
+ return TRUE
+ if("deleteRequest")
+ if(!current_user)
+ playsound(computer, 'sound/machines/buzz-sigh.ogg', 20, TRUE)
+ return TRUE
+ if(active_request.owner != current_user.account_holder)
+ playsound(computer, 'sound/machines/buzz-sigh.ogg', 20, TRUE)
+ return TRUE
+ computer.say("Deleted current request.")
+ GLOB.request_list.Remove(active_request)
+ return TRUE
+ if("bountyVal")
+ bounty_value = text2num(params["bountyval"])
+ if(!bounty_value)
+ bounty_value = 1
+ if("bountyText")
+ bounty_text = (params["bountytext"])
+ . = TRUE
+
+/datum/computer_file/program/bounty_board/Destroy()
+ GLOB.allbountyboards -= computer
+ . = ..()
diff --git a/code/modules/modular_computers/file_system/programs/card.dm b/code/modules/modular_computers/file_system/programs/card.dm
index 07d39438d8..842d6e2588 100644
--- a/code/modules/modular_computers/file_system/programs/card.dm
+++ b/code/modules/modular_computers/file_system/programs/card.dm
@@ -15,8 +15,6 @@
requires_ntnet = 0
size = 8
tgui_id = "NtosCard"
- ui_x = 450
- ui_y = 520
var/is_centcom = FALSE
var/minor = FALSE
@@ -275,7 +273,7 @@
departments = list("CentCom" = get_all_centcom_jobs())
else if(isnull(departments))
departments = list(
- CARDCON_DEPARTMENT_COMMAND = list("Captain"),
+ CARDCON_DEPARTMENT_COMMAND = list("Captain"),//lol
CARDCON_DEPARTMENT_ENGINEERING = GLOB.engineering_positions,
CARDCON_DEPARTMENT_MEDICAL = GLOB.medical_positions,
CARDCON_DEPARTMENT_SCIENCE = GLOB.science_positions,
diff --git a/code/modules/modular_computers/file_system/programs/cargobounty.dm b/code/modules/modular_computers/file_system/programs/cargobounty.dm
new file mode 100644
index 0000000000..d9bc65c98d
--- /dev/null
+++ b/code/modules/modular_computers/file_system/programs/cargobounty.dm
@@ -0,0 +1,48 @@
+/datum/computer_file/program/bounty
+ filename = "bounty"
+ filedesc = "Nanotrasen Bounty Hunter"
+ program_icon_state = "bounty"
+ extended_desc = "A basic interface for supply personnel to check and claim bounties."
+ requires_ntnet = TRUE
+ transfer_access = ACCESS_CARGO
+ network_destination = "cargo claims interface"
+ size = 10
+ tgui_id = "NtosBountyConsole"
+ ///cooldown var for printing paper sheets.
+ var/printer_ready = 0
+ ///The cargo account for grabbing the cargo account's credits.
+ var/static/datum/bank_account/cargocash
+
+/datum/computer_file/program/bounty/proc/print_paper()
+ new /obj/item/paper/bounty_printout(get_turf(computer))
+
+/datum/computer_file/program/bounty/ui_interact(mob/user, datum/tgui/ui)
+ if(!GLOB.bounties_list.len)
+ setup_bounties()
+ printer_ready = world.time + PRINTER_TIMEOUT
+ cargocash = SSeconomy.get_dep_account(ACCOUNT_CAR)
+ . = ..()
+
+/datum/computer_file/program/bounty/ui_data(mob/user)
+ var/list/data = get_header_data()
+ 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
+
+/datum/computer_file/program/bounty/ui_act(action,params)
+ if(..())
+ return
+ 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()
+ return
diff --git a/code/modules/modular_computers/file_system/programs/cargoship.dm b/code/modules/modular_computers/file_system/programs/cargoship.dm
index 39543adfa5..3ba08a3719 100644
--- a/code/modules/modular_computers/file_system/programs/cargoship.dm
+++ b/code/modules/modular_computers/file_system/programs/cargoship.dm
@@ -6,11 +6,9 @@
network_destination = "ship scanner"
size = 6
tgui_id = "NtosShipping"
- ui_x = 450
- ui_y = 350
///Account used for creating barcodes.
var/datum/bank_account/payments_acc
- ///The amount which the tagger will recieve for the sale.
+ ///The amount which the tagger will receive for the sale.
var/percent_cut = 20
/datum/computer_file/program/shipping/ui_data(mob/user)
diff --git a/code/modules/modular_computers/file_system/programs/configurator.dm b/code/modules/modular_computers/file_system/programs/configurator.dm
index 76da58ea11..fae06544d5 100644
--- a/code/modules/modular_computers/file_system/programs/configurator.dm
+++ b/code/modules/modular_computers/file_system/programs/configurator.dm
@@ -10,8 +10,6 @@
unsendable = 1
undeletable = 1
size = 4
- ui_x = 420
- ui_y = 630
available_on_ntnet = 0
requires_ntnet = 0
tgui_id = "NtosConfiguration"
diff --git a/code/modules/modular_computers/file_system/programs/crewmanifest.dm b/code/modules/modular_computers/file_system/programs/crewmanifest.dm
index 662c867a39..a1503ce3a8 100644
--- a/code/modules/modular_computers/file_system/programs/crewmanifest.dm
+++ b/code/modules/modular_computers/file_system/programs/crewmanifest.dm
@@ -7,12 +7,10 @@
requires_ntnet = FALSE
size = 4
tgui_id = "NtosCrewManifest"
- ui_x = 400
- ui_y = 480
/datum/computer_file/program/crew_manifest/ui_static_data(mob/user)
var/list/data = list()
- data["manifest"] = GLOB.data_core.get_manifest()
+ data["manifest"] = GLOB.data_core.get_manifest_tg()
return data
/datum/computer_file/program/crew_manifest/ui_data(mob/user)
@@ -41,9 +39,9 @@
if(computer && printer) //This option should never be called if there is no printer
var/contents = {"
Crew Manifest
- [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..bccc6e4dbe 100644
--- a/code/modules/modular_computers/file_system/programs/jobmanagement.dm
+++ b/code/modules/modular_computers/file_system/programs/jobmanagement.dm
@@ -7,8 +7,6 @@
requires_ntnet = 0
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..6401d6207f 100644
--- a/code/modules/modular_computers/file_system/programs/ntdownloader.dm
+++ b/code/modules/modular_computers/file_system/programs/ntdownloader.dm
@@ -11,8 +11,6 @@
available_on_ntnet = 0
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
@@ -130,7 +143,7 @@
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))
@@ -143,9 +156,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 +185,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/ntnrc_client.dm b/code/modules/modular_computers/file_system/programs/ntnrc_client.dm
index 4ae5bd326b..df9b02d8ec 100644
--- a/code/modules/modular_computers/file_system/programs/ntnrc_client.dm
+++ b/code/modules/modular_computers/file_system/programs/ntnrc_client.dm
@@ -10,9 +10,6 @@
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..bd11474858 100644
--- a/code/modules/modular_computers/file_system/programs/powermonitor.dm
+++ b/code/modules/modular_computers/file_system/programs/powermonitor.dm
@@ -12,8 +12,6 @@
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..9b0e09ef99 100644
--- a/code/modules/modular_computers/file_system/programs/radar.dm
+++ b/code/modules/modular_computers/file_system/programs/radar.dm
@@ -2,31 +2,49 @@
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
+ usage_flags = PROGRAM_LAPTOP | PROGRAM_TABLET
network_destination = "tracking program"
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 +83,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 +125,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,6 +148,59 @@
/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//
///////////////////
@@ -106,44 +209,13 @@
/datum/computer_file/program/radar/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)
@@ -184,46 +256,18 @@
/datum/computer_file/program/radar/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)
diff --git a/code/modules/modular_computers/file_system/programs/robocontrol.dm b/code/modules/modular_computers/file_system/programs/robocontrol.dm
index 910f923327..8644ce09b4 100644
--- a/code/modules/modular_computers/file_system/programs/robocontrol.dm
+++ b/code/modules/modular_computers/file_system/programs/robocontrol.dm
@@ -9,8 +9,6 @@
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.
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..32ad102871 100644
--- a/code/modules/modular_computers/file_system/programs/sm_monitor.dm
+++ b/code/modules/modular_computers/file_system/programs/sm_monitor.dm
@@ -9,8 +9,6 @@
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..b33442f99b 100644
--- a/code/modules/modular_computers/hardware/_hardware.dm
+++ b/code/modules/modular_computers/hardware/_hardware.dm
@@ -32,28 +32,29 @@
/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)
to_chat(user, "Hardware Integrity Test... (Corruption: [damage]/[max_damage]) [damage > damage_failure ? "FAIL" : damage > damage_malfunction ? "WARN" : "PASS"]")
diff --git a/code/modules/modular_computers/hardware/ai_slot.dm b/code/modules/modular_computers/hardware/ai_slot.dm
index 8428467a87..0ad157afcb 100644
--- a/code/modules/modular_computers/hardware/ai_slot.dm
+++ b/code/modules/modular_computers/hardware/ai_slot.dm
@@ -9,6 +9,10 @@
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)
. = ..()
@@ -41,13 +45,6 @@
/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
if(!stored_card)
to_chat(user, "There is no card in \the [src].")
return FALSE
@@ -57,19 +54,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..6e3193abfd 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(0, null, TRUE)
+ . = ..()
+
/obj/item/computer_hardware/battery/try_insert(obj/item/I, mob/living/user = null)
if(!holder)
return FALSE
@@ -45,7 +53,10 @@
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..18b423a42e 100644
--- a/code/modules/modular_computers/hardware/card_slot.dm
+++ b/code/modules/modular_computers/hardware/card_slot.dm
@@ -9,6 +9,13 @@
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(1, null, TRUE)
+ if(A == stored_card2)
+ try_eject(2, null, TRUE)
+ . = ..()
+
/obj/item/computer_hardware/card_slot/Destroy()
try_eject()
return ..()
@@ -67,19 +74,15 @@
else
stored_card2 = I
to_chat(user, "You insert \the [I] into \the [src].")
- playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, 0)
+ 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)
to_chat(user, "There are no cards in \the [src].")
return FALSE
@@ -89,7 +92,7 @@
if(user)
user.put_in_hands(stored_card)
else
- stored_card.forceMove(get_turf(src))
+ stored_card.forceMove(drop_location())
stored_card = null
ejected++
@@ -97,7 +100,7 @@
if(user)
user.put_in_hands(stored_card2)
else
- stored_card2.forceMove(get_turf(src))
+ stored_card2.forceMove(drop_location())
stored_card2 = null
ejected++
@@ -109,16 +112,18 @@
for(var/I in holder.idle_threads)
var/datum/computer_file/program/P = I
P.event_idremoved(1, slot)
-
+ if(ishuman(user))
+ var/mob/living/carbon/human/H = user
+ H.sec_hud_set_ID()
to_chat(user, "You remove the card[ejected>1 ? "s" : ""] from \the [src].")
- playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, 0)
+ playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, FALSE)
return TRUE
return FALSE
/obj/item/computer_hardware/card_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(0,user)
return
diff --git a/code/modules/modular_computers/hardware/hard_drive.dm b/code/modules/modular_computers/hardware/hard_drive.dm
index e27eaa53ae..b8b9624388 100644
--- a/code/modules/modular_computers/hardware/hard_drive.dm
+++ b/code/modules/modular_computers/hardware/hard_drive.dm
@@ -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/printer.dm b/code/modules/modular_computers/hardware/printer.dm
index 36c666e5d6..ebe40c1922 100644
--- a/code/modules/modular_computers/hardware/printer.dm
+++ b/code/modules/modular_computers/hardware/printer.dm
@@ -10,14 +10,14 @@
/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 +27,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/laptop_vendor.dm b/code/modules/modular_computers/laptop_vendor.dm
index ee9def4191..a8d30bad21 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
@@ -224,15 +221,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 +238,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 +254,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 has been deposited from your account.")
return
return ..()
@@ -308,4 +305,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/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/paperwork/paper.dm b/code/modules/paperwork/paper.dm
index 01c25b57f1..5d842ef11a 100644
--- a/code/modules/paperwork/paper.dm
+++ b/code/modules/paperwork/paper.dm
@@ -1,4 +1,4 @@
-/*
+/**
* Paper
* also scraps of paper
*
@@ -11,12 +11,11 @@
#define MODE_WRITING 1
#define MODE_STAMPING 2
-
/**
- ** 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
- **/
+ * 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
@@ -33,7 +32,6 @@
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)
@@ -49,12 +47,11 @@
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
@@ -71,6 +68,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.
@@ -89,9 +88,6 @@
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
@@ -103,7 +99,6 @@
// 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()
@@ -116,10 +111,10 @@
. = ..()
/**
- ** 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 +128,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,30 +147,16 @@
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"
@@ -195,17 +176,14 @@
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)
@@ -214,7 +192,6 @@
addtimer(CALLBACK(src, .proc/reset_spamflag), 20)
. = ..()
-
/obj/item/paper/proc/clearpaper()
info = ""
stamps = null
@@ -222,29 +199,28 @@
cut_overlays()
update_icon_state()
-
-/obj/item/paper/examine(mob/user)
+/obj/item/paper/examine_more(mob/user)
ui_interact(user)
-
+ return list("You try to read [src]...")
/obj/item/paper/can_interact(mob/user)
if(!..())
return FALSE
- if(resistance_flags & ON_FIRE) // Are we on fire? Hard ot read if so
+ // Are we on fire? Hard ot read if so
+ if(resistance_flags & ON_FIRE)
return FALSE
- if(user.is_blind()) // Even harder to read if your blind...braile? humm
+ // Even harder to read if your blind...braile? humm
+ if(user.is_blind())
return FALSE
- return user.can_read(src) // checks if the user can read.
-
+ // checks if the user can read.
+ return user.can_read(src)
/**
- ** 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?
-**/
+ * This creates the ui, since we are using a custom state but not much else
+ * just makes it easyer to make it.
+ */
/obj/item/paper/proc/create_ui(mob/user, datum/ui_state/default/paper_state/state)
- ui_interact(user, "main", null, FALSE, null, state)
-
+ ui_interact(user, state = state)
/obj/item/proc/burn_paper_product_attackby_check(obj/item/I, mob/living/user, bypass_clumsy)
var/ignition_message = I.ignition_effect(src, user)
@@ -320,24 +296,27 @@
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,
+ datum/ui_state/default/paper_state/state)
+ // Update the state
+ ui = ui || SStgui.get_open_ui(user, src)
+ if(ui && state)
+ var/datum/ui_state/default/paper_state/current_state = ui.state
+ current_state.copy_from(state)
+ // 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 = new(user, src, "PaperSheet", name)
+ state = new
+ ui.set_state(state)
ui.set_autoupdate(FALSE)
viewing_ui[user] = ui
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
@@ -347,7 +326,7 @@
// 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()
+ ui.process(force = TRUE)
// Again, we have to do this as autoupdate is off
/obj/item/paper/proc/close_all_ui()
@@ -383,7 +362,6 @@
return data
-
/obj/item/paper/ui_act(action, params, datum/tgui/ui, datum/ui_state/default/paper_state/state)
if(..())
return
@@ -445,21 +423,18 @@
. = 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/pen.dm b/code/modules/paperwork/pen.dm
index 997ada6b21..91b8a6719b 100644
--- a/code/modules/paperwork/pen.dm
+++ b/code/modules/paperwork/pen.dm
@@ -147,7 +147,7 @@
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)
+ var/input = stripped_input(user,"Describe \the [O.name] here", ,"", 2048)
if(QDELETED(O) || !user.canUseTopic(O, BE_CLOSE))
return
O.desc = input
diff --git a/code/modules/power/apc.dm b/code/modules/power/apc.dm
index ba51eb3bef..a70e508476 100644
--- a/code/modules/power/apc.dm
+++ b/code/modules/power/apc.dm
@@ -838,6 +838,46 @@
// attack with hand - remove cell (if cover open) or interact with the APC
/obj/machinery/power/apc/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
+ if(isethereal(user))
+ var/mob/living/carbon/human/H = user
+ if(H.a_intent == INTENT_HARM)
+ if(cell.charge <= (cell.maxcharge / 2)) // if charge is under 50% you shouldnt drain it
+ to_chat(H, "The APC doesn't have much power, you probably shouldn't drain any.")
+ return
+ var/obj/item/organ/stomach/ethereal/stomach = H.getorganslot(ORGAN_SLOT_STOMACH)
+ if(stomach.crystal_charge > 145)
+ to_chat(H, "Your charge is full!")
+ return
+ to_chat(H, "You start channeling some power through the APC into your body.")
+ if(do_after(user, 75, target = src))
+ if(cell.charge <= (cell.maxcharge / 2) || (stomach.crystal_charge > 145))
+ return
+ if(istype(stomach))
+ to_chat(H, "You receive some charge from the APC.")
+ stomach.adjust_charge(10)
+ cell.charge -= 10
+ else
+ to_chat(H, "You can't receive charge from the APC!")
+ return
+ if(H.a_intent == INTENT_GRAB)
+ if(cell.charge == cell.maxcharge)
+ to_chat(H, "The APC is full!")
+ return
+ var/obj/item/organ/stomach/ethereal/stomach = H.getorganslot(ORGAN_SLOT_STOMACH)
+ if(stomach.crystal_charge < 10)
+ to_chat(H, "Your charge is too low!")
+ return
+ to_chat(H, "You start channeling power through your body into the APC.")
+ if(do_after(user, 75, target = src))
+ if(cell.charge == cell.maxcharge || (stomach.crystal_charge < 10))
+ return
+ if(istype(stomach))
+ to_chat(H, "You transfer some power to the APC.")
+ stomach.adjust_charge(-10)
+ cell.charge += 10
+ else
+ to_chat(H, "You can't transfer power to the APC!")
+ return
if(opened && (!issilicon(user)))
if(cell)
user.visible_message("[user] removes \the [cell] from [src]!","You remove \the [cell].")
@@ -850,26 +890,19 @@
if((stat & MAINT) && !opened) //no board; no interface
return
-/obj/machinery/power/apc/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/power/apc/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
- ui = new(user, src, ui_key, "Apc", name, 480, 460, master_ui, state)
+ ui = new(user, src, "Apc", name)
ui.open()
/obj/machinery/power/apc/ui_data(mob/user)
- var/obj/item/implant/hijack/H = user.getImplant(/obj/item/implant/hijack)
- var/abilitiesavail = FALSE
- if (H && !H.stealthmode && H.toggled)
- abilitiesavail = TRUE
var/list/data = list(
"locked" = locked,
- "lock_nightshift" = nightshift_requires_auth,
"failTime" = failure_timer,
"isOperating" = operating,
"externalPower" = main_status,
- "powerCellStatus" = (cell?.percent() || null),
+ "powerCellStatus" = cell ? cell.percent() : null,
"chargeMode" = chargemode,
"chargingStatus" = charging,
"totalLoad" = DisplayPower(lastused_total),
@@ -878,10 +911,7 @@
"malfStatus" = get_malf_status(user),
"emergencyLights" = !emergency_lights,
"nightshiftLights" = nightshift_lights,
- "hijackable" = HAS_TRAIT(user,TRAIT_HIJACKER),
- "hijacker" = hijacker == user ? TRUE : FALSE,
- "drainavail" = cell && cell.percent() >= 85 && abilitiesavail,
- "lockdownavail" = cell && cell.percent() >= 35 && abilitiesavail,
+
"powerChannels" = list(
list(
"title" = "Equipment",
@@ -979,43 +1009,32 @@
. = UI_INTERACTIVE
/obj/machinery/power/apc/ui_act(action, params)
- if(..() || !can_use(usr, 1))
- return
- if(failure_timer)
- if(action == "reboot")
- failure_timer = 0
- update_icon()
- update()
- if(action == "hijack" && can_use(usr, 1)) //don't need auth for hijack button
- hijack(usr)
- return
- var/authorized = (!locked || area.hasSiliconAccessInArea(usr, PRIVILEDGES_SILICON|PRIVILEDGES_DRONE) || (integration_cog && (is_servant_of_ratvar(usr))))
- if((action == "toggle_nightshift") && (!nightshift_requires_auth || authorized))
- toggle_nightshift_lights()
- return TRUE
- if(!authorized)
+ if(..() || !can_use(usr, 1) || (locked && area.hasSiliconAccessInArea(usr, PRIVILEDGES_SILICON|PRIVILEDGES_DRONE) && !failure_timer && action != "toggle_nightshift") || (integration_cog && (is_servant_of_ratvar(usr))))
return
switch(action)
if("lock")
if(area.hasSiliconAccessInArea(usr))
if((obj_flags & EMAGGED) || (stat & (BROKEN|MAINT)))
- to_chat(usr, "The APC does not respond to the command.")
+ to_chat(usr, "The APC does not respond to the command!")
else
locked = !locked
update_icon()
- return TRUE
+ . = TRUE
if("cover")
coverlocked = !coverlocked
- return TRUE
+ . = TRUE
if("breaker")
- toggle_breaker()
- return TRUE
+ toggle_breaker(usr)
+ . = TRUE
+ if("toggle_nightshift")
+ toggle_nightshift_lights()
+ . = TRUE
if("charge")
chargemode = !chargemode
if(!chargemode)
charging = APC_NOT_CHARGING
update_icon()
- return TRUE
+ . = TRUE
if("channel")
if(params["eqp"])
equipment = setsubsystem(text2num(params["eqp"]))
@@ -1029,23 +1048,24 @@
environ = setsubsystem(text2num(params["env"]))
update_icon()
update()
- return TRUE
+ . = TRUE
if("overload")
- if(area.hasSiliconAccessInArea(usr))
+ if(area.hasSiliconAccessInArea(usr, PRIVILEDGES_SILICON|PRIVILEDGES_DRONE)) //usr.has_unlimited_silicon_privilege)
overload_lighting()
- return TRUE
+ . = TRUE
if("hack")
if(get_malf_status(usr))
malfhack(usr)
- return TRUE
if("occupy")
if(get_malf_status(usr))
malfoccupy(usr)
- return TRUE
if("deoccupy")
if(get_malf_status(usr))
malfvacate()
- return TRUE
+ if("reboot")
+ failure_timer = 0
+ update_icon()
+ update()
if("emergency_lighting")
emergency_lights = !emergency_lights
for(var/obj/machinery/light/L in area)
@@ -1053,31 +1073,14 @@
L.no_emergency = emergency_lights
INVOKE_ASYNC(L, /obj/machinery/light/.proc/update, FALSE)
CHECK_TICK
- if("drain")
- cell.use(cell.charge)
- hijacker.toggleSiliconAccessArea(area)
- hijacker = null
- set_hijacked_lighting()
- update_icon()
- var/obj/item/implant/hijack/H = usr.getImplant(/obj/item/implant/hijack)
- H.stealthcooldown = world.time + 2 MINUTES
- energy_fail(30 SECONDS * (cell.charge / cell.maxcharge))
- if("lockdown")
- var/celluse = rand(20,35)
- celluse = celluse /100
- for (var/obj/machinery/door/D in GLOB.airlocks)
- if (get_area(D) == area)
- INVOKE_ASYNC(D,/obj/machinery/door.proc/hostile_lockdown,usr, FALSE)
- addtimer(CALLBACK(D,/obj/machinery/door.proc/disable_lockdown, FALSE), 30 SECONDS)
- cell.charge -= cell.maxcharge*celluse
- var/obj/item/implant/hijack/H = usr.getImplant(/obj/item/implant/hijack)
- H.stealthcooldown = world.time + 3 MINUTES
return TRUE
-/obj/machinery/power/apc/proc/toggle_breaker()
+/obj/machinery/power/apc/proc/toggle_breaker(mob/user)
if(!is_operational() || failure_timer)
return
operating = !operating
+ add_hiddenprint(user) //delete when runtime
+ log_game("[key_name(user)] turned [operating ? "on" : "off"] the [src] in [AREACOORD(src)]")
update()
update_icon()
@@ -1122,6 +1125,10 @@
if(malf.malfhacking)
to_chat(malf, "You are already hacking an APC.")
return
+ var/area/ourarea = get_area(src)
+ if(!ourarea.valid_malf_hack)
+ to_chat(malf, "This APC is not well connected enough to the Exonet to provide any useful processing capabilities.")
+ return
to_chat(malf, "Beginning override of APC systems. This takes some time, and you cannot perform other actions during the process.")
malf.malfhack = src
malf.malfhacking = addtimer(CALLBACK(malf, /mob/living/silicon/ai/.proc/malfhacked, src), 600, TIMER_STOPPABLE)
diff --git a/code/modules/power/cell.dm b/code/modules/power/cell.dm
index ba6311a94d..6425feac31 100644
--- a/code/modules/power/cell.dm
+++ b/code/modules/power/cell.dm
@@ -151,6 +151,27 @@
if(prob(25))
corrupt()
+/obj/item/stock_parts/cell/attack_self(mob/user)
+ if(isethereal(user))
+ var/mob/living/carbon/human/H = user
+ if(charge < 100)
+ to_chat(H, "The [src] doesn't have enough power!")
+ return
+ var/obj/item/organ/stomach/ethereal/stomach = H.getorganslot(ORGAN_SLOT_STOMACH)
+ if(stomach.crystal_charge > 146)
+ to_chat(H, "Your charge is full!")
+ return
+ to_chat(H, "You clumsily channel power through the [src] and into your body, wasting some in the process.")
+ if(do_after(user, 5, target = src))
+ if((charge < 100) || (stomach.crystal_charge > 146))
+ return
+ if(istype(stomach))
+ to_chat(H, "You receive some charge from the [src].")
+ stomach.adjust_charge(3)
+ charge -= 100 //you waste way more than you receive, so that ethereals cant just steal one cell and forget about hunger
+ else
+ to_chat(H, "You can't receive charge from the [src]!")
+ return
/obj/item/stock_parts/cell/blob_act(obj/structure/blob/B)
ex_act(EXPLODE_DEVASTATE)
diff --git a/code/modules/power/gravitygenerator.dm b/code/modules/power/gravitygenerator.dm
index 30a7aa152b..e37ae56e71 100644
--- a/code/modules/power/gravitygenerator.dm
+++ b/code/modules/power/gravitygenerator.dm
@@ -116,8 +116,6 @@ GLOBAL_LIST_EMPTY(gravity_generators) // We will keep track of this by adding ne
sprite_number = 8
use_power = IDLE_POWER_USE
interaction_flags_machine = INTERACT_MACHINE_ALLOW_SILICON | INTERACT_MACHINE_OFFLINE
- ui_x = 400
- ui_y = 165
var/on = TRUE
var/breaker = TRUE
var/list/parts = list()
@@ -222,11 +220,10 @@ GLOBAL_LIST_EMPTY(gravity_generators) // We will keep track of this by adding ne
return
return ..()
-/obj/machinery/gravity_generator/main/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/gravity_generator/main/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
- ui = new(user, src, ui_key, "GravityGenerator", name, ui_x, ui_y, master_ui, state)
+ ui = new(user, src, "GravityGenerator", name)
ui.open()
/obj/machinery/gravity_generator/main/ui_data(mob/user)
diff --git a/code/modules/power/lighting.dm b/code/modules/power/lighting.dm
index 20f7ce099a..4c76c4b5b1 100644
--- a/code/modules/power/lighting.dm
+++ b/code/modules/power/lighting.dm
@@ -610,7 +610,18 @@
var/mob/living/carbon/human/H = user
if(istype(H))
-
+ var/datum/species/ethereal/eth_species = H.dna?.species
+ if(istype(eth_species))
+ to_chat(H, "You start channeling some power through the [fitting] into your body.")
+ if(do_after(user, 50, target = src))
+ var/obj/item/organ/stomach/ethereal/stomach = H.getorganslot(ORGAN_SLOT_STOMACH)
+ if(istype(stomach))
+ to_chat(H, "You receive some charge from the [fitting].")
+ stomach.adjust_charge(2)
+ else
+ to_chat(H, "You can't receive charge from the [fitting]!")
+ return
+
if(H.gloves)
var/obj/item/clothing/gloves/G = H.gloves
if(G.max_heat_protection_temperature)
diff --git a/code/modules/power/monitor.dm b/code/modules/power/monitor.dm
index 974fb1b9e2..393d403c4d 100644
--- a/code/modules/power/monitor.dm
+++ b/code/modules/power/monitor.dm
@@ -11,8 +11,6 @@
active_power_usage = 100
circuit = /obj/item/circuitboard/computer/powermonitor
tgui_id = "PowerMonitor"
- ui_x = 550
- ui_y = 700
var/obj/structure/cable/attached_wire
var/obj/machinery/power/apc/local_apc
@@ -84,11 +82,10 @@
if(demand.len > record_size)
demand.Cut(1, 2)
-/obj/machinery/computer/monitor/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/monitor/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
- ui = new(user, src, ui_key, "PowerMonitor", name, ui_x, ui_y, master_ui, state)
+ ui = new(user, src, "PowerMonitor", name)
ui.open()
/obj/machinery/computer/monitor/ui_data()
diff --git a/code/modules/power/port_gen.dm b/code/modules/power/port_gen.dm
index d4e0df8359..e2f8c4e58a 100644
--- a/code/modules/power/port_gen.dm
+++ b/code/modules/power/port_gen.dm
@@ -7,8 +7,6 @@
density = TRUE
anchored = FALSE
use_power = NO_POWER_USE
- ui_x = 450
- ui_y = 340
var/active = FALSE
var/power_gen = 5000
@@ -219,11 +217,10 @@
/obj/machinery/power/port_gen/pacman/attack_paw(mob/user)
interact(user)
-/obj/machinery/power/port_gen/pacman/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/power/port_gen/pacman/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
- ui = new(user, src, ui_key, "PortableGenerator", name, ui_x, ui_y, master_ui, state)
+ ui = new(user, src, "PortableGenerator", name)
ui.open()
/obj/machinery/power/port_gen/pacman/ui_data()
diff --git a/code/modules/power/singularity/particle_accelerator/particle_control.dm b/code/modules/power/singularity/particle_accelerator/particle_control.dm
index 912fc0a72b..96c8d9a263 100644
--- a/code/modules/power/singularity/particle_accelerator/particle_control.dm
+++ b/code/modules/power/singularity/particle_accelerator/particle_control.dm
@@ -10,8 +10,6 @@
active_power_usage = 10000
dir = NORTH
mouse_opacity = MOUSE_OPACITY_OPAQUE
- ui_x = 350
- ui_y = 185
var/strength_upper_limit = 2
var/interface_control = TRUE
var/list/obj/structure/particle_accelerator/connected_parts
@@ -275,11 +273,10 @@
return ..()
return UI_CLOSE
-/obj/machinery/particle_accelerator/control_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/machinery/particle_accelerator/control_box/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
- ui = new(user, src, ui_key, "ParticleAccelerator", name, ui_x, ui_y, master_ui, state)
+ ui = new(user, src, "ParticleAccelerator", name)
ui.open()
/obj/machinery/particle_accelerator/control_box/ui_data(mob/user)
diff --git a/code/modules/power/smes.dm b/code/modules/power/smes.dm
index fcc7faa29e..a4fc7d0641 100644
--- a/code/modules/power/smes.dm
+++ b/code/modules/power/smes.dm
@@ -21,8 +21,6 @@
density = TRUE
use_power = NO_POWER_USE
circuit = /obj/item/circuitboard/machine/smes
- ui_x = 340
- ui_y = 350
var/capacity = 5e6 // maximum charge
var/charge = 0 // actual charge
@@ -202,7 +200,7 @@
/obj/machinery/power/smes/update_overlays()
. = ..()
- if((stat & BROKEN) || panel_open)
+ if(stat & BROKEN)
return
if(panel_open)
@@ -318,11 +316,10 @@
return
-/obj/machinery/power/smes/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/power/smes/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
- ui = new(user, src, ui_key, "Smes", name, ui_x, ui_y, master_ui, state)
+ ui = new(user, src, "Smes", name)
ui.open()
/obj/machinery/power/smes/ui_data()
diff --git a/code/modules/power/solar.dm b/code/modules/power/solar.dm
index 2c258c1f49..cf526f083d 100644
--- a/code/modules/power/solar.dm
+++ b/code/modules/power/solar.dm
@@ -115,9 +115,6 @@
panel.icon_state = "solar_panel-b"
else
panel.icon_state = "solar_panel"
-#if DM_VERSION <= 512
- . += new /mutable_appearance(panel)
-#endif
/obj/machinery/power/solar/proc/queue_turn(azimuth)
needs_to_turn = TRUE
@@ -346,11 +343,10 @@
else
. += mutable_appearance(icon, icon_screen)
-/obj/machinery/power/solar_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/power/solar_control/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
- ui = new(user, src, ui_key, "SolarControl", name, 380, 230, master_ui, state)
+ ui = new(user, src, "SolarControl", name)
ui.open()
/obj/machinery/power/solar_control/ui_data()
diff --git a/code/modules/power/turbine.dm b/code/modules/power/turbine.dm
index e7a97c554f..a9acea719c 100644
--- a/code/modules/power/turbine.dm
+++ b/code/modules/power/turbine.dm
@@ -56,8 +56,6 @@
resistance_flags = FIRE_PROOF
CanAtmosPass = ATMOS_PASS_DENSITY
circuit = /obj/item/circuitboard/machine/power_turbine
- ui_x = 310
- ui_y = 150
var/opened = 0
var/obj/machinery/power/compressor/compressor
var/turf/outturf
@@ -249,11 +247,10 @@
default_deconstruction_crowbar(I)
-/obj/machinery/power/turbine/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/power/turbine/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
- ui = new(user, src, ui_key, "TurbineComputer", name, ui_x, ui_y, master_ui, state)
+ ui = new(user, src, "TurbineComputer", name)
ui.open()
/obj/machinery/power/turbine/ui_data(mob/user)
@@ -292,8 +289,6 @@
icon_screen = "turbinecomp"
icon_keyboard = "tech_key"
circuit = /obj/item/circuitboard/computer/turbine_computer
- ui_x = 310
- ui_y = 150
var/obj/machinery/power/compressor/compressor
var/id = 0
@@ -313,11 +308,10 @@
else
compressor = locate(/obj/machinery/power/compressor) in range(7, src)
-/obj/machinery/computer/turbine_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)
- ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+/obj/machinery/computer/turbine_computer/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
- ui = new(user, src, ui_key, "TurbineComputer", name, ui_x, ui_y, master_ui, state)
+ ui = new(user, src, "TurbineComputer", name)
ui.open()
/obj/machinery/computer/turbine_computer/ui_data(mob/user)
diff --git a/code/modules/procedural_mapping/mapGenerator.dm b/code/modules/procedural_mapping/mapGenerator.dm
index f509c409ce..323f74d0ef 100644
--- a/code/modules/procedural_mapping/mapGenerator.dm
+++ b/code/modules/procedural_mapping/mapGenerator.dm
@@ -147,8 +147,16 @@
set category = "Debug"
var/datum/mapGenerator/nature/N = new()
- var/startInput = input(usr,"Start turf of Map, (X;Y;Z)", "Map Gen Settings", "1;1;1") as text
- var/endInput = input(usr,"End turf of Map (X;Y;Z)", "Map Gen Settings", "[world.maxx];[world.maxy];[mob ? mob.z : 1]") as text
+ var/startInput = input(usr,"Start turf of Map, (X;Y;Z)", "Map Gen Settings", "1;1;1") as text|null
+
+ if (isnull(startInput))
+ return
+
+ var/endInput = input(usr,"End turf of Map (X;Y;Z)", "Map Gen Settings", "[world.maxx];[world.maxy];[mob ? mob.z : 1]") as text|null
+
+ if (isnull(endInput))
+ return
+
//maxx maxy and current z so that if you fuck up, you only fuck up one entire z level instead of the entire universe
if(!startInput || !endInput)
to_chat(src, "Missing Input")
diff --git a/code/modules/projectiles/ammunition/_firing.dm b/code/modules/projectiles/ammunition/_firing.dm
index 0ef4c680aa..437b7dcc5a 100644
--- a/code/modules/projectiles/ammunition/_firing.dm
+++ b/code/modules/projectiles/ammunition/_firing.dm
@@ -36,6 +36,14 @@
if(isgun(fired_from))
var/obj/item/gun/G = fired_from
BB.damage *= G.projectile_damage_multiplier
+ if(HAS_TRAIT(user, TRAIT_INSANE_AIM))
+ BB.ricochets_max = max(BB.ricochets_max, 10) //bouncy!
+ BB.ricochet_chance = max(BB.ricochet_chance, 100) //it wont decay so we can leave it at 100 for always bouncing
+ BB.ricochet_auto_aim_range = max(BB.ricochet_auto_aim_range, 3)
+ BB.ricochet_auto_aim_angle = max(BB.ricochet_auto_aim_angle, 360) //it can turn full circle and shoot you in the face because our aim? is insane.
+ BB.ricochet_decay_chance = 0
+ BB.ricochet_decay_damage = max(BB.ricochet_decay_damage, 0.1)
+ BB.ricochet_incidence_leeway = 0
if(reagents && BB.reagents)
reagents.trans_to(BB, reagents.total_volume) //For chemical darts/bullets
diff --git a/code/modules/projectiles/boxes_magazines/external/shotgun.dm b/code/modules/projectiles/boxes_magazines/external/shotgun.dm
index 1001937678..ed41375aee 100644
--- a/code/modules/projectiles/boxes_magazines/external/shotgun.dm
+++ b/code/modules/projectiles/boxes_magazines/external/shotgun.dm
@@ -1,5 +1,5 @@
/obj/item/ammo_box/magazine/m12g
- name = "shotgun magazine (12g buckshot slugs)"
+ name = "shotgun magazine (12g buckshot)"
desc = "A drum magazine."
icon_state = "m12gb"
ammo_type = /obj/item/ammo_casing/shotgun/buckshot
@@ -17,7 +17,7 @@
/obj/item/ammo_box/magazine/m12g/slug
name = "shotgun magazine (12g slugs)"
- icon_state = "m12gb" //this may need an unique sprite
+ icon_state = "m12gsl"
ammo_type = /obj/item/ammo_casing/shotgun
/obj/item/ammo_box/magazine/m12g/dragon
diff --git a/code/modules/projectiles/gun.dm b/code/modules/projectiles/gun.dm
index c3248bab2f..ee073dbfcd 100644
--- a/code/modules/projectiles/gun.dm
+++ b/code/modules/projectiles/gun.dm
@@ -29,7 +29,7 @@
trigger_guard = TRIGGER_GUARD_NORMAL //trigger guard on the weapon, hulks can't fire them with their big meaty fingers
var/sawn_desc = null //description change if weapon is sawn-off
var/sawn_off = FALSE
-
+
/// can we be put into a turret
var/can_turret = TRUE
/// can we be put in a circuit
@@ -310,8 +310,6 @@
randomized_gun_spread = rand(0, spread)
else if(burst_size > 1 && burst_spread)
randomized_gun_spread = rand(0, burst_spread)
- if(HAS_TRAIT(user, TRAIT_POOR_AIM)) //nice shootin' tex
- bonus_spread += 25
var/randomized_bonus_spread = rand(0, bonus_spread)
if(burst_size > 1)
@@ -603,10 +601,16 @@
var/penalty = (last_fire + GUN_AIMING_TIME + fire_delay) - world.time
if(penalty > 0) //Yet we only penalize users firing it multiple times in a haste. fire_delay isn't necessarily cumbersomeness.
aiming_delay = penalty
- if(SEND_SIGNAL(user, COMSIG_COMBAT_MODE_CHECK, COMBAT_MODE_ACTIVE)) //To be removed in favor of something less tactless later.
+ if(SEND_SIGNAL(user, COMSIG_COMBAT_MODE_CHECK, COMBAT_MODE_ACTIVE) || HAS_TRAIT(user, TRAIT_INSANE_AIM)) //To be removed in favor of something less tactless later.
base_inaccuracy /= 1.5
if(stamloss > STAMINA_NEAR_SOFTCRIT) //This can null out the above bonus.
base_inaccuracy *= 1 + (stamloss - STAMINA_NEAR_SOFTCRIT)/(STAMINA_NEAR_CRIT - STAMINA_NEAR_SOFTCRIT)*0.5
+ if(HAS_TRAIT(user, TRAIT_POOR_AIM)) //nice shootin' tex
+ if(!HAS_TRAIT(user, TRAIT_INSANE_AIM))
+ bonus_spread += 25
+ else
+ //you have both poor aim and insane aim, why?
+ bonus_spread += rand(0,50)
var/mult = max((GUN_AIMING_TIME + aiming_delay + user.last_click_move - world.time)/GUN_AIMING_TIME, -0.5) //Yes, there is a bonus for taking time aiming.
if(mult < 0) //accurate weapons should provide a proper bonus with negative inaccuracy. the opposite is true too.
mult *= 1/inaccuracy_modifier
diff --git a/code/modules/projectiles/guns/ballistic/revolver.dm b/code/modules/projectiles/guns/ballistic/revolver.dm
index 289b43a669..a5ed45dd48 100644
--- a/code/modules/projectiles/guns/ballistic/revolver.dm
+++ b/code/modules/projectiles/guns/ballistic/revolver.dm
@@ -84,6 +84,11 @@
. = ..()
. += "[get_ammo(0,0)] of those are live rounds."
+/obj/item/gun/ballistic/revolver/syndicate
+ unique_reskin = list("Default" = "revolver",
+ "Silver" = "russianrevolver",
+ "Robust" = "revolvercit")
+
/obj/item/gun/ballistic/revolver/detective
name = "\improper .38 Mars Special"
desc = "A cheap Martian knock-off of a classic law enforcement firearm. Uses .38-special rounds."
@@ -486,4 +491,4 @@
for(var/i = 0, i < ratio, i++)
var/mutable_appearance/charge_bar = mutable_appearance(icon, "[initial(icon_state)]_charge", color = batt_color)
charge_bar.pixel_x = i
- . += charge_bar
\ No newline at end of file
+ . += charge_bar
diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm
index 43384280d4..99a0bedc4d 100644
--- a/code/modules/projectiles/projectile.dm
+++ b/code/modules/projectiles/projectile.dm
@@ -327,16 +327,18 @@
if(!trajectory)
return
var/turf/T = get_turf(A)
- if(check_ricochet(A) && A.handle_ricochet(src)) //if you can ricochet, attempt to ricochet off the object
- on_ricochet(A) //if allowed, use autoaim to ricochet into someone, otherwise default to ricocheting off the object from above
- var/datum/point/pcache = trajectory.copy_to()
- if(hitscan)
- store_hitscan_collision(pcache)
- decayedRange = max(0, decayedRange - reflect_range_decrease)
- ricochet_chance *= ricochet_decay_chance
- damage *= ricochet_decay_damage
- range = decayedRange
- return TRUE
+ if(check_ricochet_flag(A) && check_ricochet(A)) //if you can ricochet, attempt to ricochet off the object
+ ricochets++
+ if(A.handle_ricochet(src))
+ on_ricochet(A) //if allowed, use autoaim to ricochet into someone, otherwise default to ricocheting off the object from above
+ var/datum/point/pcache = trajectory.copy_to()
+ if(hitscan)
+ store_hitscan_collision(pcache)
+ decayedRange = max(0, decayedRange - reflect_range_decrease)
+ ricochet_chance *= ricochet_decay_chance
+ damage *= ricochet_decay_damage
+ range = decayedRange
+ return TRUE
var/distance = get_dist(T, starting) // Get the distance between the turf shot from and the mob we hit and use that for the calculations.
if(def_zone && check_zone(def_zone) != BODY_ZONE_CHEST)
@@ -680,7 +682,8 @@
if(!ignore_source_check && firer)
var/mob/M = firer
if((target == firer) || ((target == firer.loc) && ismecha(firer.loc)) || (target in firer.buckled_mobs) || (istype(M) && (M.buckled == target)))
- return FALSE
+ if(!ricochets) //if it has ricocheted, it can hit the firer.
+ return FALSE
if(!ignore_loc && (loc != target.loc))
return FALSE
if(target in passthrough)
diff --git a/code/modules/reagents/chemistry/machinery/chem_dispenser.dm b/code/modules/reagents/chemistry/machinery/chem_dispenser.dm
index da33f935da..db16a10d1d 100644
--- a/code/modules/reagents/chemistry/machinery/chem_dispenser.dm
+++ b/code/modules/reagents/chemistry/machinery/chem_dispenser.dm
@@ -177,11 +177,10 @@
beaker = null
update_icon()
-/obj/machinery/chem_dispenser/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \
- datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state)
- ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+/obj/machinery/chem_dispenser/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
- ui = new(user, src, ui_key, "ChemDispenser", name, 565, 550, master_ui, state)
+ ui = new(user, src, "ChemDispenser", name)
if(user.hallucinating())
ui.set_autoupdate(FALSE) //to not ruin the immersion by constantly changing the fake chemicals
ui.open()
@@ -216,7 +215,7 @@
data["beakerTransferAmounts"] = null
data["beakerCurrentpH"] = null
- var/list/chemicals = list()
+ var/chemicals[0]
var/is_hallucinating = FALSE
if(user.hallucinating())
is_hallucinating = TRUE
@@ -275,7 +274,7 @@
. = TRUE
if("eject")
replace_beaker(usr)
- . = TRUE //no afterattack
+ . = TRUE
if("dispense_recipe")
if(!is_operational() || QDELETED(cell))
return
@@ -326,9 +325,9 @@
for(var/reagent in recording_recipe)
var/reagent_id = GLOB.name2reagent[translate_legacy_chem_id(reagent)]
if(!dispensable_reagents.Find(reagent_id))
- visible_message("[src] buzzes.", "You hear a faint buzz.")
+ visible_message("[src] buzzes.", "You hear a faint buzz.")
to_chat(usr, "[src] cannot find [reagent]!")
- playsound(src, 'sound/machines/buzz-two.ogg', 50, 1)
+ playsound(src, 'sound/machines/buzz-two.ogg', 50, TRUE)
return
saved_recipes[name] = recording_recipe
recording_recipe = null
diff --git a/code/modules/reagents/chemistry/machinery/chem_heater.dm b/code/modules/reagents/chemistry/machinery/chem_heater.dm
index 5697a2385c..28f0b2366a 100644
--- a/code/modules/reagents/chemistry/machinery/chem_heater.dm
+++ b/code/modules/reagents/chemistry/machinery/chem_heater.dm
@@ -7,6 +7,7 @@
idle_power_usage = 40
resistance_flags = FIRE_PROOF | ACID_PROOF
circuit = /obj/item/circuitboard/machine/chem_heater
+
var/obj/item/reagent_containers/beaker = null
var/target_temperature = 300
var/heater_coefficient = 0.1
@@ -30,22 +31,20 @@
/obj/machinery/chem_heater/AltClick(mob/living/user)
. = ..()
- if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK))
+ if(!can_interact(user) || !user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK))
return
replace_beaker(user)
- return TRUE
/obj/machinery/chem_heater/proc/replace_beaker(mob/living/user, obj/item/reagent_containers/new_beaker)
+ if(!user)
+ return FALSE
if(beaker)
- beaker.forceMove(drop_location())
- if(user && Adjacent(user) && user.can_hold_items())
- user.put_in_hands(beaker)
+ user.put_in_hands(beaker)
+ beaker = null
if(new_beaker)
beaker = new_beaker
- else
- beaker = null
- update_icon()
- return TRUE
+ update_icon()
+ return TRUE
/obj/machinery/chem_heater/RefreshParts()
heater_coefficient = 0.1
@@ -63,6 +62,7 @@
return
if(on)
if(beaker && beaker.reagents.total_volume)
+ //keep constant with the chemical acclimator please
beaker.reagents.adjust_thermal_energy((target_temperature - beaker.reagents.chem_temp) * heater_coefficient * SPECIFIC_HEAT_DEFAULT * beaker.reagents.total_volume)
beaker.reagents.handle_reactions()
@@ -83,27 +83,16 @@
updateUsrDialog()
update_icon()
return
-
- if(beaker)
- if(istype(I, /obj/item/reagent_containers/dropper))
- var/obj/item/reagent_containers/dropper/D = I
- D.afterattack(beaker, user, 1)
-
- if(istype(I, /obj/item/reagent_containers/syringe))
- var/obj/item/reagent_containers/syringe/S = I
- S.afterattack(beaker, user, 1)
-
return ..()
/obj/machinery/chem_heater/on_deconstruction()
replace_beaker()
return ..()
-/obj/machinery/chem_heater/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/chem_heater/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
- ui = new(user, src, ui_key, "ChemHeater", name, 300, 400, master_ui, state)
+ ui = new(user, src, "ChemHeater", name)
ui.open()
/obj/machinery/chem_heater/ui_data()
@@ -140,14 +129,7 @@
. = TRUE
if("temperature")
var/target = params["target"]
- var/adjust = text2num(params["adjust"])
- if(target == "input")
- target = input("New target temperature:", name, target_temperature) as num|null
- if(!isnull(target) && !..())
- . = TRUE
- else if(adjust)
- target = target_temperature + adjust
- else if(text2num(target) != null)
+ if(text2num(target) != null)
target = text2num(target)
. = TRUE
if(.)
diff --git a/code/modules/reagents/chemistry/machinery/chem_master.dm b/code/modules/reagents/chemistry/machinery/chem_master.dm
index dd9dd7c0bf..207325e1b3 100644
--- a/code/modules/reagents/chemistry/machinery/chem_master.dm
+++ b/code/modules/reagents/chemistry/machinery/chem_master.dm
@@ -12,6 +12,7 @@
idle_power_usage = 20
resistance_flags = FIRE_PROOF | ACID_PROOF
circuit = /obj/item/circuitboard/machine/chem_master
+
var/obj/item/reagent_containers/beaker = null
var/obj/item/storage/pill_bottle/bottle = null
var/mode = 1
@@ -154,19 +155,15 @@
bottle?.forceMove(A)
return ..()
-//Insert our custom spritesheet css link into the html
-/obj/machinery/chem_master/ui_base_html(html)
- var/datum/asset/spritesheet/assets = get_asset_datum(/datum/asset/spritesheet/simple/pills)
- . = replacetext(html, "", assets.css_tag())
+/obj/machinery/chem_master/ui_assets(mob/user)
+ return list(
+ get_asset_datum(/datum/asset/spritesheet/simple/pills),
+ )
-/obj/machinery/chem_master/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/chem_master/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/simple/pills)
- assets.send(user)
-
- ui = new(user, src, ui_key, "ChemMaster", name, 520, 550, master_ui, state)
+ ui = new(user, src, "ChemMaster", name)
ui.open()
/obj/machinery/chem_master/ui_data(mob/user)
@@ -183,8 +180,8 @@
data["isPillBottleLoaded"] = bottle ? 1 : 0
if(bottle)
var/datum/component/storage/STRB = bottle.GetComponent(/datum/component/storage)
- data["pillBotContent"] = bottle.contents.len
- data["pillBotMaxContent"] = STRB.max_items
+ data["pillBottleCurrentAmount"] = bottle.contents.len
+ data["pillBottleMaxAmount"] = STRB.max_items
var/beakerContents[0]
if(beaker)
@@ -206,213 +203,219 @@
if(..())
return
- switch(action)
- if("eject")
- replace_beaker(usr)
- . = TRUE
+ if(action == "eject")
+ replace_beaker(usr)
+ return TRUE
- if("ejectPillBottle")
- replace_pillbottle(usr)
- . = TRUE
-
- if("transfer")
- if(!beaker)
- return FALSE
- var/reagent = GLOB.name2reagent[params["id"]]
- var/amount = text2num(params["amount"])
- var/to_container = params["to"]
- // Custom amount
- if (amount == -1)
- amount = text2num(input(
- "Enter the amount you want to transfer:",
- name, ""))
- if (amount == null || amount <= 0)
- return FALSE
- if (to_container == "buffer")
- end_fermi_reaction()
- beaker.reagents.trans_id_to(src, reagent, amount)
- return TRUE
- if (to_container == "beaker" && mode)
- end_fermi_reaction()
- reagents.trans_id_to(beaker, reagent, amount)
- return TRUE
- if (to_container == "beaker" && !mode)
- end_fermi_reaction()
- reagents.remove_reagent(reagent, amount)
- return TRUE
+ if(action == "ejectPillBottle")
+ if(!bottle)
return FALSE
+ bottle.forceMove(drop_location())
+ adjust_item_drop_location(bottle)
+ bottle = null
+ return TRUE
- if("toggleMode")
- mode = !mode
- . = TRUE
+ if(action == "transfer")
+ if(!beaker)
+ return FALSE
+ var/reagent = GLOB.name2reagent[params["id"]]
+ var/amount = text2num(params["amount"])
+ var/to_container = params["to"]
+ // Custom amount
+ if (amount == -1)
+ amount = text2num(input(
+ "Enter the amount you want to transfer:",
+ name, ""))
+ if (amount == null || amount <= 0)
+ return FALSE
+ if (to_container == "buffer")
+ end_fermi_reaction()
+ beaker.reagents.trans_id_to(src, reagent, amount)
+ return TRUE
+ if (to_container == "beaker" && mode)
+ end_fermi_reaction()
+ reagents.trans_id_to(beaker, reagent, amount)
+ return TRUE
+ if (to_container == "beaker" && !mode)
+ end_fermi_reaction()
+ reagents.remove_reagent(reagent, amount)
+ return TRUE
+ return FALSE
- if("pillStyle")
- var/id = text2num(params["id"])
- chosenPillStyle = id
+ if(action == "toggleMode")
+ mode = !mode
+ return TRUE
+
+ if(action == "pillStyle")
+ var/id = text2num(params["id"])
+ chosenPillStyle = id
+ return TRUE
+
+ if(action == "create")
+ if(reagents.total_volume == 0)
+ return FALSE
+ var/item_type = params["type"]
+ // Get amount of items
+ var/amount = text2num(params["amount"])
+ if(amount == null)
+ amount = text2num(input(usr,
+ "Max 10. Buffer content will be split evenly.",
+ "How many to make?", 1))
+ amount = clamp(round(amount), 0, 10)
+ if (amount <= 0)
+ return FALSE
+ // Get units per item
+ var/vol_each = text2num(params["volume"])
+ var/vol_each_text = params["volume"]
+ var/vol_each_max = reagents.total_volume / amount
+ if (item_type == "pill")
+ vol_each_max = min(50, vol_each_max)
+ else if (item_type == "patch")
+ vol_each_max = min(40, vol_each_max)
+ else if (item_type == "bottle")
+ vol_each_max = min(30, vol_each_max)
+ else if (item_type == "condimentPack")
+ vol_each_max = min(10, vol_each_max)
+ else if (item_type == "condimentBottle")
+ vol_each_max = min(50, vol_each_max)
+ else if (item_type == "hypoVial")
+ vol_each_max = min(60, vol_each_max)
+ else if (item_type == "smartDart")
+ vol_each_max = min(20, vol_each_max)
+ else
+ return FALSE
+ if(vol_each_text == "auto")
+ vol_each = vol_each_max
+ if(vol_each == null)
+ vol_each = text2num(input(usr,
+ "Maximum [vol_each_max] units per item.",
+ "How many units to fill?",
+ vol_each_max))
+ vol_each = clamp(vol_each, 0, vol_each_max)
+ if(vol_each <= 0)
+ return FALSE
+ // Get item name
+ var/name = params["name"]
+ var/name_has_units = item_type == "pill" || item_type == "patch"
+ if(!name)
+ var/name_default = reagents.get_master_reagent_name()
+ if (name_has_units)
+ name_default += " ([vol_each]u)"
+ name = stripped_input(usr,
+ "Name:",
+ "Give it a name!",
+ name_default,
+ MAX_NAME_LEN)
+ if(!name || !reagents.total_volume || !src || QDELETED(src) || !usr.canUseTopic(src, !issilicon(usr)))
+ return FALSE
+ // Start filling
+ if(item_type == "pill")
+ var/obj/item/reagent_containers/pill/P
+ var/target_loc = drop_location()
+ var/drop_threshold = INFINITY
+ if(bottle)
+ var/datum/component/storage/STRB = bottle.GetComponent(
+ /datum/component/storage)
+ if(STRB)
+ drop_threshold = STRB.max_items - bottle.contents.len
+ for(var/i = 0; i < amount; i++)
+ if(i < drop_threshold)
+ P = new/obj/item/reagent_containers/pill(target_loc)
+ else
+ P = new/obj/item/reagent_containers/pill(drop_location())
+ P.name = trim("[name] pill")
+ if(chosenPillStyle == RANDOM_PILL_STYLE)
+ P.icon_state ="pill[rand(1,21)]"
+ else
+ P.icon_state = "pill[chosenPillStyle]"
+ if(P.icon_state == "pill4")
+ P.desc = "A tablet or capsule, but not just any, a red one, one taken by the ones not scared of knowledge, freedom, uncertainty and the brutal truths of reality."
+ adjust_item_drop_location(P)
+ reagents.trans_to(P, vol_each)//, transfered_by = usr)
+ return TRUE
+ if(item_type == "patch")
+ var/obj/item/reagent_containers/pill/patch/P
+ for(var/i = 0; i < amount; i++)
+ P = new/obj/item/reagent_containers/pill/patch(drop_location())
+ P.name = trim("[name] patch")
+ adjust_item_drop_location(P)
+ reagents.trans_to(P, vol_each)//, transfered_by = usr)
+ return TRUE
+ if(item_type == "bottle")
+ var/obj/item/reagent_containers/glass/bottle/P
+ for(var/i = 0; i < amount; i++)
+ P = new/obj/item/reagent_containers/glass/bottle(drop_location())
+ P.name = trim("[name] bottle")
+ adjust_item_drop_location(P)
+ reagents.trans_to(P, vol_each)//, transfered_by = usr)
+ return TRUE
+ if(item_type == "condimentPack")
+ var/obj/item/reagent_containers/food/condiment/pack/P
+ for(var/i = 0; i < amount; i++)
+ P = new/obj/item/reagent_containers/food/condiment/pack(drop_location())
+ P.originalname = name
+ P.name = trim("[name] pack")
+ P.desc = "A small condiment pack. The label says it contains [name]."
+ reagents.trans_to(P, vol_each)//, transfered_by = usr)
+ return TRUE
+ if(item_type == "condimentBottle")
+ var/obj/item/reagent_containers/food/condiment/P
+ for(var/i = 0; i < amount; i++)
+ P = new/obj/item/reagent_containers/food/condiment(drop_location())
+ P.originalname = name
+ P.name = trim("[name] bottle")
+ reagents.trans_to(P, vol_each)//, transfered_by = usr)
+ return TRUE
+ if(item_type == "hypoVial")
+ var/obj/item/reagent_containers/glass/bottle/vial/small/P
+ for(var/i = 0; i < amount; i++)
+ P = new/obj/item/reagent_containers/glass/bottle/vial/small(drop_location())
+ P.name = trim("[name] hypovial")
+ adjust_item_drop_location(P)
+ reagents.trans_to(P, vol_each)//, transfered_by = usr)
+ return TRUE
+ if(item_type == "smartDart")
+ var/obj/item/reagent_containers/syringe/dart/P
+ for(var/i = 0; i < amount; i++)
+ P = new /obj/item/reagent_containers/syringe/dart(drop_location())
+ P.name = trim("[name] SmartDart")
+ adjust_item_drop_location(P)
+ reagents.trans_to(P, vol_each)//, transfered_by = usr)
+ P.mode=!mode
+ P.update_icon()
+ return TRUE
+ return FALSE
+
+ if(action == "analyze")
+ // var/datum/reagent/R = GLOB.name2reagent[params["id"]]
+ var/reagent = GLOB.name2reagent[params["id"]]
+ var/datum/reagent/R = GLOB.chemical_reagents_list[reagent]
+ if(R)
+ var/state = "Unknown"
+ if(initial(R.reagent_state) == 1)
+ state = "Solid"
+ else if(initial(R.reagent_state) == 2)
+ state = "Liquid"
+ else if(initial(R.reagent_state) == 3)
+ state = "Gas"
+ var/const/P = 3 //The number of seconds between life ticks
+ var/T = initial(R.metabolization_rate) * (60 / P)
+ if(istype(R, /datum/reagent/fermi))
+ fermianalyze = TRUE
+ var/datum/chemical_reaction/Rcr = get_chemical_reaction(reagent)
+ var/pHpeakCache = (Rcr.OptimalpHMin + Rcr.OptimalpHMax)/2
+ analyzeVars = list("name" = initial(R.name), "state" = state, "color" = initial(R.color), "description" = initial(R.description), "metaRate" = T, "overD" = initial(R.overdose_threshold), "addicD" = initial(R.addiction_threshold), "purityF" = R.purity, "inverseRatioF" = initial(R.inverse_chem_val), "purityE" = initial(Rcr.PurityMin), "minTemp" = initial(Rcr.OptimalTempMin), "maxTemp" = initial(Rcr.OptimalTempMax), "eTemp" = initial(Rcr.ExplodeTemp), "pHpeak" = pHpeakCache)
+ else
+ fermianalyze = FALSE
+ analyzeVars = list("name" = initial(R.name), "state" = state, "color" = initial(R.color), "description" = initial(R.description), "metaRate" = T, "overD" = initial(R.overdose_threshold), "addicD" = initial(R.addiction_threshold))
+ screen = "analyze"
return TRUE
- if("create")
- if(reagents.total_volume == 0)
- return FALSE
- var/item_type = params["type"]
- // Get amount of items
- var/amount = text2num(params["amount"])
- if(amount == null)
- amount = text2num(input(usr,
- "Max 20. Buffer content will be split evenly.",
- "How many to make?", 1))
- amount = clamp(round(amount), 0, 20)
- if (amount <= 0)
- return FALSE
- // Get units per item
- var/vol_each = text2num(params["volume"])
- var/vol_each_text = params["volume"]
- var/vol_each_max = reagents.total_volume / amount
- if (item_type == "pill")
- vol_each_max = min(50, vol_each_max)
- else if (item_type == "patch")
- vol_each_max = min(40, vol_each_max)
- else if (item_type == "bottle")
- vol_each_max = min(30, vol_each_max)
- else if (item_type == "condimentPack")
- vol_each_max = min(10, vol_each_max)
- else if (item_type == "condimentBottle")
- vol_each_max = min(50, vol_each_max)
- else if (item_type == "hypoVial")
- vol_each_max = min(60, vol_each_max)
- else if (item_type == "smartDart")
- vol_each_max = min(20, vol_each_max)
- else
- return FALSE
- if(vol_each_text == "auto")
- vol_each = vol_each_max
- if(vol_each == null)
- vol_each = text2num(input(usr,
- "Maximum [vol_each_max] units per item.",
- "How many units to fill?",
- vol_each_max))
- vol_each = clamp(vol_each, 0, vol_each_max)
- if(vol_each <= 0)
- return FALSE
- // Get item name
- var/name = params["name"]
- var/name_has_units = item_type == "pill" || item_type == "patch"
- if(!name)
- var/name_default = reagents.get_master_reagent_name()
- if (name_has_units)
- name_default += " ([vol_each]u)"
- name = stripped_input(usr,
- "Name:",
- "Give it a name!",
- name_default,
- MAX_NAME_LEN)
- if(!name || !reagents.total_volume || !src || QDELETED(src) || !usr.canUseTopic(src, !issilicon(usr)))
- return FALSE
- // Start filling
- if(item_type == "pill")
- var/obj/item/reagent_containers/pill/P
- var/target_loc = drop_location()
- var/drop_threshold = INFINITY
- if(bottle)
- var/datum/component/storage/STRB = bottle.GetComponent(
- /datum/component/storage)
- if(STRB)
- drop_threshold = STRB.max_items - bottle.contents.len
- for(var/i = 0; i < amount; i++)
- if(i < drop_threshold)
- P = new/obj/item/reagent_containers/pill(target_loc)
- else
- P = new/obj/item/reagent_containers/pill(drop_location())
- P.name = trim("[name] pill")
- if(chosenPillStyle == RANDOM_PILL_STYLE)
- P.icon_state ="pill[rand(1,21)]"
- else
- P.icon_state = "pill[chosenPillStyle]"
- if(P.icon_state == "pill4")
- P.desc = "A tablet or capsule, but not just any, a red one, one taken by the ones not scared of knowledge, freedom, uncertainty and the brutal truths of reality."
- adjust_item_drop_location(P)
- reagents.trans_to(P, vol_each)
- return TRUE
- if(item_type == "patch")
- var/obj/item/reagent_containers/pill/patch/P
- for(var/i = 0; i < amount; i++)
- P = new/obj/item/reagent_containers/pill/patch(drop_location())
- P.name = trim("[name] patch")
- adjust_item_drop_location(P)
- reagents.trans_to(P, vol_each)
- return TRUE
- if(item_type == "bottle")
- var/obj/item/reagent_containers/glass/bottle/P
- for(var/i = 0; i < amount; i++)
- P = new/obj/item/reagent_containers/glass/bottle(drop_location())
- P.name = trim("[name] bottle")
- adjust_item_drop_location(P)
- reagents.trans_to(P, vol_each)
- return TRUE
- if(item_type == "condimentPack")
- var/obj/item/reagent_containers/food/condiment/pack/P
- for(var/i = 0; i < amount; i++)
- P = new/obj/item/reagent_containers/food/condiment/pack(drop_location())
- P.originalname = name
- P.name = trim("[name] pack")
- P.desc = "A small condiment pack. The label says it contains [name]."
- reagents.trans_to(P, vol_each)
- return TRUE
- if(item_type == "condimentBottle")
- var/obj/item/reagent_containers/food/condiment/P
- for(var/i = 0; i < amount; i++)
- P = new/obj/item/reagent_containers/food/condiment(drop_location())
- P.originalname = name
- P.name = trim("[name] bottle")
- reagents.trans_to(P, vol_each)
- return TRUE
- if(item_type == "hypoVial")
- var/obj/item/reagent_containers/glass/bottle/vial/small/P
- for(var/i = 0; i < amount; i++)
- P = new/obj/item/reagent_containers/glass/bottle/vial/small(drop_location())
- P.name = trim("[name] hypovial")
- adjust_item_drop_location(P)
- reagents.trans_to(P, vol_each)
- return TRUE
- if(item_type == "smartDart")
- var/obj/item/reagent_containers/syringe/dart/P
- for(var/i = 0; i < amount; i++)
- P = new /obj/item/reagent_containers/syringe/dart(drop_location())
- P.name = trim("[name] SmartDart")
- adjust_item_drop_location(P)
- reagents.trans_to(P, vol_each)
- P.mode=!mode
- P.update_icon()
- return TRUE
- return FALSE
+ if(action == "goScreen")
+ screen = params["screen"]
+ return TRUE
- if("analyze")
- var/reagent = GLOB.name2reagent[params["id"]]
- var/datum/reagent/R = GLOB.chemical_reagents_list[reagent]
- if(R)
- var/state = "Unknown"
- if(initial(R.reagent_state) == 1)
- state = "Solid"
- else if(initial(R.reagent_state) == 2)
- state = "Liquid"
- else if(initial(R.reagent_state) == 3)
- state = "Gas"
- var/const/P = 3 //The number of seconds between life ticks
- var/T = initial(R.metabolization_rate) * (60 / P)
- if(istype(R, /datum/reagent/fermi))
- fermianalyze = TRUE
- var/datum/chemical_reaction/Rcr = get_chemical_reaction(reagent)
- var/pHpeakCache = (Rcr.OptimalpHMin + Rcr.OptimalpHMax)/2
- analyzeVars = list("name" = initial(R.name), "state" = state, "color" = initial(R.color), "description" = initial(R.description), "metaRate" = T, "overD" = initial(R.overdose_threshold), "addicD" = initial(R.addiction_threshold), "purityF" = R.purity, "inverseRatioF" = initial(R.inverse_chem_val), "purityE" = initial(Rcr.PurityMin), "minTemp" = initial(Rcr.OptimalTempMin), "maxTemp" = initial(Rcr.OptimalTempMax), "eTemp" = initial(Rcr.ExplodeTemp), "pHpeak" = pHpeakCache)
- else
- fermianalyze = FALSE
- analyzeVars = list("name" = initial(R.name), "state" = state, "color" = initial(R.color), "description" = initial(R.description), "metaRate" = T, "overD" = initial(R.overdose_threshold), "addicD" = initial(R.addiction_threshold))
- screen = "analyze"
- return TRUE
-
- if("goScreen")
- screen = params["screen"]
- . = TRUE
+ return FALSE
diff --git a/code/modules/reagents/chemistry/machinery/chem_synthesizer.dm b/code/modules/reagents/chemistry/machinery/chem_synthesizer.dm
index 66c2616972..489f9dd179 100644
--- a/code/modules/reagents/chemistry/machinery/chem_synthesizer.dm
+++ b/code/modules/reagents/chemistry/machinery/chem_synthesizer.dm
@@ -12,11 +12,10 @@
"tricord" = /datum/reagent/medicine/tricordrazine
)
-/obj/machinery/chem_dispenser/chem_synthesizer/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/chem_dispenser/chem_synthesizer/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
- ui = new(user, src, ui_key, "ChemDebugSynthesizer", name, 390, 330, master_ui, state)
+ ui = new(user, src, "ChemDebugSynthesizer", name)
ui.open()
/obj/machinery/chem_dispenser/chem_synthesizer/ui_act(action, params)
@@ -31,7 +30,11 @@
beaker = null
. = TRUE
if("input")
- var/input_reagent = replacetext(lowertext(input("Enter the name of any reagent", "Input") as text), " ", "") //95% of the time, the reagent types is a lowercase, no spaces / underscored version of the name
+ var/input_reagent = replacetext(lowertext(input("Enter the name of any reagent", "Input") as text|null), " ", "") //95% of the time, the reagent id is a lowercase/no spaces version of the name
+
+ if (isnull(input_reagent))
+ return
+
if(shortcuts[input_reagent])
input_reagent = shortcuts[input_reagent]
else
@@ -51,7 +54,7 @@
beaker = new /obj/item/reagent_containers/glass/beaker/bluespace(src)
visible_message("[src] dispenses a bluespace beaker.")
if("amount")
- var/input = input("Units to dispense", "Units") as num|null
+ var/input = text2num(params["amount"])
if(input)
amount = input
update_icon()
diff --git a/code/modules/reagents/chemistry/machinery/pandemic.dm b/code/modules/reagents/chemistry/machinery/pandemic.dm
index 4a4bbdb546..36e102be72 100644
--- a/code/modules/reagents/chemistry/machinery/pandemic.dm
+++ b/code/modules/reagents/chemistry/machinery/pandemic.dm
@@ -7,10 +7,11 @@
density = TRUE
icon = 'icons/obj/chemical.dmi'
icon_state = "mixer0"
- circuit = /obj/item/circuitboard/computer/pandemic
use_power = TRUE
idle_power_usage = 20
resistance_flags = ACID_PROOF
+ circuit = /obj/item/circuitboard/computer/pandemic
+
var/wait
var/datum/symptom/selected_symptom
var/obj/item/reagent_containers/beaker
@@ -23,11 +24,27 @@
QDEL_NULL(beaker)
return ..()
-/obj/machinery/computer/pandemic/handle_atom_del(atom/A)
+/obj/machinery/computer/pandemic/examine(mob/user)
. = ..()
+ if(beaker)
+ var/is_close
+ if(Adjacent(user)) //don't reveal exactly what's inside unless they're close enough to see the UI anyway.
+ . += "It contains \a [beaker]."
+ is_close = TRUE
+ else
+ . += "It has a beaker inside it."
+ . += "Alt-click to eject [is_close ? beaker : "the beaker"]."
+
+/obj/machinery/computer/pandemic/AltClick(mob/user)
+ . = ..()
+ if(user.canUseTopic(src, BE_CLOSE))
+ eject_beaker()
+
+/obj/machinery/computer/pandemic/handle_atom_del(atom/A)
if(A == beaker)
beaker = null
update_icon()
+ return ..()
/obj/machinery/computer/pandemic/proc/get_by_index(thing, index)
if(!beaker || !beaker.reagents)
@@ -107,7 +124,7 @@
/obj/machinery/computer/pandemic/proc/reset_replicator_cooldown()
wait = FALSE
update_icon()
- playsound(loc, 'sound/machines/ping.ogg', 30, 1)
+ playsound(src, 'sound/machines/ping.ogg', 30, TRUE)
/obj/machinery/computer/pandemic/update_icon_state()
if(stat & BROKEN)
@@ -117,13 +134,19 @@
/obj/machinery/computer/pandemic/update_overlays()
. = ..()
- if(!(stat & BROKEN) && wait)
+ if(wait)
. += "waitlight"
-/obj/machinery/computer/pandemic/ui_interact(mob/user, ui_key = "main", datum/tgui/ui, force_open = FALSE, datum/tgui/master_ui, datum/ui_state/state = GLOB.default_state)
- ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+/obj/machinery/computer/pandemic/proc/eject_beaker()
+ if(beaker)
+ beaker.forceMove(drop_location())
+ beaker = null
+ update_icon()
+
+/obj/machinery/computer/pandemic/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
- ui = new(user, src, ui_key, "Pandemic", name, 520, 550, master_ui, state)
+ ui = new(user, src, "Pandemic", name)
ui.open()
/obj/machinery/computer/pandemic/ui_data(mob/user)
@@ -135,9 +158,9 @@
var/datum/reagent/blood/B = locate() in beaker.reagents.reagent_list
if(B)
data["has_blood"] = TRUE
- data[/datum/reagent/blood] = list()
- data[/datum/reagent/blood]["dna"] = B.data["blood_DNA"] || "none"
- data[/datum/reagent/blood]["type"] = B.data["blood_type"] || "none"
+ data["blood"] = list() //wha why the fuck are we sending pathtypes to tgui frontend?
+ data["blood"]["dna"] = B.data["blood_DNA"] || "none"
+ data["blood"]["type"] = B.data["blood_type"] || "none"
data["viruses"] = get_viruses_data(B)
data["resistances"] = get_resistance_data(B)
else
@@ -153,7 +176,7 @@
return
switch(action)
if("eject_beaker")
- replace_beaker(usr)
+ eject_beaker()
. = TRUE
if("empty_beaker")
if(beaker)
@@ -162,7 +185,7 @@
if("empty_eject_beaker")
if(beaker)
beaker.reagents.clear_reagents()
- replace_beaker(usr)
+ eject_beaker()
. = TRUE
if("rename_disease")
var/id = get_virus_id_by_index(text2num(params["index"]))
@@ -170,75 +193,62 @@
if(!A.mutable)
return
if(A)
- var/new_name = sanitize_name(html_encode(trim(params["name"], 50)))
+ var/new_name = sanitize_name(html_encode(trim(params["name"], 50)))//, allow_numbers = TRUE)
if(!new_name || ..())
return
A.AssignName(new_name)
. = TRUE
if("create_culture_bottle")
+ if (wait)
+ return
var/id = get_virus_id_by_index(text2num(params["index"]))
var/datum/disease/advance/A = SSdisease.archive_diseases[id]
if(!istype(A) || !A.mutable)
to_chat(usr, "ERROR: Cannot replicate virus strain.")
return
- wait = TRUE
- addtimer(CALLBACK(src, .proc/reset_replicator_cooldown), 50)
A = A.Copy()
- var/list/data = list("blood_DNA" = "UNKNOWN DNA", "blood_type" = "SY", "viruses" = list(A))
+ var/list/data = list("viruses" = list(A))
var/obj/item/reagent_containers/glass/bottle/B = new(drop_location())
B.name = "[A.name] culture bottle"
B.desc = "A small bottle. Contains [A.agent] culture in synthblood medium."
B.reagents.add_reagent(/datum/reagent/blood/synthetics, 10, data)
+ wait = TRUE
update_icon()
var/turf/source_turf = get_turf(src)
log_virus("A culture bottle was printed for the virus [A.admin_details()] at [loc_name(source_turf)] by [key_name(usr)]")
-
+ addtimer(CALLBACK(src, .proc/reset_replicator_cooldown), 50)
. = TRUE
if("create_vaccine_bottle")
- wait = TRUE
- addtimer(CALLBACK(src, .proc/reset_replicator_cooldown), 400)
+ if (wait)
+ return
var/id = params["index"]
var/datum/disease/D = SSdisease.archive_diseases[id]
var/obj/item/reagent_containers/glass/bottle/B = new(drop_location())
B.name = "[D.name] vaccine bottle"
B.reagents.add_reagent(/datum/reagent/vaccine, 15, list(id))
-
+ wait = TRUE
update_icon()
-
+ addtimer(CALLBACK(src, .proc/reset_replicator_cooldown), 200)
. = TRUE
+
/obj/machinery/computer/pandemic/attackby(obj/item/I, mob/user, params)
if(istype(I, /obj/item/reagent_containers) && !(I.item_flags & ABSTRACT) && I.is_open_container())
. = TRUE //no afterattack
if(stat & (NOPOWER|BROKEN))
return
- var/obj/item/reagent_containers/B = I
- if(!user.transferItemToLoc(B, src))
+ if(beaker)
+ to_chat(user, "A container is already loaded into [src]!")
return
- replace_beaker(user, B)
+ if(!user.transferItemToLoc(I, src))
+ return
+
+ beaker = I
to_chat(user, "You insert [I] into [src].")
+ update_icon()
else
return ..()
-/obj/machinery/computer/pandemic/AltClick(mob/living/user)
- . = ..()
- if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK))
- return
- replace_beaker(user)
- return TRUE
-
-/obj/machinery/computer/pandemic/proc/replace_beaker(mob/living/user, obj/item/reagent_containers/new_beaker)
- if(beaker)
- if(user && Adjacent(user) && user.can_hold_items())
- if(!user.put_in_hands(beaker))
- beaker.forceMove(drop_location())
- if(new_beaker)
- beaker = new_beaker
- else
- beaker = null
- update_icon()
- return TRUE
-
/obj/machinery/computer/pandemic/on_deconstruction()
- replace_beaker(usr)
+ eject_beaker()
. = ..()
diff --git a/code/modules/reagents/chemistry/machinery/smoke_machine.dm b/code/modules/reagents/chemistry/machinery/smoke_machine.dm
index cac90d1c14..a539897c9d 100644
--- a/code/modules/reagents/chemistry/machinery/smoke_machine.dm
+++ b/code/modules/reagents/chemistry/machinery/smoke_machine.dm
@@ -7,6 +7,7 @@
icon_state = "smoke0"
density = TRUE
circuit = /obj/item/circuitboard/machine/smoke_machine
+
var/efficiency = 10
var/on = FALSE
var/cooldown = 0
@@ -31,6 +32,7 @@
/obj/machinery/smoke_machine/Initialize()
. = ..()
create_reagents(REAGENTS_BASE_VOLUME)
+ // AddComponent(/datum/component/plumbing/simple_demand)
for(var/obj/item/stock_parts/matter_bin/B in component_parts)
reagents.maximum_volume += REAGENTS_BASE_VOLUME * B.rating
@@ -81,10 +83,9 @@
add_fingerprint(user)
if(istype(I, /obj/item/reagent_containers) && I.is_open_container())
var/obj/item/reagent_containers/RC = I
- var/units = RC.reagents.trans_to(src, RC.amount_per_transfer_from_this)
+ var/units = RC.reagents.trans_to(src, RC.amount_per_transfer_from_this) //, transfered_by = user)
if(units)
to_chat(user, "You transfer [units] units of the solution to [src].")
- log_combat(usr, src, "has added [english_list(RC.reagents.reagent_list)] to [src]")
return
if(default_unfasten_wrench(user, I, 40))
on = FALSE
@@ -100,11 +101,10 @@
reagents.clear_reagents()
return ..()
-/obj/machinery/smoke_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/smoke_machine/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
- ui = new(user, src, ui_key, "SmokeMachine", name, 350, 350, master_ui, state)
+ ui = new(user, src, "SmokeMachine", name)
ui.open()
/obj/machinery/smoke_machine/ui_data(mob/user)
diff --git a/code/modules/reagents/chemistry/reagents/drink_reagents.dm b/code/modules/reagents/chemistry/reagents/drink_reagents.dm
index 103061088e..e1433eb64e 100644
--- a/code/modules/reagents/chemistry/reagents/drink_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/drink_reagents.dm
@@ -1044,12 +1044,6 @@
M.emote("nya")
if(prob(20))
to_chat(M, "[pick("Headpats feel nice.", "Backrubs would be nice.", "Mew")]")
- if(ishuman(M))
- var/mob/living/carbon/human/H = M
- var/list/adjusted = H.adjust_arousal(5,aphro = TRUE)
- for(var/g in adjusted)
- var/obj/item/organ/genital/G = g
- to_chat(M, "You feel like playing with your [G.name]!")
..()
/datum/reagent/consumable/monkey_energy
diff --git a/code/modules/reagents/chemistry/reagents/drug_reagents.dm b/code/modules/reagents/chemistry/reagents/drug_reagents.dm
index 96f2c04598..44b6e85f47 100644
--- a/code/modules/reagents/chemistry/reagents/drug_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/drug_reagents.dm
@@ -559,128 +559,3 @@
var/mob/living/carbon/C = M
if(!C.undergoing_cardiac_arrest())
C.set_heartattack(TRUE)
-
-//aphrodisiac & anaphrodisiac
-
-/datum/reagent/drug/aphrodisiac
- name = "Crocin"
- description = "Naturally found in the crocus and gardenia flowers, this drug acts as a natural and safe aphrodisiac."
- taste_description = "strawberries"
- color = "#FFADFF"//PINK, rgb(255, 173, 255)
- can_synth = FALSE
-
-/datum/reagent/drug/aphrodisiac/on_mob_life(mob/living/M)
- if(M && M.client?.prefs.arousable && !(M.client?.prefs.cit_toggles & NO_APHRO))
- if((prob(min(current_cycle/2,5))))
- M.emote(pick("moan","blush"))
- if(prob(min(current_cycle/4,10)))
- var/aroused_message = pick("You feel frisky.", "You're having trouble suppressing your urges.", "You feel in the mood.")
- to_chat(M, "[aroused_message]")
- if(ishuman(M))
- var/mob/living/carbon/human/H = M
- var/list/genits = H.adjust_arousal(current_cycle, aphro = TRUE) // redundant but should still be here
- for(var/g in genits)
- var/obj/item/organ/genital/G = g
- to_chat(M, "[G.arousal_verb]!")
- ..()
-
-/datum/reagent/drug/aphrodisiacplus
- name = "Hexacrocin"
- description = "Chemically condensed form of basic crocin. This aphrodisiac is extremely powerful and addictive in most animals.\
- Addiction withdrawals can cause brain damage and shortness of breath. Overdosage can lead to brain damage and a \
- permanent increase in libido (commonly referred to as 'bimbofication')."
- taste_description = "liquid desire"
- color = "#FF2BFF"//dark pink
- addiction_threshold = 20
- overdose_threshold = 20
- can_synth = FALSE
-
-/datum/reagent/drug/aphrodisiacplus/on_mob_life(mob/living/M)
- if(M && M.client?.prefs.arousable && !(M.client?.prefs.cit_toggles & NO_APHRO))
- if(prob(5))
- if(prob(current_cycle))
- M.say(pick("Hnnnnngghh...", "Ohh...", "Mmnnn..."))
- else
- M.emote(pick("moan","blush"))
- if(prob(5))
- var/aroused_message
- if(current_cycle>25)
- aroused_message = pick("You need to fuck someone!", "You're bursting with sexual tension!", "You can't get sex off your mind!")
- else
- aroused_message = pick("You feel a bit hot.", "You feel strong sexual urges.", "You feel in the mood.", "You're ready to go down on someone.")
- to_chat(M, "[aroused_message]")
- REMOVE_TRAIT(M,TRAIT_NEVERBONER,APHRO_TRAIT)
- if(ishuman(M))
- var/mob/living/carbon/human/H = M
- var/list/genits = H.adjust_arousal(100, aphro = TRUE) // redundant but should still be here
- for(var/g in genits)
- var/obj/item/organ/genital/G = g
- to_chat(M, "[G.arousal_verb]!")
- ..()
-
-/datum/reagent/drug/aphrodisiacplus/addiction_act_stage2(mob/living/M)
- if(prob(30))
- M.adjustOrganLoss(ORGAN_SLOT_BRAIN, 2)
- ..()
-/datum/reagent/drug/aphrodisiacplus/addiction_act_stage3(mob/living/M)
- if(prob(30))
- M.adjustOrganLoss(ORGAN_SLOT_BRAIN, 3)
-
- ..()
-/datum/reagent/drug/aphrodisiacplus/addiction_act_stage4(mob/living/M)
- if(prob(30))
- M.adjustOrganLoss(ORGAN_SLOT_BRAIN, 4)
- ..()
-
-/datum/reagent/drug/aphrodisiacplus/overdose_process(mob/living/M)
- if(M && M.client?.prefs.arousable && !(M.client?.prefs.cit_toggles & NO_APHRO) && prob(33))
- if(prob(5) && ishuman(M) && M.has_dna() && (M.client?.prefs.cit_toggles & BIMBOFICATION))
- if(!HAS_TRAIT(M,TRAIT_PERMABONER))
- to_chat(M, "Your libido is going haywire!")
- ADD_TRAIT(M,TRAIT_PERMABONER,APHRO_TRAIT)
- ..()
-
-/datum/reagent/drug/anaphrodisiac
- name = "Camphor"
- description = "Naturally found in some species of evergreen trees, camphor is a waxy substance. When injested by most animals, it acts as an anaphrodisiac\
- , reducing libido and calming them. Non-habit forming and not addictive."
- taste_description = "dull bitterness"
- taste_mult = 2
- color = "#D9D9D9"//rgb(217, 217, 217)
- reagent_state = SOLID
- can_synth = FALSE
-
-/datum/reagent/drug/anaphrodisiac/on_mob_life(mob/living/M)
- if(M && M.client?.prefs.arousable && prob(16))
- if(ishuman(M))
- var/mob/living/carbon/human/H = M
- var/list/genits = H.adjust_arousal(-100, aphro = TRUE)
- if(genits.len)
- to_chat(M, "You no longer feel aroused.")
- ..()
-
-/datum/reagent/drug/anaphrodisiacplus
- name = "Hexacamphor"
- description = "Chemically condensed camphor. Causes an extreme reduction in libido and a permanent one if overdosed. Non-addictive."
- taste_description = "tranquil celibacy"
- color = "#D9D9D9"//rgb(217, 217, 217)
- reagent_state = SOLID
- overdose_threshold = 20
- can_synth = FALSE
-
-/datum/reagent/drug/anaphrodisiacplus/on_mob_life(mob/living/M)
- if(M && M.client?.prefs.arousable)
- REMOVE_TRAIT(M,TRAIT_PERMABONER,APHRO_TRAIT)
- if(ishuman(M))
- var/mob/living/carbon/human/H = M
- var/list/genits = H.adjust_arousal(-100, aphro = TRUE)
- if(genits.len)
- to_chat(M, "You no longer feel aroused.")
-
- ..()
-
-/datum/reagent/drug/anaphrodisiacplus/overdose_process(mob/living/M)
- if(M && M.client?.prefs.arousable && prob(5))
- to_chat(M, "You feel like you'll never feel aroused again...")
- ADD_TRAIT(M,TRAIT_NEVERBONER,APHRO_TRAIT)
- ..()
diff --git a/code/modules/reagents/chemistry/reagents/food_reagents.dm b/code/modules/reagents/chemistry/reagents/food_reagents.dm
index d449fa310c..a6e78ae98c 100644
--- a/code/modules/reagents/chemistry/reagents/food_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/food_reagents.dm
@@ -768,7 +768,6 @@
color = "#97ee63"
taste_description = "pure electricity"
-/* //We don't have ethereals here, so I'll just comment it out.
/datum/reagent/consumable/liquidelectricity/reaction_mob(mob/living/M, method=TOUCH, reac_volume) //can't be on life because of the way blood works.
if((method == INGEST || method == INJECT || method == PATCH) && iscarbon(M))
@@ -776,10 +775,9 @@
var/obj/item/organ/stomach/ethereal/stomach = C.getorganslot(ORGAN_SLOT_STOMACH)
if(istype(stomach))
stomach.adjust_charge(reac_volume * REM)
-*/
/datum/reagent/consumable/liquidelectricity/on_mob_life(mob/living/carbon/M)
- if(prob(25)) // && !isethereal(M))
+ if(prob(25) && !isethereal(M))
M.electrocute_act(rand(10,15), "Liquid Electricity in their body", 1) //lmao at the newbs who eat energy bars
playsound(M, "sparks", 50, TRUE)
return ..()
diff --git a/code/modules/reagents/chemistry/reagents/other_reagents.dm b/code/modules/reagents/chemistry/reagents/other_reagents.dm
index 4ade7607c6..c88a440260 100644
--- a/code/modules/reagents/chemistry/reagents/other_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/other_reagents.dm
@@ -2197,13 +2197,6 @@
M.emote("nya")
if(prob(20))
to_chat(M, "[pick("Headpats feel nice.", "The feeling of a hairball...", "Backrubs would be nice.", "Whats behind those doors?")]")
- if(ishuman(M))
- var/mob/living/carbon/human/H = M
- var/list/adjusted = H.adjust_arousal(2,aphro = TRUE)
- for(var/g in adjusted)
- var/obj/item/organ/genital/G = g
- to_chat(M, "You feel like playing with your [G.name]!")
-
..()
/datum/reagent/preservahyde
diff --git a/code/modules/reagents/chemistry/recipes/drugs.dm b/code/modules/reagents/chemistry/recipes/drugs.dm
index a2b8c27552..468d29c052 100644
--- a/code/modules/reagents/chemistry/recipes/drugs.dm
+++ b/code/modules/reagents/chemistry/recipes/drugs.dm
@@ -62,34 +62,3 @@
results = list(/datum/reagent/moonsugar = 1, /datum/reagent/medicine/morphine = 2.5)
required_temp = 315 //a little above normal body temperature
required_reagents = list(/datum/reagent/drug/skooma = 1)
-/datum/chemical_reaction/aphro
- name = "crocin"
- id = /datum/reagent/drug/aphrodisiac
- results = list(/datum/reagent/drug/aphrodisiac = 6)
- required_reagents = list(/datum/reagent/carbon = 2, /datum/reagent/hydrogen = 2, /datum/reagent/oxygen = 2, /datum/reagent/water = 1)
- required_temp = 400
- mix_message = "The mixture boils off a pink vapor..."//The water boils off, leaving the crocin
-
-/datum/chemical_reaction/aphroplus
- name = "hexacrocin"
- id = /datum/reagent/drug/aphrodisiacplus
- results = list(/datum/reagent/drug/aphrodisiacplus = 1)
- required_reagents = list(/datum/reagent/drug/aphrodisiac = 6, /datum/reagent/phenol = 1)
- required_temp = 400
- mix_message = "The mixture rapidly condenses and darkens in color..."
-
-/datum/chemical_reaction/anaphro
- name = "camphor"
- id = /datum/reagent/drug/anaphrodisiac
- results = list(/datum/reagent/drug/anaphrodisiac = 6)
- required_reagents = list(/datum/reagent/carbon = 2, /datum/reagent/hydrogen = 2, /datum/reagent/oxygen = 2, /datum/reagent/sulfur = 1)
- required_temp = 400
- mix_message = "The mixture boils off a yellow, smelly vapor..."//Sulfur burns off, leaving the camphor
-
-/datum/chemical_reaction/anaphroplus
- name = "pentacamphor"
- id = /datum/reagent/drug/anaphrodisiacplus
- results = list(/datum/reagent/drug/anaphrodisiacplus = 1)
- required_reagents = list(/datum/reagent/drug/aphrodisiac = 5, /datum/reagent/acetone = 1)
- required_temp = 300
- mix_message = "The mixture thickens and heats up slighty..."
diff --git a/code/modules/reagents/chemistry/recipes/others.dm b/code/modules/reagents/chemistry/recipes/others.dm
index f32bc708d0..09b7eabbc1 100644
--- a/code/modules/reagents/chemistry/recipes/others.dm
+++ b/code/modules/reagents/chemistry/recipes/others.dm
@@ -684,10 +684,10 @@
required_reagents = list(/datum/reagent/toxin/mindbreaker = 1, /datum/reagent/medicine/synaptizine = 1, /datum/reagent/water = 1)
/datum/chemical_reaction/cat
- name = "felined mutation toxic"
+ name = "felinid mutation toxic"
id = /datum/reagent/mutationtoxin/felinid
results = list(/datum/reagent/mutationtoxin/felinid = 1)
- required_reagents = list(/datum/reagent/toxin/mindbreaker = 1, /datum/reagent/ammonia = 1, /datum/reagent/water = 1, /datum/reagent/drug/aphrodisiac = 10, /datum/reagent/mutationtoxin = 1) // Maybe aphro+ if it becomes a shitty meme
+ required_reagents = list(/datum/reagent/toxin/mindbreaker = 1, /datum/reagent/ammonia = 1, /datum/reagent/water = 1, /datum/reagent/pax/catnip = 1, /datum/reagent/mutationtoxin = 1)
required_temp = 450
/datum/chemical_reaction/moff
@@ -837,4 +837,4 @@
/datum/chemical_reaction/cellulose_carbonization
results = list(/datum/reagent/carbon = 1)
required_reagents = list(/datum/reagent/cellulose = 1)
- required_temp = 512
\ No newline at end of file
+ required_temp = 512
diff --git a/code/modules/reagents/reagent_containers/bottle.dm b/code/modules/reagents/reagent_containers/bottle.dm
index eb4554b7e5..2cd0a76a4b 100644
--- a/code/modules/reagents/reagent_containers/bottle.dm
+++ b/code/modules/reagents/reagent_containers/bottle.dm
@@ -432,26 +432,4 @@
/obj/item/reagent_containers/glass/bottle/ichor/green
name = "green potion"
- list_reagents = list(/datum/reagent/green_ichor = 1)
-
-//Lewd Stuff
-
-/obj/item/reagent_containers/glass/bottle/crocin
- name = "Crocin bottle"
- desc = "A bottle of mild aphrodisiac. Increases libido."
- list_reagents = list(/datum/reagent/drug/aphrodisiac = 30)
-
-/obj/item/reagent_containers/glass/bottle/hexacrocin
- name = "Hexacrocin bottle"
- desc = "A bottle of strong aphrodisiac. Increases libido."
- list_reagents = list(/datum/reagent/drug/aphrodisiacplus = 30)
-
-/obj/item/reagent_containers/glass/bottle/camphor
- name = "Camphor bottle"
- desc = "A bottle of mild anaphrodisiac. Reduces libido."
- list_reagents = list(/datum/reagent/drug/anaphrodisiac = 30)
-
-/obj/item/reagent_containers/glass/bottle/hexacamphor
- name = "Hexacamphor bottle"
- desc = "A bottle of strong anaphrodisiac. Reduces libido."
- list_reagents = list(/datum/reagent/drug/anaphrodisiacplus = 30)
+ list_reagents = list(/datum/reagent/green_ichor = 1)
\ No newline at end of file
diff --git a/code/modules/reagents/reagent_dispenser.dm b/code/modules/reagents/reagent_dispenser.dm
index 819012a61a..79edb37913 100644
--- a/code/modules/reagents/reagent_dispenser.dm
+++ b/code/modules/reagents/reagent_dispenser.dm
@@ -222,19 +222,6 @@
icon_state = "orangekeg"
reagent_id = /datum/reagent/consumable/ethanol/mead
-/obj/structure/reagent_dispensers/keg/aphro
- name = "keg of aphrodisiac"
- desc = "A keg of aphrodisiac."
- icon_state = "pinkkeg"
- reagent_id = /datum/reagent/drug/aphrodisiac
- tank_volume = 150
-
-/obj/structure/reagent_dispensers/keg/aphro/strong
- name = "keg of strong aphrodisiac"
- desc = "A keg of strong and addictive aphrodisiac."
- reagent_id = /datum/reagent/drug/aphrodisiacplus
- tank_volume = 120
-
/obj/structure/reagent_dispensers/keg/milk
name = "keg of milk"
desc = "A keg of pasteurised, homogenised, filtered and semi-skimmed space milk."
diff --git a/code/modules/recycling/disposal/bin.dm b/code/modules/recycling/disposal/bin.dm
index ba4870903e..c8da9ab5e3 100644
--- a/code/modules/recycling/disposal/bin.dm
+++ b/code/modules/recycling/disposal/bin.dm
@@ -299,13 +299,15 @@
// handle machine interaction
-/obj/machinery/disposal/bin/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)
+/obj/machinery/disposal/bin/ui_state(mob/user)
+ return GLOB.notcontained_state
+
+/obj/machinery/disposal/bin/ui_interact(mob/user, datum/tgui/ui)
if(stat & BROKEN)
return
- 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, "DisposalUnit", name, ui_x, ui_y, master_ui, state)
+ ui = new(user, src, "DisposalUnit", name)
ui.open()
/obj/machinery/disposal/bin/ui_data(mob/user)
diff --git a/code/modules/research/anomaly/anomaly_core.dm b/code/modules/research/anomaly/anomaly_core.dm
new file mode 100644
index 0000000000..7aeb7b3a9b
--- /dev/null
+++ b/code/modules/research/anomaly/anomaly_core.dm
@@ -0,0 +1,63 @@
+// Embedded signaller used in anomalies.
+/obj/item/assembly/signaler/anomaly
+ name = "anomaly core"
+ desc = "The neutralized core of an anomaly. It'd probably be valuable for research."
+ icon_state = "anomaly_core"
+ //inhand_icon_state = "electronic"
+ lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi'
+ resistance_flags = FIRE_PROOF
+ var/anomaly_type = /obj/effect/anomaly
+
+/obj/item/assembly/signaler/anomaly/receive_signal(datum/signal/signal)
+ if(!signal)
+ return FALSE
+ if(signal.data["code"] != code)
+ return FALSE
+ if(suicider)
+ manual_suicide(suicider)
+ for(var/obj/effect/anomaly/A in get_turf(src))
+ A.anomalyNeutralize()
+ return TRUE
+
+/obj/item/assembly/signaler/anomaly/manual_suicide(mob/living/carbon/user)
+ user.visible_message("[user]'s [src] is reacting to the radio signal, warping [user.p_their()] body!")
+ //user.set_suicide(TRUE)
+ user.suicide_log()
+ user.gib()
+
+/obj/item/assembly/signaler/anomaly/attackby(obj/item/I, mob/user, params)
+ if(I.tool_behaviour == TOOL_ANALYZER)
+ to_chat(user, "Analyzing... [src]'s stabilized field is fluctuating along frequency [format_frequency(frequency)], code [code].")
+ return ..()
+
+//Anomaly cores
+/obj/item/assembly/signaler/anomaly/pyro
+ name = "\improper pyroclastic anomaly core"
+ desc = "The neutralized core of a pyroclastic anomaly. It feels warm to the touch. It'd probably be valuable for research."
+ icon_state = "pyro_core"
+ anomaly_type = /obj/effect/anomaly/pyro
+
+/obj/item/assembly/signaler/anomaly/grav
+ name = "\improper gravitational anomaly core"
+ desc = "The neutralized core of a gravitational anomaly. It feels much heavier than it looks. It'd probably be valuable for research."
+ icon_state = "grav_core"
+ anomaly_type = /obj/effect/anomaly/grav
+
+/obj/item/assembly/signaler/anomaly/flux
+ name = "\improper flux anomaly core"
+ desc = "The neutralized core of a flux anomaly. Touching it makes your skin tingle. It'd probably be valuable for research."
+ icon_state = "flux_core"
+ anomaly_type = /obj/effect/anomaly/flux
+
+/obj/item/assembly/signaler/anomaly/bluespace
+ name = "\improper bluespace anomaly core"
+ desc = "The neutralized core of a bluespace anomaly. It keeps phasing in and out of view. It'd probably be valuable for research."
+ icon_state = "anomaly_core"
+ anomaly_type = /obj/effect/anomaly/bluespace
+
+/obj/item/assembly/signaler/anomaly/vortex
+ name = "\improper vortex anomaly core"
+ desc = "The neutralized core of a vortex anomaly. It won't sit still, as if some invisible force is acting on it. It'd probably be valuable for research."
+ icon_state = "vortex_core"
+ anomaly_type = /obj/effect/anomaly/bhole
diff --git a/code/modules/research/bepis.dm b/code/modules/research/bepis.dm
index 87aec72646..7b36a614a7 100644
--- a/code/modules/research/bepis.dm
+++ b/code/modules/research/bepis.dm
@@ -33,11 +33,13 @@
var/inaccuracy_percentage = 1.5
var/positive_cash_offset = 0
var/negative_cash_offset = 0
- var/minor_rewards = list(/obj/item/stack/circuit_stack/full, //To add a new minor reward, add it here.
- /obj/item/flashlight/flashdark,
- /obj/item/pen/survival,
- /obj/item/circuitboard/machine/sleeper/party,
- /obj/item/toy/sprayoncan)
+ var/list/minor_rewards = list(
+ //To add a new minor reward, add it here.
+ /obj/item/stack/circuit_stack/full,
+ /obj/item/pen/survival,
+ /obj/item/circuitboard/machine/sleeper/party,
+ /obj/item/toy/sprayoncan,
+ )
var/static/list/item_list = list()
/obj/machinery/rnd/bepis/attackby(obj/item/O, mob/user, params)
@@ -101,6 +103,7 @@
return
account.adjust_money(-deposit_value) //The money vanishes, not paid to any accounts.
SSblackbox.record_feedback("amount", "BEPIS_credits_spent", deposit_value)
+ //log_econ("[deposit_value] credits were inserted into [src] by [account.account_holder]")
banked_cash += deposit_value
use_power(1000 * power_saver)
say("Cash deposit successful. There is [banked_cash] in the chamber.")
@@ -179,10 +182,10 @@
icon_state = "chamber"
return
-/obj/machinery/rnd/bepis/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state)
- ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+/obj/machinery/rnd/bepis/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
- ui = new(user, src, ui_key, "Bepis", name, 500, 480, master_ui, state)
+ ui = new(user, src, "Bepis", name)
ui.open()
RefreshParts()
diff --git a/code/modules/research/nanites/nanite_chamber_computer.dm b/code/modules/research/nanites/nanite_chamber_computer.dm
index b9623b751d..70e4d05590 100644
--- a/code/modules/research/nanites/nanite_chamber_computer.dm
+++ b/code/modules/research/nanites/nanite_chamber_computer.dm
@@ -5,8 +5,6 @@
var/obj/item/disk/nanite_program/disk
icon_screen = "nanite_chamber_control"
circuit = /obj/item/circuitboard/computer/nanite_chamber_control
- ui_x = 380
- ui_y = 570
/obj/machinery/computer/nanite_chamber_control/Initialize()
. = ..()
@@ -25,10 +23,10 @@
find_chamber()
..()
-/obj/machinery/computer/nanite_chamber_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/nanite_chamber_control/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
- ui = new(user, src, ui_key, "NaniteChamberControl", name, ui_x, ui_y, master_ui, state)
+ ui = new(user, src, "NaniteChamberControl", name)
ui.open()
/obj/machinery/computer/nanite_chamber_control/ui_data()
diff --git a/code/modules/research/nanites/nanite_cloud_controller.dm b/code/modules/research/nanites/nanite_cloud_controller.dm
index ef0fc33bd7..1a0a5dbd4d 100644
--- a/code/modules/research/nanites/nanite_cloud_controller.dm
+++ b/code/modules/research/nanites/nanite_cloud_controller.dm
@@ -1,11 +1,9 @@
/obj/machinery/computer/nanite_cloud_controller
name = "nanite cloud controller"
desc = "Stores and controls nanite cloud backups."
- circuit = /obj/item/circuitboard/computer/nanite_cloud_controller
icon = 'icons/obj/machines/research.dmi'
icon_state = "nanite_cloud_controller"
- ui_x = 375
- ui_y = 700
+ circuit = /obj/item/circuitboard/computer/nanite_cloud_controller
var/obj/item/disk/nanite_program/disk
var/list/datum/nanite_cloud_backup/cloud_backups = list()
@@ -20,19 +18,25 @@
/obj/machinery/computer/nanite_cloud_controller/attackby(obj/item/I, mob/user)
if(istype(I, /obj/item/disk/nanite_program))
var/obj/item/disk/nanite_program/N = I
- if(disk)
- eject(user)
- if(user.transferItemToLoc(N, src))
+ if (user.transferItemToLoc(N, src))
to_chat(user, "You insert [N] into [src].")
playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, FALSE)
+ if(disk)
+ eject(user)
disk = N
else
..()
+/obj/machinery/computer/nanite_cloud_controller/AltClick(mob/user)
+ if(disk && user.canUseTopic(src, !issilicon(user)))
+ to_chat(user, "You take out [disk] from [src].")
+ eject(user)
+ return
+
/obj/machinery/computer/nanite_cloud_controller/proc/eject(mob/living/user)
if(!disk)
return
- if(!istype(user) || !Adjacent(user) ||!user.put_in_active_hand(disk))
+ if(!istype(user) || !Adjacent(user))// ||!user.put_in_active_hand(disk))
disk.forceMove(drop_location())
disk = null
@@ -53,10 +57,10 @@
backup.nanites = cloud_copy
investigate_log("[key_name(user)] created a new nanite cloud backup with id #[cloud_id]", INVESTIGATE_NANITES)
-/obj/machinery/computer/nanite_cloud_controller/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/nanite_cloud_controller/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
- ui = new(user, src, ui_key, "NaniteCloudControl", name, ui_x, ui_y, master_ui, state)
+ ui = new(user, src, "NaniteCloudControl", name)
ui.open()
/obj/machinery/computer/nanite_cloud_controller/ui_data()
diff --git a/code/modules/research/nanites/nanite_program_hub.dm b/code/modules/research/nanites/nanite_program_hub.dm
index 3f62a11f89..5a330f7a4f 100644
--- a/code/modules/research/nanites/nanite_program_hub.dm
+++ b/code/modules/research/nanites/nanite_program_hub.dm
@@ -3,26 +3,24 @@
desc = "Compiles nanite programs from the techweb servers and downloads them into disks."
icon = 'icons/obj/machines/research.dmi'
icon_state = "nanite_program_hub"
- circuit = /obj/item/circuitboard/machine/nanite_program_hub
use_power = IDLE_POWER_USE
anchored = TRUE
density = TRUE
- ui_x = 500
- ui_y = 700
+ circuit = /obj/item/circuitboard/machine/nanite_program_hub
var/obj/item/disk/nanite_program/disk
var/datum/techweb/linked_techweb
var/current_category = "Main"
var/detail_view = TRUE
var/categories = list(
- list(name = "Utility Nanites"),
- list(name = "Medical Nanites"),
- list(name = "Sensor Nanites"),
- list(name = "Augmentation Nanites"),
- list(name = "Suppression Nanites"),
- list(name = "Weaponized Nanites"),
- list(name = "Protocols") //Moved to default techweb from B.E.P.I.S. research, for now
- )
+ list(name = "Utility Nanites"),
+ list(name = "Medical Nanites"),
+ list(name = "Sensor Nanites"),
+ list(name = "Augmentation Nanites"),
+ list(name = "Suppression Nanites"),
+ list(name = "Weaponized Nanites"),
+ list(name = "Protocols"),
+ )
/obj/machinery/nanite_program_hub/Initialize()
. = ..()
@@ -31,26 +29,44 @@
/obj/machinery/nanite_program_hub/attackby(obj/item/I, mob/user)
if(istype(I, /obj/item/disk/nanite_program))
var/obj/item/disk/nanite_program/N = I
- if(disk)
- eject(user)
if(user.transferItemToLoc(N, src))
to_chat(user, "You insert [N] into [src].")
playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, FALSE)
+ if(disk)
+ eject(user)
disk = N
else
..()
+/obj/machinery/nanite_program_hub/screwdriver_act(mob/living/user, obj/item/I) //remove when runtimed
+ if(..())
+ return TRUE
+
+ return default_deconstruction_screwdriver(user, "nanite_program_hub_t", "nanite_program_hub", I)
+
+/obj/machinery/nanite_program_hub/crowbar_act(mob/living/user, obj/item/I)
+ if(..())
+ return TRUE
+
+ return default_deconstruction_crowbar(I)
+
/obj/machinery/nanite_program_hub/proc/eject(mob/living/user)
if(!disk)
return
- if(!istype(user) || !Adjacent(user) || !user.put_in_active_hand(disk))
+ if(!istype(user) || !Adjacent(user))// || !user.put_in_active_hand(disk))
disk.forceMove(drop_location())
disk = null
-/obj/machinery/nanite_program_hub/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/nanite_program_hub/AltClick(mob/user)
+ if(disk && user.canUseTopic(src, !issilicon(user)))
+ to_chat(user, "You take out [disk] from [src].")
+ eject(user)
+ return
+
+/obj/machinery/nanite_program_hub/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
- ui = new(user, src, ui_key, "NaniteProgramHub", name, ui_x, ui_y, master_ui, state)
+ ui = new(user, src, "NaniteProgramHub", name)
ui.open()
/obj/machinery/nanite_program_hub/ui_data()
@@ -123,9 +139,3 @@
disk.program = null
disk.name = initial(disk.name)
. = TRUE
-
-
-/obj/machinery/nanite_program_hub/admin/Initialize()
- . = ..()
- linked_techweb = SSresearch.admin_tech
-
diff --git a/code/modules/research/nanites/nanite_programmer.dm b/code/modules/research/nanites/nanite_programmer.dm
index 3de2a974e2..804f256cf6 100644
--- a/code/modules/research/nanites/nanite_programmer.dm
+++ b/code/modules/research/nanites/nanite_programmer.dm
@@ -3,41 +3,57 @@
desc = "A device that can edit nanite program disks to adjust their functionality."
var/obj/item/disk/nanite_program/disk
var/datum/nanite_program/program
- circuit = /obj/item/circuitboard/machine/nanite_programmer
icon = 'icons/obj/machines/research.dmi'
icon_state = "nanite_programmer"
use_power = IDLE_POWER_USE
anchored = TRUE
density = TRUE
flags_1 = HEAR_1
- ui_x = 420
- ui_y = 550
+ circuit = /obj/item/circuitboard/machine/nanite_programmer
/obj/machinery/nanite_programmer/attackby(obj/item/I, mob/user)
if(istype(I, /obj/item/disk/nanite_program))
var/obj/item/disk/nanite_program/N = I
- if(disk)
- eject(user)
if(user.transferItemToLoc(N, src))
to_chat(user, "You insert [N] into [src]")
playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, FALSE)
+ if(disk)
+ eject(user)
disk = N
program = N.program
else
..()
+/obj/machinery/nanite_programmer/screwdriver_act(mob/living/user, obj/item/I)
+ if(..())
+ return TRUE
+
+ return default_deconstruction_screwdriver(user, "nanite_programmer_t", "nanite_programmer", I)
+
+/obj/machinery/nanite_programmer/crowbar_act(mob/living/user, obj/item/I)
+ if(..())
+ return TRUE
+
+ return default_deconstruction_crowbar(I)
+
/obj/machinery/nanite_programmer/proc/eject(mob/living/user)
if(!disk)
return
- if(!istype(user) || !Adjacent(user) || !user.put_in_active_hand(disk))
+ if(!istype(user) || !Adjacent(user))// || !user.put_in_active_hand(disk))
disk.forceMove(drop_location())
disk = null
program = null
-/obj/machinery/nanite_programmer/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/nanite_programmer/AltClick(mob/user)
+ if(disk && user.canUseTopic(src, !issilicon(user)))
+ to_chat(user, "You take out [disk] from [src].")
+ eject(user)
+ return
+
+/obj/machinery/nanite_programmer/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
- ui = new(user, src, ui_key, "NaniteProgrammer", name, ui_x, ui_y, master_ui, state)
+ ui = new(user, src, "NaniteProgrammer", name)
ui.open()
/obj/machinery/nanite_programmer/ui_data()
@@ -131,7 +147,7 @@
program.timer_trigger_delay = timer
. = TRUE
-/obj/machinery/nanite_programmer/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode, atom/movable/source)
+/obj/machinery/nanite_programmer/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, list/message_mods = list())
. = ..()
var/static/regex/when = regex("(?:^\\W*when|when\\W*$)", "i") //starts or ends with when
if(findtext(raw_message, when) && !istype(speaker, /obj/machinery/nanite_programmer))
diff --git a/code/modules/research/nanites/nanite_remote.dm b/code/modules/research/nanites/nanite_remote.dm
index b222b2ad35..e3f5a0f286 100644
--- a/code/modules/research/nanites/nanite_remote.dm
+++ b/code/modules/research/nanites/nanite_remote.dm
@@ -80,10 +80,13 @@
var/datum/nanite_program/relay/N = X
N.relay_signal(code, relay_code, source)
-/obj/item/nanite_remote/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.hands_state)
- ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+/obj/item/nanite_remote/ui_state(mob/user)
+ return GLOB.hands_state
+
+/obj/item/nanite_remote/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
- ui = new(user, src, ui_key, "NaniteRemote", name, 420, 500, master_ui, state)
+ ui = new(user, src, "NaniteRemote", name)
ui.open()
/obj/item/nanite_remote/ui_data()
@@ -94,7 +97,6 @@
data["locked"] = locked
data["saved_settings"] = saved_settings
data["program_name"] = current_program_name
-
return data
/obj/item/nanite_remote/ui_act(action, params)
diff --git a/code/modules/ruins/spaceruin_code/TheDerelict.dm b/code/modules/ruins/spaceruin_code/TheDerelict.dm
index 513d16a0cb..d26e023df0 100644
--- a/code/modules/ruins/spaceruin_code/TheDerelict.dm
+++ b/code/modules/ruins/spaceruin_code/TheDerelict.dm
@@ -17,7 +17,6 @@
desc = "Looks like someone started shakily writing a will in space common, but were interrupted by something bloody..."
info = "__Objectives #1__: Find out what is hidden in Kosmicheskaya Stantsiya 13s Vault"
-
/// Vault controller for use on the derelict/KS13.
/obj/machinery/computer/vaultcontroller
name = "vault controller"
@@ -35,15 +34,10 @@
var/siphoned_power = 0
var/siphon_max = 1e7
- ui_x = 300
- ui_y = 120
-
-
/obj/machinery/computer/monitor/examine(mob/user)
. = ..()
. += "It appears to be powered via a cable connector."
-
//Checks for cable connection, charges if possible.
/obj/machinery/computer/vaultcontroller/process()
if(siphoned_power >= siphon_max)
@@ -52,13 +46,11 @@
if(attached_cable)
attempt_siphon()
-
///Looks for a cable connection beneath the machine.
/obj/machinery/computer/vaultcontroller/proc/update_cable()
var/turf/T = get_turf(src)
attached_cable = locate(/obj/structure/cable) in T
-
///Initializes airlock links.
/obj/machinery/computer/vaultcontroller/proc/find_airlocks()
for(var/obj/machinery/door/airlock/A in GLOB.airlocks)
@@ -70,7 +62,6 @@
door2 = A
break
-
///Tries to charge from powernet excess, no upper limit except max charge.
/obj/machinery/computer/vaultcontroller/proc/attempt_siphon()
var/surpluspower = clamp(attached_cable.surplus(), 0, (siphon_max - siphoned_power))
@@ -78,7 +69,6 @@
attached_cable.add_load(surpluspower)
siphoned_power += surpluspower
-
///Handles the doors closing
/obj/machinery/computer/vaultcontroller/proc/cycle_close(obj/machinery/door/airlock/A)
A.safe = FALSE //Make sure its forced closed, always
@@ -86,14 +76,12 @@
A.close()
A.bolt()
-
///Handles the doors opening
/obj/machinery/computer/vaultcontroller/proc/cycle_open(obj/machinery/door/airlock/A)
A.unbolt()
A.open()
A.bolt()
-
///Attempts to lock the vault doors
/obj/machinery/computer/vaultcontroller/proc/lock_vault()
if(door1 && !door1.density)
@@ -103,7 +91,6 @@
if(door1.density && door1.locked && door2.density && door2.locked)
locked = TRUE
-
///Attempts to unlock the vault doors
/obj/machinery/computer/vaultcontroller/proc/unlock_vault()
if(door1 && door1.density)
@@ -113,7 +100,6 @@
if(!door1.density && door1.locked && !door2.density && door2.locked)
locked = FALSE
-
///Attempts to lock/unlock vault doors, if machine is charged.
/obj/machinery/computer/vaultcontroller/proc/activate_lock()
if(siphoned_power < siphon_max)
@@ -125,15 +111,12 @@
else
lock_vault()
-
-/obj/machinery/computer/vaultcontroller/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/vaultcontroller/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
- ui = new(user, src, ui_key, "VaultController", name, ui_x, ui_y, master_ui, state)
+ ui = new(user, src, "VaultController", name)
ui.open()
-
/obj/machinery/computer/vaultcontroller/ui_act(action, params)
if(..())
return
@@ -141,7 +124,6 @@
if("togglelock")
activate_lock()
-
/obj/machinery/computer/vaultcontroller/ui_data()
var/list/data = list()
data["stored"] = siphoned_power
@@ -149,7 +131,6 @@
data["doorstatus"] = locked
return data
-
///Airlock that can't be deconstructed, broken or hacked.
/obj/machinery/door/airlock/vault/derelict
locked = TRUE
@@ -158,16 +139,13 @@
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
id_tag = "derelictvault"
-
///Overrides screwdriver attack to prevent all deconstruction and hacking.
/obj/machinery/door/airlock/vault/derelict/attackby(obj/item/C, mob/user, params)
if(C.tool_behaviour == TOOL_SCREWDRIVER)
return
..()
-
// So drones can teach borgs and AI dronespeak. For best effect, combine with mother drone lawset.
-
/obj/item/dronespeak_manual
name = "dronespeak manual"
desc = "The book's cover reads: \"Understanding Dronespeak - An exercise in futility.\""
@@ -181,7 +159,7 @@
to_chat(user, "You start skimming through [src], but you already know dronespeak.")
else
to_chat(user, "You start skimming through [src], and suddenly the drone chittering makes sense.")
- user.grant_language(/datum/language/drone, TRUE, TRUE)
+ user.grant_language(/datum/language/drone, TRUE, TRUE)//, LANGUAGE_MIND)
return
if(user.has_language(/datum/language/drone))
@@ -202,7 +180,7 @@
M.visible_message("[user] beats [M] over the head with [src]!", "[user] beats you over the head with [src]!", "You hear smacking.")
else
M.visible_message("[user] teaches [M] by beating [M.p_them()] over the head with [src]!", "As [user] hits you with [src], chitters resonate in your mind.", "You hear smacking.")
- M.grant_language(/datum/language/drone, TRUE, TRUE)
+ M.grant_language(/datum/language/drone, TRUE, TRUE) //, LANGUAGE_MIND)
return
/obj/structure/fluff/oldturret
diff --git a/code/modules/security_levels/keycard_authentication.dm b/code/modules/security_levels/keycard_authentication.dm
index f6418b9236..7326cad816 100644
--- a/code/modules/security_levels/keycard_authentication.dm
+++ b/code/modules/security_levels/keycard_authentication.dm
@@ -35,11 +35,13 @@ GLOBAL_DATUM_INIT(keycard_events, /datum/events, new)
QDEL_NULL(ev)
return ..()
-/obj/machinery/keycard_auth/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/keycard_auth/ui_state(mob/user)
+ return GLOB.physical_state
+
+/obj/machinery/keycard_auth/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
- ui = new(user, src, ui_key, "KeycardAuth", name, ui_x, ui_y, master_ui, state)
+ ui = new(user, src, "KeycardAuth", name)
ui.open()
/obj/machinery/keycard_auth/ui_data()
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/station_goals/bsa.dm b/code/modules/station_goals/bsa.dm
index 2ca0e65477..4c3f30a786 100644
--- a/code/modules/station_goals/bsa.dm
+++ b/code/modules/station_goals/bsa.dm
@@ -210,20 +210,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 +258,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
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/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/tongue.dm b/code/modules/surgery/organs/tongue.dm
index 1c4a2d3043..7090ab62e2 100644
--- a/code/modules/surgery/organs/tongue.dm
+++ b/code/modules/surgery/organs/tongue.dm
@@ -312,3 +312,26 @@
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/tgui/external.dm b/code/modules/tgui/external.dm
index 5de54c439c..46b324e151 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,17 @@
/**
* 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()
+
/**
* public
*
@@ -137,17 +146,43 @@
*
* 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 Whether the topic is passed (TRUE), or cancelled (FALSE).
+ */
+/proc/tgui_Topic(href_list)
+ // Skip non-tgui topics
+ if(!href_list["tgui"])
+ return TRUE
+ var/type = href_list["type"]
+ // Unconditionally collect tgui logs
+ if(type == "log")
+ log_tgui(usr, href_list["message"])
+ // 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 FALSE
+ // 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 FALSE
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/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..d0d5ff8ebb 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,53 @@
*/
/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(inline_assets = list(
+ 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")
+ window.send_asset(get_asset_datum(/datum/asset/simple/fontawesome))
+ for(var/datum/asset/asset in src_object.ui_assets(user))
+ window.send_asset(asset)
+ 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 +122,173 @@
*
* 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
+ */
+/datum/tgui/proc/send_asset(datum/asset/asset)
+ if(!window)
+ CRASH("send_asset() can only be called after open().")
+ 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
+ ),
+ "user" = list(
+ "name" = "[user]",
+ "ckey" = "[user.ckey]",
+ "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..3f271163c9
--- /dev/null
+++ b/code/modules/tgui/tgui_window.dm
@@ -0,0 +1,238 @@
+/**
+ * Copyright (c) 2020 Aleksej Komarov
+ * SPDX-License-Identifier: MIT
+ */
+
+/datum/tgui_window
+ var/id
+ var/client/client
+ var/pooled
+ var/pool_index
+ var/status = TGUI_WINDOW_CLOSED
+ var/locked = FALSE
+ var/datum/tgui/locked_by
+ var/fatally_errored = FALSE
+ var/message_queue
+ var/sent_assets = list()
+
+/**
+ * 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.pooled = pooled
+ if(pooled)
+ client.tgui_windows[id] = src
+ 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.
+ */
+/datum/tgui_window/proc/initialize(inline_assets = list())
+ log_tgui(client, "[id]/initialize")
+ if(!client)
+ return
+ status = TGUI_WINDOW_LOADING
+ fatally_errored = FALSE
+ message_queue = null
+ // Build window options
+ var/options = "file=[id].html;can_minimize=0;auto_format=0;"
+ // Remove titlebar and resize handles for a fancy window
+ if(client.prefs.tgui_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()
+ html = replacetextEx(html, "\n", inline_styles)
+ html = replacetextEx(html, "\n", inline_scripts)
+ // 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]\"")
+
+/**
+ * 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 hook its on_message
+ * callback into the message stream.
+ *
+ * optional ui /datum/tgui
+ */
+/datum/tgui_window/proc/acquire_lock(datum/tgui/ui)
+ locked = TRUE
+ locked_by = ui
+
+/**
+ * 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
+ *
+ * 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, list/payload, force)
+ if(!client)
+ return
+ var/message = json_encode(list(
+ "type" = type,
+ "payload" = payload,
+ ))
+ // Strip #255/improper.
+ message = replacetext(message, "\proper", "")
+ message = replacetext(message, "\improper", "")
+ // Pack for sending via output()
+ message = url_encode(message)
+ // 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, "[id].browser:update")
+
+/**
+ * public
+ *
+ * Makes an asset available to use in tgui.
+ *
+ * required asset datum/asset
+ */
+/datum/tgui_window/proc/send_asset(datum/asset/asset)
+ if(!client || !asset)
+ return
+ 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())
+ sent_assets += list(asset)
+ asset.send(client)
+
+/**
+ * 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, "[id].browser:update")
+ message_queue = null
+
+/**
+ * private
+ *
+ * Callback for handling incoming tgui messages.
+ */
+/datum/tgui_window/proc/on_message(type, list/payload, list/href_list)
+ switch(type)
+ if("ready")
+ // Status can be READY if user has refreshed the window.
+ if(status == TGUI_WINDOW_READY)
+ // Resend the assets
+ for(var/asset in sent_assets)
+ send_asset(asset)
+ status = TGUI_WINDOW_READY
+ if("log")
+ if(href_list["fatal"])
+ fatally_errored = TRUE
+ // Pass message to UI that requested the lock
+ if(locked && locked_by)
+ locked_by.on_message(type, payload, href_list)
+ flush_message_queue()
+ return
+ // If not locked, handle these message types
+ switch(type)
+ if("suspend")
+ close(can_be_suspended = TRUE)
+ if("close")
+ close(can_be_suspended = FALSE)
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..c26a9ae1f0 100644
--- a/code/modules/uplink/uplink_items/uplink_clothing.dm
+++ b/code/modules/uplink/uplink_items/uplink_clothing.dm
@@ -97,3 +97,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_devices.dm b/code/modules/uplink/uplink_items/uplink_devices.dm
index eddf1760ed..5f5eb91a04 100644
--- a/code/modules/uplink/uplink_items/uplink_devices.dm
+++ b/code/modules/uplink/uplink_items/uplink_devices.dm
@@ -206,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_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/vending/_vending.dm b/code/modules/vending/_vending.dm
index a42fe97cb4..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!")
@@ -672,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(
@@ -713,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
@@ -722,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)
@@ -766,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
@@ -780,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)
@@ -792,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)
@@ -805,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/kinkmate.dm b/code/modules/vending/kinkmate.dm
index df8a4e8a96..dc4f4e9273 100644
--- a/code/modules/vending/kinkmate.dm
+++ b/code/modules/vending/kinkmate.dm
@@ -36,13 +36,10 @@
/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/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/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 3a7cd4b03f..2cb3a83257 100644
--- a/code/modules/zombie/items.dm
+++ b/code/modules/zombie/items.dm
@@ -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/game_options.txt b/config/game_options.txt
index 405ec0405a..1585d45e6d 100644
--- a/config/game_options.txt
+++ b/config/game_options.txt
@@ -445,6 +445,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/html/changelog.html b/html/changelog.html
index 65a9d69167..b8560b032e 100644
--- a/html/changelog.html
+++ b/html/changelog.html
@@ -50,6 +50,211 @@
-->
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:
@@ -1231,87 +1436,6 @@
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 c82d7d4058..64b9e13271 100644
--- a/html/changelogs/.all_changelog.yml
+++ b/html/changelogs/.all_changelog.yml
@@ -26753,3 +26753,158 @@ DO NOT EDIT THIS FILE BY HAND! AUTOMATICALLY GENERATED BY ss13_genchangelog.py.
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
diff --git a/html/changelogs/AutoChangeLog-pr-12969.yml b/html/changelogs/AutoChangeLog-pr-12969.yml
deleted file mode 100644
index 8042556cde..0000000000
--- a/html/changelogs/AutoChangeLog-pr-12969.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: "silicons"
-delete-after: True
-changes:
- - rscadd: "holoparasites can now play music"
diff --git a/html/changelogs/AutoChangeLog-pr-13001.yml b/html/changelogs/AutoChangeLog-pr-13001.yml
deleted file mode 100644
index bc4e5b5311..0000000000
--- a/html/changelogs/AutoChangeLog-pr-13001.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: "silicons"
-delete-after: True
-changes:
- - balance: "explosions only recurse one level into storage before dropping 1 level per storage layer."
diff --git a/html/changelogs/AutoChangeLog-pr-13006.yml b/html/changelogs/AutoChangeLog-pr-13006.yml
deleted file mode 100644
index e13902099a..0000000000
--- a/html/changelogs/AutoChangeLog-pr-13006.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: "silicons"
-delete-after: True
-changes:
- - tweak: "volumetric storage is now minimum 16 pixels per item because 8 was ridiculous"
diff --git a/html/changelogs/AutoChangeLog-pr-13032.yml b/html/changelogs/AutoChangeLog-pr-13032.yml
deleted file mode 100644
index 06a8e274b5..0000000000
--- a/html/changelogs/AutoChangeLog-pr-13032.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: "lolman360"
-delete-after: True
-changes:
- - bugfix: "podpeople no fat when sunbathing."
diff --git a/html/changelogs/AutoChangeLog-pr-13035.yml b/html/changelogs/AutoChangeLog-pr-13035.yml
deleted file mode 100644
index 1c2bf4dc8d..0000000000
--- a/html/changelogs/AutoChangeLog-pr-13035.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: "lolman360"
-delete-after: True
-changes:
- - bugfix: "vendors are now unanchored when tipped. it just fell over it's not bolted to the ground anymore."
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/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/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/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/head.dmi b/icons/mob/clothing/head.dmi
index a6e75123bb..0ad9452994 100644
Binary files a/icons/mob/clothing/head.dmi and b/icons/mob/clothing/head.dmi differ
diff --git a/icons/mob/clothing/suit.dmi b/icons/mob/clothing/suit.dmi
index 7a690bb29f..c94e8a46d3 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/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/hud.dmi b/icons/mob/hud.dmi
index c21fa47b9c..c6dc7130c7 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/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/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/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/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/hats.dmi b/icons/obj/clothing/hats.dmi
index 90f1fba315..60304999fa 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..e8a360bc87 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/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..9c6df36f49 100644
Binary files a/icons/obj/guns/projectile.dmi and b/icons/obj/guns/projectile.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/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/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/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/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..378c70d187 100644
--- a/modular_citadel/code/modules/client/loadout/__donator.dm
+++ b/modular_citadel/code/modules/client/loadout/__donator.dm
@@ -1,496 +1,497 @@
//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/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
+/datum/gear/donator/cebusoap
name = "Cebutris' soap"
- category = SLOT_IN_BACKPACK
+ slot = 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")
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..ffa4724f63 100644
--- a/modular_citadel/code/modules/client/loadout/gloves.dm
+++ b/modular_citadel/code/modules/client/loadout/gloves.dm
@@ -1,28 +1,30 @@
-/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/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/reagents/chemistry/recipes/fermi.dm b/modular_citadel/code/modules/reagents/chemistry/recipes/fermi.dm
index a24a5beaad..39ba69bd61 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
@@ -384,7 +384,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 +402,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.
@@ -607,4 +603,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/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/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/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/traumas.json b/strings/traumas.json
index f461c5f5fd..58170bd55a 100644
--- a/strings/traumas.json
+++ b/strings/traumas.json
@@ -125,13 +125,13 @@
";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"
],
"mutations": [
diff --git a/tgstation.dme b/tgstation.dme
index 203a6ad8e5..bfe0c58b8c 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -47,6 +47,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 +62,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"
@@ -200,6 +202,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"
@@ -428,6 +431,7 @@
#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\identification.dm"
#include "code\datums\components\igniter.dm"
#include "code\datums\components\infective.dm"
@@ -1405,6 +1409,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"
@@ -1522,6 +1527,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"
@@ -1529,6 +1535,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"
@@ -1540,6 +1547,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"
@@ -2266,6 +2274,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"
@@ -2280,6 +2289,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"
@@ -2494,6 +2508,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"
@@ -2607,6 +2622,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 +2730,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 +2751,16 @@
#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\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"
@@ -3074,6 +3094,7 @@
#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\designs\AI_module_designs.dm"
#include "code\modules\research\designs\autobotter_designs.dm"
#include "code\modules\research\designs\autoylathe_designs.dm"
@@ -3356,15 +3377,14 @@
#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\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"
diff --git a/tgui/.eslintrc.yml b/tgui/.eslintrc.yml
index 67e74085c7..9fd4db9fd2 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:
@@ -388,7 +390,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..9382416e69 100644
--- a/tgui/.gitattributes
+++ b/tgui/.gitattributes
@@ -2,9 +2,18 @@
## 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
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..97a86159e6 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
@@ -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..ff1b4e7dfd 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)
@@ -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
```
@@ -584,6 +585,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
@@ -962,6 +981,7 @@ Example:
- `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,
diff --git a/tgui/docs/converting-old-tgui-interfaces.md b/tgui/docs/converting-old-tgui-interfaces.md
index a42724e05c..fe2feebfee 100644
--- a/tgui/docs/converting-old-tgui-interfaces.md
+++ b/tgui/docs/converting-old-tgui-interfaces.md
@@ -73,6 +73,7 @@ This might look a bit intimidating compared to the reactive part but it's not as
You don't really need to know all this to understand how to use it, but I find it helps with understanding when things go wrong.
Ractive conditionals can have an `else` as well
+
```ractive
{{#if data.condition}}
value
@@ -116,7 +117,7 @@ and you can mix string literals, values, and tags as well.
Ractive has loops for iterating over data and inserting something for each
member of an array or object
-```
+```ractive
{{#each data.list_of_foo}}
foo {{number}} is here.
{{/each}}
@@ -135,6 +136,7 @@ Objects are represented by `{}`, arrays by `[]`
`list("bla", "blo")` would become `["bla", "blo"]` and `list("foo" = 1, "bar" = 2)` would become `{"foo": 1, "bar": 2}`
First things first, above the `return` of the function you're making the interface in, you're going to want to add something like this
+
```jsx
const things = data.things || [];
```
@@ -142,6 +144,7 @@ const things = data.things || [];
This ensures that you'll never be reading a null entry by mistake. Substitute `{}` for objects as appropriate.
If it's an array, you'll want to do this in the template
+
```jsx
{things.map(thing => (
@@ -187,7 +190,7 @@ const fooArray = toArray(fooObject);
Also occasionally you'd see an else:
-```
+```ractive
{{#each data.potentially_empty_list}}
Thing "{{name}}" is in this list!
{{else}}
@@ -220,7 +223,7 @@ This will be a reference of tgui components and the tgui-next equivalent.
Equivalent of `` is ``
-```
+```ractive
Contents
@@ -236,7 +239,7 @@ becomes
A feature sometimes used is if `ui-display` has the `button` property, it will contain a `partial` command. This becomes the `buttons` property on `Section`:
-```
+```ractive
{{#partial button}}
// lots more button bullshit here
@@ -263,7 +266,7 @@ Very important to note `ui-section` is NOT the equivalent of `Section`
`` does not have a direct equivalent, but the closest equivalent is ``
-```
+```ractive
No Power
@@ -293,7 +296,7 @@ Also good to know that if you need the contents of a `LabeledList.Item` to be co
`` has a direct equivalent in ``
-```
+```ractive
Notice stuff!
@@ -311,7 +314,7 @@ becomes
The equivalent of `ui-button` is `Button` but it works quite a bit differently.
-```
+```ractive
{
@@ -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..8dd15925b2 100644
--- a/tgui/package.json
+++ b/tgui/package.json
@@ -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/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/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/perf.js b/tgui/packages/common/perf.js
new file mode 100644
index 0000000000..319b77cea3
--- /dev/null
+++ b/tgui/packages/common/perf.js
@@ -0,0 +1,44 @@
+/**
+ * Ghetto performance measurement tools.
+ *
+ * Uses NODE_ENV to redact itself from production bundles.
+ *
+ * @file
+ * @copyright 2020 Aleksej Komarov
+ * @license MIT
+ */
+
+let markersByLabel = {};
+
+/**
+ * Marks a certain spot in the code for later measurements.
+ */
+const mark = (label, timestamp) => {
+ if (process.env.NODE_ENV !== 'production') {
+ markersByLabel[label] = timestamp || Date.now();
+ }
+};
+
+/**
+ * Calculates and returns the difference between two markers as a string.
+ *
+ * Use logger.log() to print the measurement.
+ */
+const measure = (markerA, markerB) => {
+ if (process.env.NODE_ENV !== 'production') {
+ return timeDiff(
+ markersByLabel[markerA],
+ markersByLabel[markerB]);
+ }
+};
+
+const timeDiff = (startedAt, finishedAt) => {
+ const diff = Math.abs(finishedAt - startedAt);
+ const diffFrames = (diff / 16.6667).toFixed(2);
+ return `${diff}ms (${diffFrames} frames)`;
+};
+
+export const perf = {
+ mark,
+ measure,
+};
diff --git a/tgui/packages/common/react.js b/tgui/packages/common/react.js
index 0828bb8864..dba84b7b10 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
*
diff --git a/tgui/packages/common/redux.js b/tgui/packages/common/redux.js
index 257a9eebf5..dc486ff7b8 100644
--- a/tgui/packages/common/redux.js
+++ b/tgui/packages/common/redux.js
@@ -1,3 +1,9 @@
+/**
+ * @file
+ * @copyright 2020 Aleksej Komarov
+ * @license MIT
+ */
+
import { compose } from './fp';
/**
@@ -63,3 +69,31 @@ 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;
+ };
+};
diff --git a/tgui/packages/common/storage.js b/tgui/packages/common/storage.js
new file mode 100644
index 0000000000..8e1d2183e4
--- /dev/null
+++ b/tgui/packages/common/storage.js
@@ -0,0 +1,76 @@
+/**
+ * Browser-agnostic abstraction of key-value web storage.
+ *
+ * @file
+ * @copyright 2020 Aleksej Komarov
+ * @license MIT
+ */
+
+export const STORAGE_NONE = 0;
+export const STORAGE_LOCAL_STORAGE = 1;
+export const STORAGE_INDEXED_DB = 2;
+
+const createMock = () => {
+ let storage = {};
+ const get = key => storage[key];
+ const set = (key, value) => {
+ storage[key] = value;
+ };
+ const remove = key => {
+ storage[key] = undefined;
+ };
+ const clear = () => {
+ // NOTE: On IE8, this will probably leak memory if used often.
+ storage = {};
+ };
+ return {
+ get,
+ set,
+ remove,
+ clear,
+ engine: STORAGE_NONE,
+ };
+};
+
+const createLocalStorage = () => {
+ const get = key => {
+ const value = localStorage.getItem(key);
+ if (typeof value !== 'string') {
+ return;
+ }
+ return JSON.parse(value);
+ };
+ const set = (key, value) => {
+ localStorage.setItem(key, JSON.stringify(value));
+ };
+ const remove = key => {
+ localStorage.removeItem(key);
+ };
+ const clear = () => {
+ localStorage.clear();
+ };
+ return {
+ get,
+ set,
+ remove,
+ clear,
+ engine: STORAGE_LOCAL_STORAGE,
+ };
+};
+
+const testLocalStorage = () => {
+ // Localstorage can sometimes throw an error, even if DOM storage is not
+ // disabled in IE11 settings.
+ // See: https://superuser.com/questions/1080011
+ try {
+ return Boolean(window.localStorage && window.localStorage.getItem);
+ }
+ catch {
+ return false;
+ }
+};
+
+export const storage = (
+ testLocalStorage() && createLocalStorage()
+ || createMock()
+);
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..f4e26fa5aa 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
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/index.js b/tgui/packages/tgui-dev-server/index.js
index 1e7683080c..f4e8155d29 100644
--- a/tgui/packages/tgui-dev-server/index.js
+++ b/tgui/packages/tgui-dev-server/index.js
@@ -1,3 +1,9 @@
+/**
+ * @file
+ * @copyright 2020 Aleksej Komarov
+ * @license MIT
+ */
+
import { setupWebpack, getWebpackConfig } from './webpack.js';
import { reloadByondCache } from './reloader.js';
diff --git a/tgui/packages/tgui-dev-server/link/client.js b/tgui/packages/tgui-dev-server/link/client.js
index 774b7ed6db..4671b340c5 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 = [];
@@ -92,8 +98,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);
diff --git a/tgui/packages/tgui-dev-server/link/retrace.js b/tgui/packages/tgui-dev-server/link/retrace.js
index 74c87e6a55..e0b17a01d6 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';
diff --git a/tgui/packages/tgui-dev-server/link/server.js b/tgui/packages/tgui-dev-server/link/server.js
index 84f3700048..94a79c9ad5 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';
diff --git a/tgui/packages/tgui-dev-server/reloader.js b/tgui/packages/tgui-dev-server/reloader.js
index 394e55afdb..e33f7226b9 100644
--- a/tgui/packages/tgui-dev-server/reloader.js
+++ b/tgui/packages/tgui-dev-server/reloader.js
@@ -1,9 +1,16 @@
+/**
+ * @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';
const logger = createLogger('reloader');
@@ -40,6 +47,21 @@ export const findCacheRoot = async () => {
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';
+ logger.log(`found cache at '${cacheRoot}'`);
+ return cacheRoot;
+ }
+ }
logger.log('found no cache directories');
};
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..c625827409 100644
--- a/tgui/packages/tgui-dev-server/webpack.js
+++ b/tgui/packages/tgui-dev-server/webpack.js
@@ -1,3 +1,9 @@
+/**
+ * @file
+ * @copyright 2020 Aleksej Komarov
+ * @license MIT
+ */
+
import { createLogger } from 'common/logging.js';
import fs from 'fs';
import { createRequire } from 'module';
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/assets.js b/tgui/packages/tgui/assets.js
new file mode 100644
index 0000000000..b0f71bb9ff
--- /dev/null
+++ b/tgui/packages/tgui/assets.js
@@ -0,0 +1,54 @@
+/**
+ * @file
+ * @copyright 2020 Aleksej Komarov
+ * @license MIT
+ */
+
+import { loadCSS as fgLoadCSS } from 'fg-loadcss';
+import { createLogger } from './logging';
+
+const logger = createLogger('assets');
+
+const EXCLUDED_PATTERNS = [
+ /v4shim/i,
+];
+
+const loadedStyles = [];
+const loadedMappings = {};
+
+export const loadCSS = url => {
+ if (loadedStyles.includes(url)) {
+ return;
+ }
+ loadedStyles.push(url);
+ logger.log(`loading stylesheet '${url}'`);
+ fgLoadCSS(url);
+};
+
+export const resolveAsset = name => (
+ loadedMappings[name] || name
+);
+
+export const assetMiddleware = store => next => action => {
+ const { type, payload } = action;
+ if (type === 'asset/stylesheet') {
+ loadCSS(payload);
+ return;
+ }
+ if (type === 'asset/mappings') {
+ for (let name of Object.keys(payload)) {
+ // Skip anything that matches excluded patterns
+ if (EXCLUDED_PATTERNS.some(regex => regex.test(name))) {
+ continue;
+ }
+ const url = payload[name];
+ const ext = name.split('.').pop();
+ loadedMappings[name] = url;
+ if (ext === 'css') {
+ loadCSS(url);
+ }
+ }
+ return;
+ }
+ next(action);
+};
diff --git a/tgui/packages/tgui/assets/bg-neutral.svg b/tgui/packages/tgui/assets/bg-neutral.svg
new file mode 100644
index 0000000000..1c397616e8
--- /dev/null
+++ b/tgui/packages/tgui/assets/bg-neutral.svg
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/tgui/packages/tgui/backend.js b/tgui/packages/tgui/backend.js
index 0c35483487..dab86ee918 100644
--- a/tgui/packages/tgui/backend.js
+++ b/tgui/packages/tgui/backend.js
@@ -5,10 +5,18 @@
* Sometimes backend can response without a "data" field, but our final
* state will still contain previous "data" because we are merging
* the response with already existing state.
+ *
+ * @file
+ * @copyright 2020 Aleksej Komarov
+ * @license MIT
*/
+import { perf } from 'common/perf';
import { UI_DISABLED, UI_INTERACTIVE } from './constants';
-import { callByond } from './byond';
+import { releaseHeldKeys } from './hotkeys';
+import { createLogger } from './logging';
+
+const logger = createLogger('backend');
export const backendUpdate = state => ({
type: 'backend/update',
@@ -20,7 +28,23 @@ export const backendSetSharedState = (key, nextState) => ({
payload: { key, nextState },
});
-export const backendReducer = (state, action) => {
+export const backendSuspendStart = () => ({
+ type: 'backend/suspendStart',
+});
+
+export const backendSuspendSuccess = () => ({
+ type: 'backend/suspendSuccess',
+ payload: {
+ timestamp: Date.now(),
+ },
+});
+
+const initialState = {
+ config: {},
+ data: {},
+};
+
+export const backendReducer = (state = initialState, action) => {
const { type, payload } = action;
if (type === 'backend/update') {
@@ -59,6 +83,7 @@ export const backendReducer = (state, action) => {
shared,
visible,
interactive,
+ suspended: false,
};
}
@@ -73,30 +98,175 @@ export const backendReducer = (state, action) => {
};
}
+ if (type === 'backend/suspendStart') {
+ return {
+ ...state,
+ suspending: true,
+ };
+ }
+
+ if (type === 'backend/suspendSuccess') {
+ const { timestamp } = payload;
+ return {
+ ...state,
+ data: {},
+ shared: {},
+ config: {
+ ...state.config,
+ title: '',
+ status: 1,
+ },
+ suspending: false,
+ suspended: timestamp,
+ };
+ }
+
return state;
};
+export const backendMiddleware = store => {
+ let fancyState;
+ let suspendInterval;
+
+ return next => action => {
+ const { config, suspended } = selectBackend(store.getState());
+ const { type, payload } = action;
+
+ if (type === 'backend/suspendStart' && !suspendInterval) {
+ logger.log(`suspending (${window.__windowId__})`);
+ // Keep sending suspend messages until it succeeds.
+ // It may fail multiple times due to topic rate limiting.
+ const suspendFn = () => sendMessage({
+ type: 'suspend',
+ });
+ suspendFn();
+ suspendInterval = setInterval(suspendFn, 2000);
+ }
+
+ if (type === 'backend/suspendSuccess') {
+ clearInterval(suspendInterval);
+ suspendInterval = undefined;
+ releaseHeldKeys();
+ Byond.winset(window.__windowId__, {
+ 'is-visible': false,
+ });
+ }
+
+ if (type === 'backend/update') {
+ const fancy = payload.config?.window?.fancy;
+ // Initialize fancy state
+ if (fancyState === undefined) {
+ fancyState = fancy;
+ }
+ // React to changes in fancy
+ else if (fancyState !== fancy) {
+ logger.log('changing fancy mode to', fancy);
+ fancyState = fancy;
+ Byond.winset(window.__windowId__, {
+ titlebar: !fancy,
+ 'can-resize': !fancy,
+ });
+ }
+ }
+
+ if (type === 'backend/update' && suspended) {
+ // We schedule this for the next tick here because resizing and unhiding
+ // during the same tick will flash with a white background.
+ setImmediate(() => {
+ perf.mark('resume/start');
+ // Doublecheck if we are not re-suspended.
+ const { suspended } = selectBackend(store.getState());
+ if (suspended) {
+ return;
+ }
+ Byond.winset(window.__windowId__, {
+ 'is-visible': true,
+ });
+ perf.mark('resume/finish');
+ if (process.env.NODE_ENV !== 'production') {
+ logger.log('visible in',
+ perf.measure('render/finish', 'resume/finish'));
+ }
+ });
+ }
+
+ return next(action);
+ };
+};
+
+/**
+ * Sends a message to /datum/tgui_window.
+ */
+export const sendMessage = (message = {}) => {
+ const { payload, ...rest } = message;
+ const data = {
+ // Message identifying header
+ tgui: 1,
+ window_id: window.__windowId__,
+ // Message body
+ ...rest,
+ };
+ // JSON-encode the payload
+ if (payload !== null && payload !== undefined) {
+ data.payload = JSON.stringify(payload);
+ }
+ Byond.topic(data);
+};
+
+/**
+ * Sends an action to `ui_act` on `src_object` that this tgui window
+ * is associated with.
+ */
+export const sendAct = (action, payload = {}) => {
+ // Validate that payload is an object
+ const isObject = typeof payload === 'object'
+ && payload !== null
+ && !Array.isArray(payload);
+ if (!isObject) {
+ logger.error(`Payload for act() must be an object, got this:`, payload);
+ return;
+ }
+ sendMessage({
+ type: 'act/' + action,
+ payload,
+ });
+};
+
/**
* @typedef BackendState
* @type {{
* config: {
* title: string,
* status: number,
- * screen: string,
- * style: string,
* interface: string,
- * fancy: number,
- * locked: number,
- * observer: number,
- * window: string,
- * ref: string,
+ * user: {
+ * name: string,
+ * ckey: string,
+ * observer: number,
+ * },
+ * window: {
+ * key: string,
+ * size: [number, number],
+ * fancy: boolean,
+ * locked: boolean,
+ * },
* },
* data: any,
+ * shared: any,
* visible: boolean,
* interactive: boolean,
+ * suspending: boolean,
+ * suspended: boolean,
* }}
*/
+/**
+ * Selects a backend-related slice of Redux state
+ *
+ * @return {BackendState}
+ */
+export const selectBackend = state => state.backend || {};
+
/**
* A React hook (sort of) for getting tgui state and related functions.
*
@@ -104,21 +274,16 @@ export const backendReducer = (state, action) => {
* be used in functional components.
*
* @return {BackendState & {
- * act: (action: string, params?: object) => void,
+ * act: sendAct,
* }}
*/
export const useBackend = context => {
const { store } = context;
- const state = store.getState();
- const ref = state.config.ref;
- const act = (action, params = {}) => {
- callByond('', {
- src: ref,
- action,
- ...params,
- });
+ const state = selectBackend(store.getState());
+ return {
+ ...state,
+ act: sendAct,
};
- return { ...state, act };
};
/**
@@ -136,7 +301,7 @@ export const useBackend = context => {
*/
export const useLocalState = (context, key, initialState) => {
const { store } = context;
- const state = store.getState();
+ const state = selectBackend(store.getState());
const sharedStates = state.shared ?? {};
const sharedState = (key in sharedStates)
? sharedStates[key]
@@ -144,7 +309,11 @@ export const useLocalState = (context, key, initialState) => {
return [
sharedState,
nextState => {
- store.dispatch(backendSetSharedState(key, nextState));
+ store.dispatch(backendSetSharedState(key, (
+ typeof nextState === 'function'
+ ? nextState(sharedState)
+ : nextState
+ )));
},
];
};
@@ -165,8 +334,7 @@ export const useLocalState = (context, key, initialState) => {
*/
export const useSharedState = (context, key, initialState) => {
const { store } = context;
- const state = store.getState();
- const ref = state.config.ref;
+ const state = selectBackend(store.getState());
const sharedStates = state.shared ?? {};
const sharedState = (key in sharedStates)
? sharedStates[key]
@@ -174,11 +342,14 @@ export const useSharedState = (context, key, initialState) => {
return [
sharedState,
nextState => {
- callByond('', {
- src: ref,
- action: 'tgui:setSharedState',
+ sendMessage({
+ type: 'setSharedState',
key,
- value: JSON.stringify(nextState) || '',
+ value: JSON.stringify(
+ typeof nextState === 'function'
+ ? nextState(sharedState)
+ : nextState
+ ) || '',
});
},
];
diff --git a/tgui/packages/tgui/byond.js b/tgui/packages/tgui/byond.js
index bd70056613..fa2b03e715 100644
--- a/tgui/packages/tgui/byond.js
+++ b/tgui/packages/tgui/byond.js
@@ -1,3 +1,9 @@
+/**
+ * @file
+ * @copyright 2020 Aleksej Komarov
+ * @license MIT
+ */
+
// Reference a global Byond object
const { Byond } = window;
diff --git a/tgui/packages/tgui/components/Box.js b/tgui/packages/tgui/components/Box.js
index cb86890a97..a85c692e9a 100644
--- a/tgui/packages/tgui/components/Box.js
+++ b/tgui/packages/tgui/components/Box.js
@@ -9,17 +9,22 @@ import { createVNode } from 'inferno';
import { ChildFlags, VNodeFlags } from 'inferno-vnode-flags';
import { CSS_COLORS } from '../constants';
-const UNIT_PX = 12;
-
/**
* Coverts our rem-like spacing unit into a CSS unit.
*/
export const unit = value => {
if (typeof value === 'string') {
+ // Transparently convert pixels into rem units
+ if (value.endsWith('px') && !Byond.IS_LTE_IE8) {
+ return parseFloat(value) / 12 + 'rem';
+ }
return value;
}
if (typeof value === 'number') {
- return (value * UNIT_PX) + 'px';
+ if (Byond.IS_LTE_IE8) {
+ return value * 12 + 'px';
+ }
+ return value + 'rem';
}
};
@@ -28,10 +33,10 @@ export const unit = value => {
*/
export const halfUnit = value => {
if (typeof value === 'string') {
- return value;
+ return unit(value);
}
if (typeof value === 'number') {
- return (value * UNIT_PX * 0.5) + 'px';
+ return unit(value * 0.5);
}
};
@@ -90,7 +95,13 @@ const styleMapperByPropName = {
maxHeight: mapUnitPropTo('max-height', unit),
fontSize: mapUnitPropTo('font-size', unit),
fontFamily: mapRawPropTo('font-family'),
- lineHeight: mapRawPropTo('line-height'),
+ lineHeight: (style, value) => {
+ if (!isFalsy(value)) {
+ style['line-height'] = typeof value === 'number'
+ ? value
+ : unit(value);
+ }
+ },
opacity: mapRawPropTo('opacity'),
textAlign: mapRawPropTo('text-align'),
verticalAlign: mapRawPropTo('vertical-align'),
diff --git a/tgui/packages/tgui/components/Button.js b/tgui/packages/tgui/components/Button.js
index 52adf286ad..bb8b4bcbf0 100644
--- a/tgui/packages/tgui/components/Button.js
+++ b/tgui/packages/tgui/components/Button.js
@@ -6,7 +6,6 @@
import { classes, pureComponentHooks } from 'common/react';
import { Component, createRef } from 'inferno';
-import { IS_IE8 } from '../byond';
import { KEY_ENTER, KEY_ESCAPE, KEY_SPACE } from '../hotkeys';
import { refocusLayout } from '../layouts';
import { createLogger } from '../logging';
@@ -61,7 +60,7 @@ export const Button = props => {
className,
])}
tabIndex={!disabled && '0'}
- unselectable={IS_IE8}
+ unselectable={Byond.IS_LTE_IE8}
onclick={e => {
refocusLayout();
if (!disabled && onClick) {
@@ -87,7 +86,10 @@ export const Button = props => {
}}
{...rest}>
{icon && (
-
+
)}
{content}
{children}
diff --git a/tgui/packages/tgui/components/ByondUi.js b/tgui/packages/tgui/components/ByondUi.js
index db9c5822ba..2369cc7993 100644
--- a/tgui/packages/tgui/components/ByondUi.js
+++ b/tgui/packages/tgui/components/ByondUi.js
@@ -7,7 +7,6 @@
import { shallowDiffers } from 'common/react';
import { debounce } from 'common/timer';
import { Component, createRef } from 'inferno';
-import { callByond, IS_IE8 } from '../byond';
import { createLogger } from '../logging';
import { computeBoxProps } from './Box';
@@ -28,16 +27,12 @@ const createByondUiElement = elementId => {
render: params => {
logger.log(`rendering '${id}'`);
byondUiStack[index] = id;
- callByond('winset', {
- ...params,
- id,
- });
+ Byond.winset(id, params);
},
unmount: () => {
logger.log(`unmounting '${id}'`);
byondUiStack[index] = null;
- callByond('winset', {
- id,
+ Byond.winset(id, {
parent: '',
});
},
@@ -51,8 +46,7 @@ window.addEventListener('beforeunload', () => {
if (typeof id === 'string') {
logger.log(`unmounting '${id}' (beforeunload)`);
byondUiStack[index] = null;
- callByond('winset', {
- id,
+ Byond.winset(id, {
parent: '',
});
}
@@ -83,7 +77,7 @@ export class ByondUi extends Component {
this.byondUiElement = createByondUiElement(props.params?.id);
this.handleResize = debounce(() => {
this.forceUpdate();
- }, 500);
+ }, 100);
}
shouldComponentUpdate(nextProps) {
@@ -101,16 +95,17 @@ export class ByondUi extends Component {
componentDidMount() {
// IE8: It probably works, but fuck you anyway.
- if (IS_IE8) {
+ if (Byond.IS_LTE_IE10) {
return;
}
window.addEventListener('resize', this.handleResize);
- return this.componentDidUpdate();
+ this.componentDidUpdate();
+ this.handleResize();
}
componentDidUpdate() {
// IE8: It probably works, but fuck you anyway.
- if (IS_IE8) {
+ if (Byond.IS_LTE_IE10) {
return;
}
const {
@@ -119,6 +114,7 @@ export class ByondUi extends Component {
const box = getBoundingBox(this.containerRef.current);
logger.log('bounding box', box);
this.byondUiElement.render({
+ parent: window.__windowId__,
...params,
pos: box.pos[0] + ',' + box.pos[1],
size: box.size[0] + 'x' + box.size[1],
@@ -127,7 +123,7 @@ export class ByondUi extends Component {
componentWillUnmount() {
// IE8: It probably works, but fuck you anyway.
- if (IS_IE8) {
+ if (Byond.IS_LTE_IE10) {
return;
}
window.removeEventListener('resize', this.handleResize);
@@ -135,26 +131,16 @@ export class ByondUi extends Component {
}
render() {
- const {
- parent,
- params,
- ...rest
- } = this.props;
+ const { params, ...rest } = this.props;
const type = params?.type;
const boxProps = computeBoxProps(rest);
return (
- {type === 'button' && }
+ {/* Filler */}
+
);
}
}
-
-const ButtonMock = () => (
-
-);
diff --git a/tgui/packages/tgui/components/Chart.js b/tgui/packages/tgui/components/Chart.js
index 16ed82c355..77913779db 100644
--- a/tgui/packages/tgui/components/Chart.js
+++ b/tgui/packages/tgui/components/Chart.js
@@ -7,7 +7,6 @@
import { map, zipWith } from 'common/collections';
import { pureComponentHooks } from 'common/react';
import { Component, createRef } from 'inferno';
-import { IS_IE8 } from '../byond';
import { Box } from './Box';
const normalizeData = (data, scale, rangeX, rangeY) => {
@@ -123,5 +122,5 @@ const Stub = props => null;
// IE8: No inline svg support
export const Chart = {
- Line: IS_IE8 ? Stub : LineChart,
+ Line: Byond.IS_LTE_IE8 ? Stub : LineChart,
};
diff --git a/tgui/packages/tgui/components/Flex.js b/tgui/packages/tgui/components/Flex.js
index 4ee69a1902..02d2fac314 100644
--- a/tgui/packages/tgui/components/Flex.js
+++ b/tgui/packages/tgui/components/Flex.js
@@ -5,7 +5,6 @@
*/
import { classes, pureComponentHooks } from 'common/react';
-import { IS_IE8 } from '../byond';
import { Box, unit } from './Box';
export const computeFlexProps = props => {
@@ -22,10 +21,10 @@ export const computeFlexProps = props => {
return {
className: classes([
'Flex',
- IS_IE8 && (
+ Byond.IS_LTE_IE10 && (
direction === 'column'
- ? 'Flex--ie8--column'
- : 'Flex--ie8'
+ ? 'Flex--iefix--column'
+ : 'Flex--iefix'
),
inline && 'Flex--inline',
spacing > 0 && 'Flex--spacing--' + spacing,
@@ -63,7 +62,7 @@ export const computeFlexItemProps = props => {
return {
className: classes([
'Flex__item',
- IS_IE8 && 'Flex__item--ie8',
+ Byond.IS_LTE_IE10 && 'Flex__item--iefix',
className,
]),
style: {
diff --git a/tgui/packages/tgui/components/Knob.js b/tgui/packages/tgui/components/Knob.js
index e72b15f195..4861e71bf3 100644
--- a/tgui/packages/tgui/components/Knob.js
+++ b/tgui/packages/tgui/components/Knob.js
@@ -6,7 +6,6 @@
import { keyOfMatchingRange, scale } from 'common/math';
import { classes } from 'common/react';
-import { IS_IE8 } from '../byond';
import { computeBoxClassName, computeBoxProps } from './Box';
import { DraggableControl } from './DraggableControl';
import { NumberInput } from './NumberInput';
@@ -14,7 +13,7 @@ import { NumberInput } from './NumberInput';
export const Knob = props => {
// IE8: I don't want to support a yet another component on IE8.
// IE8: It also can't handle SVG.
- if (IS_IE8) {
+ if (Byond.IS_LTE_IE8) {
return (
);
diff --git a/tgui/packages/tgui/components/NumberInput.js b/tgui/packages/tgui/components/NumberInput.js
index 806c81d542..cba6f5025e 100644
--- a/tgui/packages/tgui/components/NumberInput.js
+++ b/tgui/packages/tgui/components/NumberInput.js
@@ -7,7 +7,6 @@
import { clamp } from 'common/math';
import { classes, pureComponentHooks } from 'common/react';
import { Component, createRef } from 'inferno';
-import { IS_IE8 } from '../byond';
import { AnimatedNumber } from './AnimatedNumber';
import { Box } from './Box';
@@ -168,7 +167,7 @@ export class NumberInput extends Component {
const renderContentElement = value => (