"
- 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/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 7c30381161..1b6e1f9748 100644
--- a/code/modules/client/preferences.dm
+++ b/code/modules/client/preferences.dm
@@ -1153,7 +1153,6 @@ GLOBAL_LIST_EMPTY(preferences_datums)
dat += "Breast Enlargement:[(cit_toggles & BREAST_ENLARGEMENT) ? "Allowed" : "Disallowed"] "
dat += "Penis Enlargement:[(cit_toggles & PENIS_ENLARGEMENT) ? "Allowed" : "Disallowed"] "
dat += "Hypno:[(cit_toggles & NEVER_HYPNO) ? "Disallowed" : "Allowed"] "
- dat += "Aphrodisiacs:[(cit_toggles & NO_APHRO) ? "Disallowed" : "Allowed"] "
dat += "Ass Slapping:[(cit_toggles & NO_ASS_SLAP) ? "Disallowed" : "Allowed"] "
dat += "Automatic Wagging:[(cit_toggles & NO_AUTO_WAG) ? "Disabled" : "Enabled"] "
dat += ""
diff --git a/code/modules/clothing/glasses/engine_goggles.dm b/code/modules/clothing/glasses/engine_goggles.dm
index 2a64445776..d7e7ae3669 100644
--- a/code/modules/clothing/glasses/engine_goggles.dm
+++ b/code/modules/clothing/glasses/engine_goggles.dm
@@ -97,14 +97,14 @@
if(get_dist(user, place) >= range*8) //Rads are easier to see than wires under the floor
continue
var/strength = round(rad_places[i] / 1000, 0.1)
- var/image/pic = new(loc = place)
+ var/image/pic = image(loc = place)
var/mutable_appearance/MA = new()
- MA.alpha = 180
- MA.maptext = "[strength]k"
- MA.color = "#64C864"
- MA.layer = FLY_LAYER
+ MA.maptext = "[strength]k"
+ MA.color = "#04e604"
+ MA.layer = RAD_TEXT_LAYER
+ MA.plane = GAME_PLANE
pic.appearance = MA
- flick_overlay(pic, list(user.client), 8)
+ flick_overlay(pic, list(user.client), 10)
/obj/item/clothing/glasses/meson/engine/proc/show_shuttle()
var/mob/living/carbon/human/user = loc
diff --git a/code/modules/events/immovable_rod.dm b/code/modules/events/immovable_rod.dm
index 89511a7b15..06318df4f5 100644
--- a/code/modules/events/immovable_rod.dm
+++ b/code/modules/events/immovable_rod.dm
@@ -42,6 +42,7 @@ In my current plan for it, 'solid' will be defined as anything with density == 1
name = "immovable rod"
desc = "What the fuck is that?"
icon = 'icons/obj/objects.dmi'
+ movement_type = FLOATING
icon_state = "immrod"
throwforce = 100
move_force = INFINITY
diff --git a/code/modules/events/pirates.dm b/code/modules/events/pirates.dm
index f1803b03ee..af44b54ed0 100644
--- a/code/modules/events/pirates.dm
+++ b/code/modules/events/pirates.dm
@@ -95,14 +95,11 @@
icon_state = "dominator"
density = TRUE
var/active = FALSE
- var/obj/item/gps/gps
var/credits_stored = 0
var/siphon_per_tick = 5
/obj/machinery/shuttle_scrambler/Initialize(mapload)
. = ..()
- gps = new/obj/item/gps/internal/pirate(src)
- gps.tracking = FALSE
update_icon()
/obj/machinery/shuttle_scrambler/process()
@@ -112,6 +109,7 @@
if(D)
var/siphoned = min(D.account_balance,siphon_per_tick)
D.adjust_money(-siphoned)
+ credits_stored += siphoned
interrupt_research()
else
return
@@ -120,7 +118,7 @@
/obj/machinery/shuttle_scrambler/proc/toggle_on(mob/user)
SSshuttle.registerTradeBlockade(src)
- gps.tracking = TRUE
+ AddComponent(/datum/component/gps, "Nautical Signal")
active = TRUE
to_chat(user,"You toggle [src] [active ? "on":"off"].")
to_chat(user,"The scrambling signal can be now tracked by GPS.")
@@ -130,7 +128,7 @@
if(!active)
if(alert(user, "Turning the scrambler on will make the shuttle trackable by GPS. Are you sure you want to do it?", "Scrambler", "Yes", "Cancel") == "Cancel")
return
- if(active || !user.canUseTopic(src))
+ if(active || !user.canUseTopic(src, BE_CLOSE))
return
toggle_on(user)
update_icon()
@@ -147,35 +145,31 @@
new /obj/effect/temp_visual/emp(get_turf(S))
/obj/machinery/shuttle_scrambler/proc/dump_loot(mob/user)
- new /obj/item/holochip(drop_location(), credits_stored)
- to_chat(user,"You retrieve the siphoned credits!")
- credits_stored = 0
+ if(credits_stored) // Prevents spamming empty holochips
+ new /obj/item/holochip(drop_location(), credits_stored)
+ to_chat(user,"You retrieve the siphoned credits!")
+ credits_stored = 0
+ else
+ to_chat(user,"There's nothing to withdraw.")
/obj/machinery/shuttle_scrambler/proc/send_notification()
priority_announce("Data theft signal detected, source registered on local gps units.")
/obj/machinery/shuttle_scrambler/proc/toggle_off(mob/user)
SSshuttle.clearTradeBlockade(src)
- gps.tracking = FALSE
active = FALSE
STOP_PROCESSING(SSobj,src)
-/obj/machinery/shuttle_scrambler/update_overlays()
- . = ..()
+/obj/machinery/shuttle_scrambler/update_icon_state()
if(active)
- var/mutable_appearance/M = mutable_appearance(icon, "dominator-overlay")
- M.color = "#00FFFF"
- . += M
+ icon_state = "dominator-blue"
+ else
+ icon_state = "dominator"
/obj/machinery/shuttle_scrambler/Destroy()
toggle_off()
- QDEL_NULL(gps)
return ..()
-/obj/item/gps/internal/pirate
- gpstag = "Nautical Signal"
- desc = "You can hear shanties over the static."
-
/obj/machinery/computer/shuttle/pirate
name = "pirate shuttle console"
shuttleId = "pirateship"
@@ -312,11 +306,10 @@
else
pad = locate() in range(4,src)
-/obj/machinery/computer/piratepad_control/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \
- datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state)
- ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+/obj/machinery/computer/piratepad_control/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
- ui = new(user, src, ui_key, "CargoHoldTerminal", name, ui_x, ui_y, master_ui, state)
+ ui = new(user, src, "CargoHoldTerminal", name)
ui.open()
/obj/machinery/computer/piratepad_control/ui_data(mob/user)
diff --git a/code/modules/food_and_drinks/food/snacks/meat.dm b/code/modules/food_and_drinks/food/snacks/meat.dm
index fe70739ab5..4d287fdb86 100644
--- a/code/modules/food_and_drinks/food/snacks/meat.dm
+++ b/code/modules/food_and_drinks/food/snacks/meat.dm
@@ -162,6 +162,14 @@
tastes = list("brains" = 1, "meat" = 1)
foodtype = RAW | MEAT | TOXIC
+/obj/item/reagent_containers/food/snacks/meat/slab/human/mutant/ethereal
+ icon_state = "etherealmeat"
+ desc = "So shiny you feel like ingesting it might make you shine too"
+ filling_color = "#97ee63"
+ list_reagents = list(/datum/reagent/consumable/liquidelectricity = 3)
+ tastes = list("pure electricity" = 2, "meat" = 1)
+ foodtype = RAW | MEAT | TOXIC
+
/obj/item/reagent_containers/food/snacks/carpmeat/aquatic
name = "fillet"
desc = "A fillet of one of the local water dwelling species."
diff --git a/code/modules/food_and_drinks/food/snacks_meat.dm b/code/modules/food_and_drinks/food/snacks_meat.dm
index 2a4139dbb9..05a0da2793 100644
--- a/code/modules/food_and_drinks/food/snacks_meat.dm
+++ b/code/modules/food_and_drinks/food/snacks_meat.dm
@@ -139,11 +139,22 @@
tastes = list("meat" = 1, "salmon" = 1)
foodtype = MEAT | ALCOHOL
+/obj/item/reagent_containers/food/snacks/rawmeatball
+ name = "raw meatball"
+ desc = "Raw mushy meat. Better cook this!"
+ icon_state = "rawmeatball"
+ cooked_type = /obj/item/reagent_containers/food/snacks/meatball
+ list_reagents = list(/datum/reagent/consumable/nutriment = 3)
+ filling_color = "#bd2020"
+ tastes = list("meat" = 1, "slime" = 1)
+ foodtype = MEAT | RAW
+
/obj/item/reagent_containers/food/snacks/meatball
name = "meatball"
desc = "MAMA MIA DAS A SPICY"
icon_state = "meatball"
list_reagents = list(/datum/reagent/consumable/nutriment = 4, /datum/reagent/consumable/nutriment/vitamin = 1)
+ bonus_reagents = list(/datum/reagent/consumable/nutriment = 1, /datum/reagent/consumable/nutriment/vitamin = 1)
filling_color = "#800000"
tastes = list("meat" = 1)
foodtype = MEAT
diff --git a/code/modules/food_and_drinks/food/snacks_vend.dm b/code/modules/food_and_drinks/food/snacks_vend.dm
index 38f7ecf5b1..b4c7c89b74 100644
--- a/code/modules/food_and_drinks/food/snacks_vend.dm
+++ b/code/modules/food_and_drinks/food/snacks_vend.dm
@@ -91,3 +91,13 @@
tastes = list("sweetness" = 3, "cake" = 1)
foodtype = GRAIN | FRUIT | VEGETABLES
custom_price = PRICE_CHEAP
+
+/obj/item/reagent_containers/food/snacks/energybar
+ name = "High-power energy bars"
+ icon_state = "energybar"
+ desc = "An energy bar with a lot of punch, you probably shouldn't eat this if you're not an Ethereal."
+ trash = /obj/item/trash/energybar
+ list_reagents = list(/datum/reagent/consumable/nutriment = 3, /datum/reagent/consumable/liquidelectricity = 3)
+ filling_color = "#97ee63"
+ tastes = list("pure electricity" = 3, "fitness" = 2)
+ foodtype = TOXIC
diff --git a/code/modules/food_and_drinks/kitchen_machinery/smartfridge.dm b/code/modules/food_and_drinks/kitchen_machinery/smartfridge.dm
index 36e3b64487..35fa40e15d 100644
--- a/code/modules/food_and_drinks/kitchen_machinery/smartfridge.dm
+++ b/code/modules/food_and_drinks/kitchen_machinery/smartfridge.dm
@@ -12,6 +12,7 @@
idle_power_usage = 5
active_power_usage = 100
circuit = /obj/item/circuitboard/machine/smartfridge
+
var/max_n_of_items = 1500
var/allow_ai_retrieve = FALSE
var/list/initial_contents
@@ -38,12 +39,10 @@
if(in_range(user, src) || isobserver(user))
. += "The status display reads: This unit can hold a maximum of [max_n_of_items] items."
-/obj/machinery/smartfridge/power_change()
- ..()
- update_icon()
-
/obj/machinery/smartfridge/update_icon_state()
+ SSvis_overlays.remove_vis_overlay(src, managed_vis_overlays)
if(!stat)
+ SSvis_overlays.add_vis_overlay(src, icon, "smartfridge-light-mask", EMISSIVE_LAYER, EMISSIVE_PLANE, dir, alpha)
if(visible_contents)
switch(contents.len)
if(0)
@@ -66,9 +65,6 @@
********************/
/obj/machinery/smartfridge/attackby(obj/item/O, mob/user, params)
- if(user.a_intent == INTENT_HARM)
- return ..()
-
if(default_deconstruction_screwdriver(user, icon_state, icon_state, O))
cut_overlays()
if(panel_open)
@@ -87,46 +83,53 @@
updateUsrDialog()
return
- if(stat)
- updateUsrDialog()
- return FALSE
+ if(!stat)
- if(contents.len >= max_n_of_items)
- to_chat(user, "\The [src] is full!")
- return FALSE
-
- if(accept_check(O))
- load(O)
- user.visible_message("[user] has added \the [O] to \the [src].", "You add \the [O] to \the [src].")
- updateUsrDialog()
- if (visible_contents)
- update_icon()
- return TRUE
-
- if(istype(O, /obj/item/storage/bag))
- var/obj/item/storage/P = O
- var/loaded = 0
- for(var/obj/G in P.contents)
- if(contents.len >= max_n_of_items)
- break
- if(accept_check(G))
- load(G)
- loaded++
- updateUsrDialog()
-
- if(loaded)
- user.visible_message("[user] loads \the [src] with \the [O].", \
- "You [contents.len >= max_n_of_items ? "fill" : "load"] \the [src] with \the [O].")
- if(O.contents.len > 0)
- to_chat(user, "Some items are refused.")
- return TRUE
- else
- to_chat(user, "There is nothing in [O] to put in [src]!")
+ if(contents.len >= max_n_of_items)
+ to_chat(user, "\The [src] is full!")
return FALSE
- to_chat(user, "\The [src] smartly refuses [O].")
- updateUsrDialog()
- return FALSE
+ if(accept_check(O))
+ load(O)
+ user.visible_message("[user] adds \the [O] to \the [src].", "You add \the [O] to \the [src].")
+ updateUsrDialog()
+ if (visible_contents)
+ update_icon()
+ return TRUE
+
+ if(istype(O, /obj/item/storage/bag))
+ var/obj/item/storage/P = O
+ var/loaded = 0
+ for(var/obj/G in P.contents)
+ if(contents.len >= max_n_of_items)
+ break
+ if(accept_check(G))
+ load(G)
+ loaded++
+ updateUsrDialog()
+
+ if(loaded)
+ if(contents.len >= max_n_of_items)
+ user.visible_message("[user] loads \the [src] with \the [O].", \
+ "You fill \the [src] with \the [O].")
+ else
+ user.visible_message("[user] loads \the [src] with \the [O].", \
+ "You load \the [src] with \the [O].")
+ if(O.contents.len > 0)
+ to_chat(user, "Some items are refused.")
+ if (visible_contents)
+ update_icon()
+ return TRUE
+ else
+ to_chat(user, "There is nothing in [O] to put in [src]!")
+ return FALSE
+
+ if(user.a_intent != INTENT_HARM)
+ to_chat(user, "\The [src] smartly refuses [O].")
+ updateUsrDialog()
+ return FALSE
+ else
+ return ..()
@@ -151,16 +154,16 @@
return TRUE
///Really simple proc, just moves the object "O" into the hands of mob "M" if able, done so I could modify the proc a little for the organ fridge
-/obj/machinery/smartfridge/proc/dispense(obj/item/O, var/mob/M)
+/obj/machinery/smartfridge/proc/dispense(obj/item/O, mob/M)
if(!M.put_in_hands(O))
O.forceMove(drop_location())
adjust_item_drop_location(O)
-/obj/machinery/smartfridge/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state)
- ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+/obj/machinery/smartfridge/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
- ui = new(user, src, ui_key, "SmartVend", name, 440, 550, master_ui, state)
+ ui = new(user, src, "SmartVend", name)
ui.set_autoupdate(FALSE)
ui.open()
@@ -232,7 +235,7 @@
// ----------------------------
/obj/machinery/smartfridge/drying_rack
name = "drying rack"
- desc = "A wooden contraption, used to dry plant products, food and leather."
+ desc = "A wooden contraption, used to dry plant products, food and hide."
icon = 'icons/obj/hydroponics/equipment.dmi'
icon_state = "drying_rack"
use_power = IDLE_POWER_USE
@@ -277,6 +280,11 @@
return TRUE
return FALSE
+// /obj/machinery/smartfridge/drying_rack/powered() do we have this? no.
+// if(!anchored)
+// return FALSE
+// return ..()
+
/obj/machinery/smartfridge/drying_rack/power_change()
if(powered() && anchored)
stat &= ~NOPOWER
@@ -285,6 +293,10 @@
toggle_drying(TRUE)
update_icon()
+ // . = ..()
+ // if(!powered())
+ // toggle_drying(TRUE)
+
/obj/machinery/smartfridge/drying_rack/load() //For updating the filled overlay
..()
update_icon()
@@ -308,7 +320,7 @@
var/obj/item/reagent_containers/food/snacks/S = O
if(S.dried_type)
return TRUE
- if(istype(O, /obj/item/stack/sheet/wetleather/))
+ if(istype(O, /obj/item/stack/sheet/wetleather/)) //no wethide
return TRUE
return FALSE
@@ -386,19 +398,19 @@
/obj/machinery/smartfridge/extract/preloaded
initial_contents = list(/obj/item/slime_scanner = 2)
-// ------------------------- You think you're better than Chem, huh?
+// -------------------------
// Organ Surgery Smartfridge
-// ------------------------- Just wait till Tamiorgans
+// -------------------------
/obj/machinery/smartfridge/organ
name = "smart organ storage"
desc = "A refrigerated storage unit for organ storage."
- max_n_of_items = 25 //vastly lower to prevent processing too long
+ max_n_of_items = 20 //vastly lower to prevent processing too long
var/repair_rate = 0
/obj/machinery/smartfridge/organ/accept_check(obj/item/O)
- if(istype(O, /obj/item/organ))
+ if(isorgan(O) || isbodypart(O))
return TRUE
- if(istype(O, /obj/item/reagent_containers/syringe))
+ if(istype(O, /obj/item/reagent_containers/syringe)) //other medical things.
return TRUE
if(istype(O, /obj/item/reagent_containers/glass/bottle))
return TRUE
@@ -410,7 +422,7 @@
. = ..()
if(!.) //if the item loads, clear can_decompose
return
- if(istype(O, /obj/item/organ))
+ if(isorgan(O))
var/obj/item/organ/organ = O
organ.organ_flags |= ORGAN_FROZEN
@@ -426,12 +438,13 @@
return
O.applyOrganDamage(-repair_rate)
-/obj/machinery/smartfridge/organ/Exited(obj/item/organ/AM, atom/newLoc)
+/obj/machinery/smartfridge/organ/Exited(atom/movable/AM, atom/newLoc)
. = ..()
- if(istype(AM))
- AM.organ_flags &= ~ORGAN_FROZEN
+ if(isorgan(AM))
+ var/obj/item/organ/O = AM
+ O.organ_flags &= ~ORGAN_FROZEN
-/obj/machinery/smartfridge/organ/preloaded
+/obj/machinery/smartfridge/organ/preloaded //cit specific??????
initial_contents = list(
/obj/item/reagent_containers/medspray/synthtissue = 1,
/obj/item/reagent_containers/medspray/sterilizine = 1)
@@ -450,6 +463,15 @@
desc = "A refrigerated storage unit for medicine storage."
/obj/machinery/smartfridge/chemistry/accept_check(obj/item/O)
+ var/static/list/chemfridge_typecache = typecacheof(list(
+ /obj/item/reagent_containers/syringe,
+ /obj/item/reagent_containers/glass/bottle,
+ /obj/item/reagent_containers/glass/beaker,
+ /obj/item/reagent_containers/spray,
+ // /obj/item/reagent_containers/medigel,
+ /obj/item/reagent_containers/chem_pack
+ ))
+
if(istype(O, /obj/item/storage/pill_bottle))
if(O.contents.len)
for(var/obj/item/I in O)
@@ -463,7 +485,7 @@
return TRUE
if(!O.reagents || !O.reagents.reagent_list.len) // other empty containers not accepted
return FALSE
- if(istype(O, /obj/item/reagent_containers/syringe) || istype(O, /obj/item/reagent_containers/glass/bottle) || istype(O, /obj/item/reagent_containers/glass/beaker) || istype(O, /obj/item/reagent_containers/spray) || istype(O, /obj/item/reagent_containers/medspray) || istype(O, /obj/item/reagent_containers/chem_pack))
+ if(is_type_in_typecache(O, chemfridge_typecache))
return TRUE
return FALSE
@@ -487,6 +509,7 @@
/obj/item/reagent_containers/glass/bottle/cold = 1,
/obj/item/reagent_containers/glass/bottle/flu_virion = 1,
/obj/item/reagent_containers/glass/bottle/mutagen = 1,
+ /obj/item/reagent_containers/glass/bottle/sugar = 1,
/obj/item/reagent_containers/glass/bottle/plasma = 1,
/obj/item/reagent_containers/glass/bottle/synaptizine = 1,
/obj/item/reagent_containers/glass/bottle/formaldehyde = 1)
@@ -498,8 +521,8 @@
name = "disk compartmentalizer"
desc = "A machine capable of storing a variety of disks. Denoted by most as the DSU (disk storage unit)."
icon_state = "disktoaster"
- visible_contents = FALSE
pass_flags = PASSTABLE
+ visible_contents = FALSE
/obj/machinery/smartfridge/disks/accept_check(obj/item/O)
if(istype(O, /obj/item/disk/))
diff --git a/code/modules/food_and_drinks/recipes/processor_recipes.dm b/code/modules/food_and_drinks/recipes/processor_recipes.dm
index 1e3afd1cf5..f75cf6ef3a 100644
--- a/code/modules/food_and_drinks/recipes/processor_recipes.dm
+++ b/code/modules/food_and_drinks/recipes/processor_recipes.dm
@@ -6,7 +6,7 @@
/datum/food_processor_process/meat
input = /obj/item/reagent_containers/food/snacks/meat/slab
- output = /obj/item/reagent_containers/food/snacks/meatball
+ output = /obj/item/reagent_containers/food/snacks/rawmeatball
/datum/food_processor_process/bacon
input = /obj/item/reagent_containers/food/snacks/meat/rawcutlet
diff --git a/code/modules/holodeck/computer.dm b/code/modules/holodeck/computer.dm
index d72e1b1fc8..e5cd36cad6 100644
--- a/code/modules/holodeck/computer.dm
+++ b/code/modules/holodeck/computer.dm
@@ -2,10 +2,7 @@
Holodeck Update
The on-station holodeck area is of type [holodeck_type].
- All types found in GLOB.holodeck_areas_per_comp_type[src.type], generated on make_datum_references_lists(),
- are loaded into the program cache or emag programs list.
- Paths with their abstract_type variable equal to themselves will be skipped.
-
+ All subtypes of [program_type] are loaded into the program cache or emag programs list.
If init_program is null, a random program will be loaded on startup.
If you don't wish this, set it to the offline program or another of your choosing.
@@ -15,6 +12,7 @@
3) Create a new control console that uses those areas
Non-mapped areas should be skipped but you should probably comment them out anyway.
+ The base of program_type will always be ignored; only subtypes will be loaded.
*/
#define HOLODECK_CD 25
@@ -26,18 +24,20 @@
icon_screen = "holocontrol"
idle_power_usage = 10
active_power_usage = 50
+
var/area/holodeck/linked
var/area/holodeck/program
var/area/holodeck/last_program
var/area/offline_program = /area/holodeck/rec_center/offline
- // Splitting this up allows two holodecks of the same size
- // to use the same source patterns. Y'know, if you want to.
- var/holodeck_type = /area/holodeck/rec_center
-
var/list/program_cache
var/list/emag_programs
+ // Splitting this up allows two holodecks of the same size
+ // to use the same source patterns. Y'know, if you want to.
+ var/holodeck_type = /area/holodeck/rec_center // locate(this) to get the target holodeck
+ var/program_type = /area/holodeck/rec_center // subtypes of this (but not this itself) are loadable programs
+
var/active = FALSE
var/damaged = FALSE
var/list/spawned = list()
@@ -49,41 +49,47 @@
return INITIALIZE_HINT_LATELOAD
/obj/machinery/computer/holodeck/LateInitialize()
- linked = SSholodeck.target_holodeck_area[type]
- offline_program = SSholodeck.offline_programs[type]
+ if(ispath(holodeck_type, /area))
+ linked = pop(get_areas(holodeck_type, FALSE))
+ if(ispath(offline_program, /area))
+ offline_program = pop(get_areas(offline_program), FALSE)
+ // the following is necessary for power reasons
if(!linked || !offline_program)
log_world("No matching holodeck area found")
qdel(src)
return
-
- program_cache = SSholodeck.program_cache[type]
- emag_programs = SSholodeck.emag_program_cache[type]
-
- // the following is necessary for power reasons
- var/area/AS = get_base_area(src)
+ var/area/AS = get_area(src)
if(istype(AS, /area/holodeck))
log_mapping("Holodeck computer cannot be in a holodeck, This would cause circular power dependency.")
qdel(src)
return
else
linked.linked = src
-
+ /*
+ var/area/my_area = get_area(src)
+ if(my_area)
+ linked.power_usage = my_area.power_usage
+ else
+ linked.power_usage = new /list(AREA_USAGE_LEN)
+ */
+ generate_program_list()
load_program(offline_program, FALSE, FALSE)
/obj/machinery/computer/holodeck/Destroy()
emergency_shutdown()
if(linked)
linked.linked = null
+ //linked.power_usage = new /list(AREA_USAGE_LEN)
return ..()
/obj/machinery/computer/holodeck/power_change()
. = ..()
toggle_power(!stat)
-/obj/machinery/computer/holodeck/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/holodeck/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
- ui = new(user, src, ui_key, "Holodeck", name, 400, 500, master_ui, state)
+ ui = new(user, src, "Holodeck", name)
ui.open()
/obj/machinery/computer/holodeck/ui_data(mob/user)
@@ -107,19 +113,27 @@
var/program_to_load = text2path(params["type"])
if(!ispath(program_to_load))
return FALSE
+ var/valid = FALSE
+ var/list/checked = program_cache.Copy()
+ if(obj_flags & EMAGGED)
+ checked |= emag_programs
+ for(var/prog in checked)
+ var/list/P = prog
+ if(P["type"] == program_to_load)
+ valid = TRUE
+ break
+ if(!valid)
+ return FALSE
+
var/area/A = locate(program_to_load) in GLOB.sortedAreas
if(A)
load_program(A)
if("safety")
- if(!hasSiliconAccessInArea(usr) && !IsAdminGhost(usr))
- var/msg = "[key_name(usr)] attempted to emag the holodeck using a href they shouldn't have!"
- message_admins(msg)
- log_admin(msg)
- return
- obj_flags ^= EMAGGED
- if((obj_flags & EMAGGED) && program && emag_programs[program.name])
+ if((obj_flags & EMAGGED) && program)
emergency_shutdown()
nerf(obj_flags & EMAGGED)
+ obj_flags ^= EMAGGED
+ say("Safeties restored. Restarting...")
/obj/machinery/computer/holodeck/process()
if(damaged && prob(10))
@@ -160,13 +174,12 @@
if(!LAZYLEN(emag_programs))
to_chat(user, "[src] does not seem to have a card swipe port. It must be an inferior model.")
return
- playsound(src, "sparks", 75, 1)
+ playsound(src, "sparks", 75, TRUE)
obj_flags |= EMAGGED
to_chat(user, "You vastly increase projector power and override the safety and security protocols.")
- to_chat(user, "Warning. Automatic shutoff and derezing protocols have been corrupted. Please call Nanotrasen maintenance and do not use the simulator.")
+ say("Warning. Automatic shutoff and derezzing protocols have been corrupted. Please call Nanotrasen maintenance and do not use the simulator.")
log_game("[key_name(user)] emagged the Holodeck Control Console")
nerf(!(obj_flags & EMAGGED))
- return TRUE
/obj/machinery/computer/holodeck/emp_act(severity)
. = ..()
@@ -182,6 +195,19 @@
emergency_shutdown()
return ..()
+/obj/machinery/computer/holodeck/proc/generate_program_list()
+ for(var/typekey in subtypesof(program_type))
+ var/area/holodeck/A = GLOB.areas_by_type[typekey]
+ if(!A || !A.contents.len)
+ continue
+ var/list/info_this = list()
+ info_this["name"] = A.name
+ info_this["type"] = A.type
+ if(A.restricted)
+ LAZYADD(emag_programs, list(info_this))
+ else
+ LAZYADD(program_cache, list(info_this))
+
/obj/machinery/computer/holodeck/proc/toggle_power(toggleOn = FALSE)
if(active == toggleOn)
return
@@ -281,7 +307,7 @@
silent = FALSE // otherwise make sure they are dropped
if(!silent)
- visible_message("[O] fades away!")
+ visible_message("[O] fades away!")
qdel(O)
#undef HOLODECK_CD
diff --git a/code/modules/hydroponics/biogenerator.dm b/code/modules/hydroponics/biogenerator.dm
index 06d2b3efda..b61d9451ef 100644
--- a/code/modules/hydroponics/biogenerator.dm
+++ b/code/modules/hydroponics/biogenerator.dm
@@ -10,13 +10,13 @@
var/processing = FALSE
var/obj/item/reagent_containers/glass/beaker = null
var/points = 0
- var/menustat = "menu"
var/efficiency = 0
var/productivity = 0
var/max_items = 40
var/datum/techweb/stored_research
var/list/show_categories = list("Food", "Botany Chemicals", "Organic Materials")
- var/list/timesFiveCategories = list("Food", "Botany Chemicals")
+ /// Currently selected category in the UI
+ var/selected_cat
/obj/machinery/biogenerator/Initialize()
. = ..()
@@ -37,22 +37,20 @@
if(A == beaker)
beaker = null
update_icon()
- updateUsrDialog()
/obj/machinery/biogenerator/RefreshParts()
- var/E = 0.5
- var/P = 0.5
- var/max_storage = 20
+ var/E = 0
+ var/P = 0
+ var/max_storage = 40
for(var/obj/item/stock_parts/matter_bin/B in component_parts)
- P += B.rating * 0.5
- max_storage = max(20 * B.rating, max_storage)
+ P += B.rating
+ max_storage = 40 * B.rating
for(var/obj/item/stock_parts/manipulator/M in component_parts)
- E += M.rating * 0.5
+ E += M.rating
efficiency = E
productivity = P
max_items = max_storage
-
/obj/machinery/biogenerator/examine(mob/user)
. = ..()
if(in_range(user, src) || isobserver(user))
@@ -70,7 +68,6 @@
icon_state = "biogen-stand"
else
icon_state = "biogen-work"
- return
/obj/machinery/biogenerator/attackby(obj/item/O, mob/user, params)
if(user.a_intent == INTENT_HARM)
@@ -102,7 +99,6 @@
beaker = O
to_chat(user, "You add the container to the machine.")
update_icon()
- updateUsrDialog()
else
to_chat(user, "Close the maintenance panel first.")
return
@@ -139,9 +135,9 @@
to_chat(user, "You put [O.name] in [src.name]")
return TRUE //no afterattack
else if (istype(O, /obj/item/disk/design_disk))
- user.visible_message("[user] begins to load \the [O] in \the [src]...",
- "You begin to load a design from \the [O]...",
- "You hear the chatter of a floppy drive.")
+ user.visible_message("[user] begins to load \the [O] in \the [src]...",
+ "You begin to load a design from \the [O]...",
+ "You hear the chatter of a floppy drive.")
processing = TRUE
var/obj/item/disk/design_disk/D = O
if(do_after(user, 10, target = src))
@@ -153,106 +149,53 @@
else
to_chat(user, "You cannot put this in [src.name]!")
-/obj/machinery/biogenerator/ui_interact(mob/user)
- if(stat & BROKEN || panel_open)
- return
- . = ..()
- var/dat
- if(processing)
- 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/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/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/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 81ad676613..b6de7b2eab 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 ad8d722b61..20abcf847f 100644
--- a/code/modules/reagents/reagent_containers/bottle.dm
+++ b/code/modules/reagents/reagent_containers/bottle.dm
@@ -416,25 +416,3 @@
/obj/item/reagent_containers/glass/bottle/bromine
name = "bromine bottle"
list_reagents = list(/datum/reagent/bromine = 30)
-
-//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)
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_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/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 1e539bac4e..da32c25786 100644
--- a/html/changelog.html
+++ b/html/changelog.html
@@ -50,6 +50,84 @@
-->
+
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:
@@ -1345,30 +1423,6 @@
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 c5dd954e87..492f779d45 100644
--- a/html/changelogs/.all_changelog.yml
+++ b/html/changelogs/.all_changelog.yml
@@ -26808,3 +26808,59 @@ DO NOT EDIT THIS FILE BY HAND! AUTOMATICALLY GENERATED BY ss13_genchangelog.py.
- 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
diff --git a/html/changelogs/AutoChangeLog-pr-13046.yml b/html/changelogs/AutoChangeLog-pr-13046.yml
deleted file mode 100644
index 8b4ff0e966..0000000000
--- a/html/changelogs/AutoChangeLog-pr-13046.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: "MrJWhit"
-delete-after: True
-changes:
- - rscadd: "Adds a second shutter on the top of the hop line"
diff --git a/html/changelogs/AutoChangeLog-pr-13048.yml b/html/changelogs/AutoChangeLog-pr-13048.yml
deleted file mode 100644
index 5bc470d93b..0000000000
--- a/html/changelogs/AutoChangeLog-pr-13048.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: "Hatterhat"
-delete-after: True
-changes:
- - rscadd: "Proto-kinetic glaives (not crushers) can parry now."
diff --git a/html/changelogs/AutoChangeLog-pr-13093.yml b/html/changelogs/AutoChangeLog-pr-13093.yml
new file mode 100644
index 0000000000..e2bf919eba
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-13093.yml
@@ -0,0 +1,4 @@
+author: "kappa-sama"
+delete-after: True
+changes:
+ - tweak: "tracer no longer gives you full stamheals per use"
diff --git a/html/changelogs/AutoChangeLog-pr-13094.yml b/html/changelogs/AutoChangeLog-pr-13094.yml
new file mode 100644
index 0000000000..a89e792f25
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-13094.yml
@@ -0,0 +1,4 @@
+author: "Hatterhat"
+delete-after: True
+changes:
+ - bugfix: "PDA uplinks can now steal from pens. Properly. Just make sure to have a pen in your PDA, first."
diff --git a/html/changelogs/AutoChangeLog-pr-13097.yml b/html/changelogs/AutoChangeLog-pr-13097.yml
new file mode 100644
index 0000000000..8e4a7d0b1a
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-13097.yml
@@ -0,0 +1,4 @@
+author: "zeroisthebiggay"
+delete-after: True
+changes:
+ - rscadd: "volaju two"
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/human_face.dmi b/icons/mob/human_face.dmi
index a4e2a9d5b2..82fb3ea385 100644
Binary files a/icons/mob/human_face.dmi and b/icons/mob/human_face.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/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/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/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/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/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/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 faa1ce990c..9306ae0ef1 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"
@@ -430,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"
@@ -1407,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"
@@ -2271,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"
@@ -2499,6 +2503,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"
@@ -2719,6 +2724,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"
@@ -2739,13 +2745,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"
@@ -3078,6 +3087,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"
@@ -3360,15 +3370,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/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 => (