"
- 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/exports/gear.dm b/code/modules/cargo/exports/gear.dm
index 646e1c6e47..678948128f 100644
--- a/code/modules/cargo/exports/gear.dm
+++ b/code/modules/cargo/exports/gear.dm
@@ -310,7 +310,7 @@
/datum/export/gear/combatgloves
cost = 80
unit_name = "combat gloves"
- export_types = list(/obj/item/clothing/gloves/tackler/combat, /obj/item/clothing/gloves/tackler/dolphin, /obj/item/clothing/gloves/fingerless/pugilist/rapid, /obj/item/clothing/gloves/krav_maga)
+ export_types = list(/obj/item/clothing/gloves/tackler/combat, /obj/item/clothing/gloves/tackler/dolphin, /obj/item/clothing/gloves/krav_maga)
include_subtypes = TRUE
/datum/export/gear/bonegloves
diff --git a/code/modules/cargo/exports/organs_robotics.dm b/code/modules/cargo/exports/organs_robotics.dm
index 7f54e675ca..b65cf28949 100644
--- a/code/modules/cargo/exports/organs_robotics.dm
+++ b/code/modules/cargo/exports/organs_robotics.dm
@@ -2,11 +2,11 @@
/datum/export/robotics
include_subtypes = FALSE
- k_elasticity = 1/50
+ k_elasticity = 1/200
/datum/export/implant
include_subtypes = FALSE
- k_elasticity = 1/50
+ k_elasticity = 1/200
/datum/export/organs
include_subtypes = TRUE
@@ -34,8 +34,8 @@
export_types = list(/obj/item/organ/cyberimp/brain/anti_stun)
/datum/export/implant/breathtube
- cost = 150
- k_elasticity = 300/20 //Large before depleating
+ cost = 175
+ k_elasticity = 1/350 //Large before depleating
unit_name = "breath implant"
export_types = list(/obj/item/organ/cyberimp/mouth/breathing_tube)
@@ -71,35 +71,35 @@
export_types = list(/obj/item/organ/cyberimp/arm/gun/laser, /obj/item/organ/cyberimp/arm/gun/taser, /obj/item/organ/cyberimp/arm/esword, /obj/item/organ/cyberimp/arm/medibeam, /obj/item/organ/cyberimp/arm/combat, /obj/item/organ/cyberimp/arm/flash, /obj/item/organ/cyberimp/arm/baton)
include_subtypes = TRUE
-/datum/export/orgains/heart
+/datum/export/organs/heart
cost = 250
unit_name = "heart"
export_types = list(/obj/item/organ/heart)
exclude_types = list(/obj/item/organ/heart/cursed, /obj/item/organ/heart/cybernetic)
-/datum/export/orgains/tongue
+/datum/export/organs/tongue
cost = 75
unit_name = "tongue"
export_types = list(/obj/item/organ/tongue)
-/datum/export/orgains/eyes
+/datum/export/organs/eyes
cost = 50 //So many things take your eyes out anyways
unit_name = "eyes"
export_types = list(/obj/item/organ/eyes)
exclude_types = list(/obj/item/organ/eyes/robotic)
-/datum/export/orgains/stomach
+/datum/export/organs/stomach
cost = 50 //can be replaced
unit_name = "stomach"
export_types = list(/obj/item/organ/stomach)
-/datum/export/orgains/lungs
+/datum/export/organs/lungs
cost = 150
unit_name = "lungs"
export_types = list(/obj/item/organ/lungs)
exclude_types = list(/obj/item/organ/lungs/cybernetic, /obj/item/organ/lungs/cybernetic/upgraded)
-/datum/export/orgains/liver
+/datum/export/organs/liver
cost = 175
unit_name = "liver"
export_types = list(/obj/item/organ/liver)
@@ -116,33 +116,33 @@
unit_name = "upgraded cybernetic organ"
export_types = list(/obj/item/organ/lungs/cybernetic/upgraded, /obj/item/organ/liver/cybernetic/upgraded)
-/datum/export/organs/tail //Shhh
+/datum/export/organs/tail // yeah have fun pulling this off someone without catching a bwoink
cost = 500
- unit_name = "error shipment failer"
+ unit_name = "organic tail"
export_types = list(/obj/item/organ/tail)
-/datum/export/orgains/vocal_cords
+/datum/export/organs/vocal_cords
cost = 500
unit_name = "vocal cords"
export_types = list(/obj/item/organ/vocal_cords) //These are gotten via different races
-/datum/export/robotics/lims
- cost = 30
- unit_name = "robotic lim replacement"
+/datum/export/robotics/limbs
+ cost = 60
+ unit_name = "robotic limb replacement"
export_types = list(/obj/item/bodypart/l_arm/robot, /obj/item/bodypart/r_arm/robot, /obj/item/bodypart/l_leg/robot, /obj/item/bodypart/r_leg/robot, /obj/item/bodypart/chest/robot, /obj/item/bodypart/head/robot)
/datum/export/robotics/surpluse
- cost = 40
- unit_name = "robotic lim replacement"
+ cost = 50
+ unit_name = "robotic limb replacement"
export_types = list(/obj/item/bodypart/l_arm/robot/surplus, /obj/item/bodypart/r_arm/robot/surplus, /obj/item/bodypart/l_leg/robot/surplus, /obj/item/bodypart/r_leg/robot/surplus)
/datum/export/robotics/surplus_upgraded
- cost = 50
- unit_name = "upgraded robotic lim replacement"
+ cost = 80
+ unit_name = "upgraded robotic limb replacement"
export_types = list(/obj/item/bodypart/l_arm/robot/surplus_upgraded, /obj/item/bodypart/r_arm/robot/surplus_upgraded, /obj/item/bodypart/l_leg/robot/surplus_upgraded, /obj/item/bodypart/r_leg/robot/surplus_upgraded)
/datum/export/robotics/surgery_gear_basic
- cost = 10
+ cost = 50
unit_name = "surgery tool"
export_types = list(/obj/item/retractor, /obj/item/hemostat, /obj/item/cautery, /obj/item/surgicaldrill, /obj/item/scalpel, /obj/item/circular_saw, /obj/item/bonesetter, /obj/item/surgical_drapes)
diff --git a/code/modules/cargo/exports/parts.dm b/code/modules/cargo/exports/parts.dm
index da3c0cf31d..3e52780d44 100644
--- a/code/modules/cargo/exports/parts.dm
+++ b/code/modules/cargo/exports/parts.dm
@@ -102,6 +102,7 @@
export_types = list(/obj/item/stock_parts/cell/high/slime/hypercharged)
//Glass working stuff
+// i'd just like to say how i despise the previous coder's fetish for their funny glasswork
/datum/export/glasswork_dish
cost = 300
diff --git a/code/modules/cargo/exports/sheets.dm b/code/modules/cargo/exports/sheets.dm
index a562210164..b0676fbde2 100644
--- a/code/modules/cargo/exports/sheets.dm
+++ b/code/modules/cargo/exports/sheets.dm
@@ -138,11 +138,11 @@
message = "of bones"
export_types = list(/obj/item/stack/sheet/bone)
-/datum/export/stack/bronze
+/datum/export/stack/sheet/bronze
unit_name = "tiles"
cost = 5
message = "of brozne"
- export_types = list(/obj/item/stack/tile/bronze)
+ export_types = list(/obj/item/stack/sheet/bronze)
/datum/export/stack/brass
unit_name = "tiles"
diff --git a/code/modules/cargo/exports/weapons.dm b/code/modules/cargo/exports/weapons.dm
index 983348a358..dc2703c146 100644
--- a/code/modules/cargo/exports/weapons.dm
+++ b/code/modules/cargo/exports/weapons.dm
@@ -5,7 +5,7 @@
/datum/export/weapon/makeshift_shield
cost = 30
- unit_name = "unknown shield"
+ unit_name = "nonstandard shield"
export_types = list(/obj/item/shield/riot, /obj/item/shield/riot/roman, /obj/item/shield/riot/buckler, /obj/item/shield/makeshift)
/datum/export/weapon/riot_shield
@@ -37,7 +37,7 @@
/datum/export/weapon/taser
cost = 200
- unit_name = "advanced taser"
+ unit_name = "hybrid taser"
export_types = list(/obj/item/gun/energy/e_gun/advtaser)
/datum/export/weapon/laser
@@ -104,12 +104,12 @@
/datum/export/weapon/aeg
cost = 200 //Endless power
- unit_name = "advance engery gun"
+ unit_name = "advanced energy gun"
export_types = list(/obj/item/gun/energy/e_gun/nuclear)
/datum/export/weapon/deconer
cost = 600
- unit_name = "deconer"
+ unit_name = "decloner"
export_types = list(/obj/item/gun/energy/decloner)
/datum/export/weapon/ntsniper
@@ -123,9 +123,8 @@
export_types = list(/obj/item/gun/syringe/rapidsyringe)
/datum/export/weapon/temp_gun
- cost = 175 //Its just smaller
+ cost = 175
unit_name = "small temperature gun"
- k_elasticity = 1/30 //Its just a smaller temperature gun, easy to mass make
export_types = list(/obj/item/gun/energy/temperature)
/datum/export/weapon/flowergun
@@ -139,8 +138,7 @@
export_types = list(/obj/item/gun/energy/xray)
/datum/export/weapon/ioncarbine
- cost = 200
- k_elasticity = 1/30 //Its just a smaller temperature gun, easy to mass make
+ cost = 200
unit_name = "ion carbine"
export_types = list(/obj/item/gun/energy/ionrifle/carbine)
@@ -194,7 +192,7 @@
export_types = list(/obj/item/firing_pin/test_range)
/datum/export/weapon/techslug
- cost = 25
+ cost = 30
k_elasticity = 0
unit_name = "advanced shotgun shell"
export_types = list(/obj/item/ammo_casing/shotgun/dragonsbreath, /obj/item/ammo_casing/shotgun/meteorslug, /obj/item/ammo_casing/shotgun/pulseslug, /obj/item/ammo_casing/shotgun/frag12, /obj/item/ammo_casing/shotgun/ion, /obj/item/ammo_casing/shotgun/laserslug)
@@ -215,7 +213,7 @@
/datum/export/weapon/bow_teaching
cost = 500
- unit_name = "stone tablets"
+ unit_name = "bowyery tablet"
export_types = list(/obj/item/book/granter/crafting_recipe/bone_bow)
/datum/export/weapon/quiver
@@ -230,48 +228,48 @@
/datum/export/weapon/pistol
cost = 120
- unit_name = "illegal firearm"
+ unit_name = "nonstandard sidearm"
export_types = list(/obj/item/gun/ballistic/automatic/pistol)
/datum/export/weapon/revolver
cost = 200
- unit_name = "large handgun"
+ unit_name = "large-caliber revolver"
export_types = list(/obj/item/gun/ballistic/revolver)
exclude_types = list(/obj/item/gun/ballistic/revolver/russian, /obj/item/gun/ballistic/revolver/doublebarrel)
/datum/export/weapon/rocketlauncher
cost = 1000
- unit_name = "rocketlauncher"
+ unit_name = "PML-9 rocket-propelled grenade launcher"
export_types = list(/obj/item/gun/ballistic/rocketlauncher)
/datum/export/weapon/antitank
cost = 300
- unit_name = "hand cannon"
+ unit_name = "anti-tank pistol"
export_types = list(/obj/item/gun/ballistic/automatic/pistol/antitank/syndicate)
/datum/export/weapon/clownstuff
cost = 500
- unit_name = "clown war tech"
- export_types = list(/obj/item/pneumatic_cannon/pie/selfcharge, /obj/item/shield/energy/bananium, /obj/item/melee/transforming/energy/sword/bananium, )
+ unit_name = "clown combat equipment"
+ export_types = list(/obj/item/pneumatic_cannon/pie/selfcharge, /obj/item/shield/energy/bananium, /obj/item/melee/transforming/energy/sword/bananium)
/datum/export/weapon/bulldog
cost = 400
- unit_name = "drum loaded shotgun"
+ unit_name = "drum-fed compact combat shotgun"
export_types = list(/obj/item/gun/ballistic/automatic/shotgun/bulldog)
/datum/export/weapon/smg
cost = 350
- unit_name = "automatic c-20r"
+ unit_name = "C-20r sub-machine gun"
export_types = list(/obj/item/gun/ballistic/automatic/c20r)
/datum/export/weapon/duelsaber
- cost = 360 //Get it?
- unit_name = "energy saber"
+ cost = 360
+ unit_name = "double-bladed energy saber"
export_types = list(/obj/item/dualsaber)
/datum/export/weapon/esword
cost = 130
- unit_name = "energy sword"
+ unit_name = "energy saber"
export_types = list(/obj/item/melee/transforming/energy/sword/cx/traitor, /obj/item/melee/transforming/energy/sword/saber)
/datum/export/weapon/rapier
@@ -286,32 +284,32 @@
/datum/export/weapon/gloves
cost = 90
- unit_name = "star struck gloves"
+ unit_name = "anomalous armwraps"
export_types = list(/obj/item/clothing/gloves/fingerless/pugilist/rapid)
/datum/export/weapon/l6
cost = 500
- unit_name = "law 6 saw"
+ unit_name = "Aussec Armory L6 SAW"
export_types = list(/obj/item/gun/ballistic/automatic/l6_saw)
/datum/export/weapon/m90
cost = 400
- unit_name = "assault class weapon"
+ unit_name = "M90-gl carbine"
export_types = list(/obj/item/gun/ballistic/automatic/m90)
/datum/export/weapon/powerglove
cost = 100
- unit_name = "hydraulic glove"
+ unit_name = "pneumatic gauntlet"
export_types = list(/obj/item/melee/powerfist)
/datum/export/weapon/sniper
cost = 750
- unit_name = ".50 sniper"
+ unit_name = "anti-materiel rifle"
export_types = list(/obj/item/gun/ballistic/automatic/sniper_rifle/syndicate)
/datum/export/weapon/ebow
cost = 600
- unit_name = "mini crossbow"
+ unit_name = "compact energy crossbow"
export_types = list(/obj/item/gun/energy/kinetic_accelerator/crossbow)
/datum/export/weapon/m10mm
@@ -333,12 +331,12 @@
/datum/export/weapon/smg_mag
cost = 45
- unit_name = "smg magazine"
+ unit_name = "SMG/carbine magazine"
export_types = list(/obj/item/ammo_box/magazine/smgm45, /obj/item/ammo_box/magazine/m556)
/datum/export/weapon/l6sawammo
cost = 60
- unit_name = "law 6 saw ammo box"
+ unit_name = "L6 SAW ammo box"
export_types = list(/obj/item/ammo_box/magazine/mm195x129)
include_subtypes = TRUE
@@ -355,13 +353,13 @@
/datum/export/weapon/fletcher_ammo
cost = 60
- unit_name = "illegal ammo magazines"
+ unit_name = "flechette launcher magazine"
export_types = list(/obj/item/ammo_box/magazine/flechette)
include_subtypes = TRUE
/datum/export/weapon/dj_a_pizzabomb
cost = -6000
- unit_name = "Repair Costs"
+ unit_name = "undeclared ordinance and subsequent repair costs"
export_types = list(/obj/item/pizzabox/bomb, /obj/item/sbeacondrop/bomb)
/datum/export/weapon/real_toolbox
@@ -371,12 +369,12 @@
/datum/export/weapon/melee
cost = 50
- unit_name = "unlisted weapon"
+ unit_name = "any other melee weapon"
export_types = list(/obj/item/melee)
include_subtypes = TRUE
/datum/export/weapon/gun
cost = 50
- unit_name = "unlisted weapon"
+ unit_name = "any other weapon"
export_types = list(/obj/item/gun)
include_subtypes = TRUE
diff --git a/code/modules/cargo/expressconsole.dm b/code/modules/cargo/expressconsole.dm
index dc7e4b5a06..4ca97a13a5 100644
--- a/code/modules/cargo/expressconsole.dm
+++ b/code/modules/cargo/expressconsole.dm
@@ -1,5 +1,5 @@
#define MAX_EMAG_ROCKETS 8
-#define BEACON_COST 5000
+#define BEACON_COST 500
#define SP_LINKED 1
#define SP_READY 2
#define SP_LAUNCH 3
@@ -13,10 +13,9 @@
All sales are near instantaneous - please choose carefully"
icon_screen = "supply_express"
circuit = /obj/item/circuitboard/computer/cargo/express
- ui_x = 600
- ui_y = 700
blockade_warning = "Bluespace instability detected. Delivery impossible."
req_access = list(ACCESS_QM)
+
var/message
var/printed_beacons = 0 //number of beacons printed. Used to determine beacon names.
var/list/meme_pack_data
@@ -42,7 +41,7 @@
to_chat(user, "You [locked ? "lock" : "unlock"] the interface.")
return
else if(istype(W, /obj/item/disk/cargo/bluespace_pod))
- podType = /obj/structure/closet/supplypod/bluespacepod
+ podType = /obj/structure/closet/supplypod/bluespacepod //doesnt effect circuit board, making reversal possible
to_chat(user, "You insert the disk into [src], allowing for advanced supply delivery vehicles.")
qdel(W)
return TRUE
@@ -52,22 +51,20 @@
sb.link_console(src, user)
return TRUE
else
- to_chat(user, "[src] is already linked to [sb].")
+ to_chat(user, "[src] is already linked to [sb].")
..()
/obj/machinery/computer/cargo/express/emag_act(mob/living/user)
- . = SEND_SIGNAL(src, COMSIG_ATOM_EMAG_ACT)
if(obj_flags & EMAGGED)
return
- user.visible_message("[user] swipes a suspicious card through [src]!",
- "You change the routing protocols, allowing the Supply Pod to land anywhere on the station.")
+ if(user)
+ user.visible_message("[user] swipes a suspicious card through [src]!",
+ "You change the routing protocols, allowing the Supply Pod to land anywhere on the station.")
obj_flags |= EMAGGED
// This also sets this on the circuit board
var/obj/item/circuitboard/computer/cargo/board = circuit
board.obj_flags |= EMAGGED
packin_up()
- req_access = list()
- return TRUE
/obj/machinery/computer/cargo/express/proc/packin_up() // oh shit, I'm sorry
meme_pack_data = list() // sorry for what?
@@ -89,10 +86,10 @@
"desc" = P.desc || P.name // If there is a description, use it. Otherwise use the pack's name.
))
-/obj/machinery/computer/cargo/express/ui_interact(mob/living/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) // Remember to use the appropriate state.
- ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+/obj/machinery/computer/cargo/express/ui_interact(mob/living/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
- ui = new(user, src, ui_key, "CargoExpress", name, ui_x, ui_y, master_ui, state)
+ ui = new(user, src, "CargoExpress", name)
ui.open()
/obj/machinery/computer/cargo/express/ui_data(mob/user)
@@ -131,9 +128,6 @@
return data
/obj/machinery/computer/cargo/express/ui_act(action, params, datum/tgui/ui)
- if(!allowed(usr))
- to_chat(usr, "Access denied.")
- return
switch(action)
if("LZCargo")
usingBeacon = FALSE
@@ -153,6 +147,7 @@
printed_beacons++//printed_beacons starts at 0, so the first one out will be called beacon # 1
beacon.name = "Supply Pod Beacon #[printed_beacons]"
+
if("add")//Generate Supply Order first
var/id = text2path(params["id"])
var/datum/supply_pack/pack = SSshuttle.supply_packs[id]
@@ -195,7 +190,6 @@
LZ = pick(empty_turfs)
if (SO.pack.cost <= points_to_check && LZ)//we need to call the cost check again because of the CHECK_TICK call
D.adjust_money(-SO.pack.cost)
- SSblackbox.record_feedback("nested tally", "cargo_imports", 1, list("[SO.pack.cost]", "[SO.pack.name]"))
new /obj/effect/abstract/DPtarget(LZ, podType, SO)
. = TRUE
update_icon()
@@ -209,7 +203,7 @@
CHECK_TICK
if(empty_turfs && empty_turfs.len)
D.adjust_money(-(SO.pack.cost * (0.72*MAX_EMAG_ROCKETS)))
- SSblackbox.record_feedback("nested tally", "cargo_imports", MAX_EMAG_ROCKETS, list("[SO.pack.cost * 0.72]", "[SO.pack.name]"))
+
SO.generateRequisition(get_turf(src))
for(var/i in 1 to MAX_EMAG_ROCKETS)
var/LZ = pick(empty_turfs)
diff --git a/code/modules/cargo/packs/armory.dm b/code/modules/cargo/packs/armory.dm
index 835457536f..9f8bb2f25f 100644
--- a/code/modules/cargo/packs/armory.dm
+++ b/code/modules/cargo/packs/armory.dm
@@ -158,11 +158,10 @@
/datum/supply_pack/security/armory/russian
name = "Russian Surplus Crate"
- desc = "Hello Comrade, we have the most modern russian military equipment the black market can offer, for the right price of course. Sadly we couldnt remove the lock so it requires Armory access to open."
+ desc = "Hello Comrade, we have the most modern Russian military equipment the black market can offer, for the right price of course. Sadly we couldn't remove the lock so it requires Armory access to open."
cost = 7500
contraband = TRUE
contains = list(/obj/item/reagent_containers/food/snacks/rationpack,
- /obj/item/ammo_box/magazine/m10mm/rifle,
/obj/item/clothing/suit/armor/vest/russian,
/obj/item/clothing/head/helmet/rus_helmet,
/obj/item/clothing/shoes/russian,
@@ -172,7 +171,10 @@
/obj/item/clothing/mask/russian_balaclava,
/obj/item/clothing/head/helmet/rus_ushanka,
/obj/item/clothing/suit/armor/vest/russian_coat,
- /obj/item/gun/ballistic/automatic/surplus)
+ /obj/effect/spawner/bundle/crate/mosin,
+ /obj/item/storage/toolbox/ammo,
+ /obj/effect/spawner/bundle/crate/surplusrifle,
+ /obj/item/storage/toolbox/ammo/surplus)
crate_name = "surplus military crate"
/datum/supply_pack/security/armory/russian/fill(obj/structure/closet/crate/C)
@@ -223,3 +225,10 @@
/obj/item/ammo_box/magazine/wt550m9/wtrubber,
/obj/item/ammo_box/magazine/wt550m9/wtrubber)
crate_name = "auto rifle ammo crate"
+
+/datum/supply_pack/security/armory/hell_single
+ name = "Hellgun Single-Pack"
+ crate_name = "hellgun crate"
+ desc = "Contains one hellgun, an old pattern of laser gun infamous for its ability to horribly disfigure targets with burns. Technically violates the Space Geneva Convention when used on humanoids."
+ cost = 1500
+ contains = list(/obj/item/gun/energy/laser/hellgun)
diff --git a/code/modules/cargo/packs/goodies.dm b/code/modules/cargo/packs/goodies.dm
index 86a7c73a34..5d07e85bac 100644
--- a/code/modules/cargo/packs/goodies.dm
+++ b/code/modules/cargo/packs/goodies.dm
@@ -76,12 +76,6 @@
cost = 200
contains = list(/obj/item/toy/beach_ball)
-/datum/supply_pack/goody/hell_single
- name = "Hellgun Single-Pack"
- desc = "Contains one hellgun, an old pattern of laser gun infamous for its ability to horribly disfigure targets with burns. Technically violates the Space Geneva Convention when used on humanoids."
- cost = 1500
- contains = list(/obj/item/gun/energy/laser/hellgun)
-
/datum/supply_pack/goody/medipen_twopak
name = "Medipen Two-Pak"
desc = "Contains one standard epinephrine medipen and one standard emergency first-aid kit medipen. For when you want to prepare for the worst."
diff --git a/code/modules/cargo/packs/misc.dm b/code/modules/cargo/packs/misc.dm
index a84e22f6f9..c6728831eb 100644
--- a/code/modules/cargo/packs/misc.dm
+++ b/code/modules/cargo/packs/misc.dm
@@ -194,9 +194,9 @@
/datum/supply_pack/misc/dirtymags
name = "Dirty Magazines"
- desc = "Get your mind out of the gutter operative, you have work to do. Three items per order. Possible Results: .357 Speedloaders, Kitchen Gun Mags, Stetchkin Mags."
+ desc = "Get your mind out of the gutter operative, you have work to do. Three items per order. Possible Results: .357 Speedloaders, Kitchen Gun patented magazines, or Stetchkin magazines."
hidden = TRUE
- cost = 12000
+ cost = 4000
var/num_contained = 3
contains = list(/obj/item/ammo_box/a357,
/obj/item/ammo_box/magazine/pistolm9mm,
@@ -415,21 +415,10 @@
/obj/item/restraints/handcuffs/fake/kinky,
/obj/item/clothing/head/kitty/genuine, // Why its illegal
/obj/item/clothing/head/kitty/genuine,
- /obj/item/storage/pill_bottle/penis_enlargement,
- /obj/structure/reagent_dispensers/keg/aphro)
+ /obj/item/storage/pill_bottle/penis_enlargement)
crate_name = "lewd kit"
crate_type = /obj/structure/closet/crate
-/datum/supply_pack/misc/lewdkeg
- name = "Lewd Deluxe Keg"
- desc = "That other stuff not getting you ready? Well I have a Chemslut making tons of the good stuff."
- cost = 7500 //It can be a weapon
- contraband = TRUE
- contains = list(/obj/structure/reagent_dispensers/keg/aphro/strong)
- crate_name = "deluxe keg"
- crate_type = /obj/structure/closet/crate
-
-
///Special supply crate that generates random syndicate gear up to a determined TC value
/datum/supply_pack/misc/syndicate
@@ -466,4 +455,4 @@
if(crate_value < I.cost)
continue
crate_value -= I.cost
- new I.item(C)
\ No newline at end of file
+ new I.item(C)
diff --git a/code/modules/cargo/packs/security.dm b/code/modules/cargo/packs/security.dm
index 738eb03fbf..cf9cc5e0d1 100644
--- a/code/modules/cargo/packs/security.dm
+++ b/code/modules/cargo/packs/security.dm
@@ -100,7 +100,7 @@
crate_name = "surplus russian clothing"
crate_type = /obj/structure/closet/crate/internals
-/datum/supply_pack/security/russianmosin
+/datum/supply_pack/security/russian_partisan
name = "Russian Partisan Gear"
desc = "An old russian partisan equipment crate, comes with a full russian outfit, a loaded surplus rifle and a second magazine."
contraband = TRUE
@@ -112,12 +112,17 @@
/obj/item/clothing/suit/armor/bulletproof,
/obj/item/clothing/head/helmet/alt,
/obj/item/clothing/gloves/tackler/combat/insulated,
- /obj/item/clothing/mask/gas,
- /obj/item/ammo_box/magazine/m10mm/rifle,
- /obj/item/gun/ballistic/automatic/surplus)
+ /obj/item/clothing/mask/gas)
crate_name = "surplus russian gear"
crate_type = /obj/structure/closet/crate/internals
+/datum/supply_pack/security/russian_partisan/fill(obj/structure/closet/crate/C)
+ ..()
+ if(prob(20))
+ new /obj/effect/spawner/bundle/crate/mosin(C)
+ else
+ new /obj/effect/spawner/bundle/crate/surplusrifle(C)
+
/datum/supply_pack/security/sechardsuit
name = "Sec Hardsuit"
desc = "One Sec Hardsuit with a small air tank and mask."
diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm
index 5c9b1eec2e..e7aa447840 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)
@@ -988,3 +993,6 @@ GLOBAL_LIST_EMPTY(external_rsc_urls)
screen -= S
qdel(S)
char_render_holders = null
+
+/client/proc/can_have_part(part_name)
+ return prefs.pref_species.mutant_bodyparts[part_name] || (part_name in GLOB.unlocked_mutant_parts)
diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm
index 3c9697c03a..480ebda4f8 100644
--- a/code/modules/client/preferences.dm
+++ b/code/modules/client/preferences.dm
@@ -229,7 +229,8 @@ GLOBAL_LIST_EMPTY(preferences_datums)
var/gear_points = 10
var/list/gear_categories
var/list/chosen_gear = list()
- var/gear_tab
+ var/gear_category
+ var/gear_subcategory
var/screenshake = 100
var/damagescreenshake = 2
@@ -506,310 +507,19 @@ GLOBAL_LIST_EMPTY(preferences_datums)
dat += ""
mutant_category = 0
- if(pref_species.mutant_bodyparts["tail_lizard"])
- if(!mutant_category)
- dat += APPEARANCE_CATEGORY_COLUMN
-
- dat += "
Tail
"
-
- dat += "[features["tail_lizard"]]"
-
- mutant_category++
- if(mutant_category >= MAX_MUTANT_ROWS)
- dat += ""
- mutant_category = 0
-
- if(pref_species.mutant_bodyparts["mam_tail"])
- if(!mutant_category)
- dat += APPEARANCE_CATEGORY_COLUMN
-
- dat += "
Tail
"
-
- dat += "[features["mam_tail"]]"
-
- mutant_category++
- if(mutant_category >= MAX_MUTANT_ROWS)
- dat += ""
- mutant_category = 0
- if(pref_species.mutant_bodyparts["tail_human"])
- if(!mutant_category)
- dat += APPEARANCE_CATEGORY_COLUMN
-
- dat += "
Tail
"
-
- dat += "[features["tail_human"]]"
-
- mutant_category++
- if(mutant_category >= MAX_MUTANT_ROWS)
- dat += ""
- mutant_category = 0
-
- if(pref_species.mutant_bodyparts["meat_type"])
- if(!mutant_category)
- dat += APPEARANCE_CATEGORY_COLUMN
-
- dat += "
Meat Type
"
-
- dat += "[features["meat_type"]]"
-
- mutant_category++
- if(mutant_category >= MAX_MUTANT_ROWS)
- dat += ""
- mutant_category = 0
- if(pref_species.mutant_bodyparts["snout"])
- if(!mutant_category)
- dat += APPEARANCE_CATEGORY_COLUMN
-
- dat += "
Snout
"
-
- dat += "[features["snout"]]"
-
- mutant_category++
- if(mutant_category >= MAX_MUTANT_ROWS)
- dat += ""
- mutant_category = 0
- if(pref_species.mutant_bodyparts["horns"])
- if(!mutant_category)
- dat += APPEARANCE_CATEGORY_COLUMN
-
- dat += "
Horns
"
-
- dat += "[features["horns"]]"
- dat += "Change "
-
-
- mutant_category++
- if(mutant_category >= MAX_MUTANT_ROWS)
- dat += ""
- if(pref_species.mutant_bodyparts["frills"])
- if(!mutant_category)
- dat += APPEARANCE_CATEGORY_COLUMN
-
- dat += "
Frills
"
-
- dat += "[features["frills"]]"
-
- mutant_category++
- if(mutant_category >= MAX_MUTANT_ROWS)
- dat += ""
- mutant_category = 0
-
- if(pref_species.mutant_bodyparts["spines"])
- if(!mutant_category)
- dat += APPEARANCE_CATEGORY_COLUMN
-
- dat += "
Spines
"
-
- dat += "[features["spines"]]"
-
- mutant_category++
- if(mutant_category >= MAX_MUTANT_ROWS)
- dat += ""
- mutant_category = 0
-
- if(pref_species.mutant_bodyparts["body_markings"])
- if(!mutant_category)
- dat += APPEARANCE_CATEGORY_COLUMN
-
- dat += "
Body Markings
"
-
- dat += "[features["body_markings"]]"
-
- mutant_category++
- if(mutant_category >= MAX_MUTANT_ROWS)
- dat += ""
- mutant_category = 0
- if(pref_species.mutant_bodyparts["mam_body_markings"])
- if(!mutant_category)
- dat += APPEARANCE_CATEGORY_COLUMN
-
- dat += "
Species Markings
"
-
- dat += "[features["mam_body_markings"]]"
-
- mutant_category++
- if(mutant_category >= MAX_MUTANT_ROWS)
- dat += ""
-
- if(pref_species.mutant_bodyparts["mam_ears"])
- if(!mutant_category)
- dat += APPEARANCE_CATEGORY_COLUMN
-
- dat += "
Ears
"
-
- dat += "[features["mam_ears"]]"
-
- mutant_category++
- if(mutant_category >= MAX_MUTANT_ROWS)
- dat += ""
- mutant_category = 0
-
- if(pref_species.mutant_bodyparts["ears"])
- if(!mutant_category)
- dat += APPEARANCE_CATEGORY_COLUMN
-
- dat += "
Ears
"
-
- dat += "[features["ears"]]"
-
- mutant_category++
- if(mutant_category >= MAX_MUTANT_ROWS)
- dat += ""
- mutant_category = 0
-
- if(pref_species.mutant_bodyparts["mam_snouts"])
- if(!mutant_category)
- dat += APPEARANCE_CATEGORY_COLUMN
-
- dat += "
Snout
"
-
- dat += "[features["mam_snouts"]]"
-
- mutant_category++
- if(mutant_category >= MAX_MUTANT_ROWS)
- dat += ""
- mutant_category = 0
- if(pref_species.mutant_bodyparts["legs"])
- if(!mutant_category)
- dat += APPEARANCE_CATEGORY_COLUMN
-
- dat += "
Legs
"
-
- dat += "[features["legs"]]"
-
- mutant_category++
- if(mutant_category >= MAX_MUTANT_ROWS)
- dat += ""
- mutant_category = 0
- if(pref_species.mutant_bodyparts["deco_wings"])
- if(!mutant_category)
- dat += APPEARANCE_CATEGORY_COLUMN
-
- dat += "
Decorative wings
"
-
- dat += "[features["deco_wings"]]"
- dat += "Change "
-
- if(pref_species.mutant_bodyparts["insect_wings"])
- if(!mutant_category)
- dat += APPEARANCE_CATEGORY_COLUMN
-
- dat += "
Insect wings
"
-
- dat += "[features["insect_wings"]]"
- dat += "Change "
- mutant_category++
- if(mutant_category >= MAX_MUTANT_ROWS)
- dat += ""
- mutant_category = 0
- if(pref_species.mutant_bodyparts["insect_fluff"])
- if(!mutant_category)
- dat += APPEARANCE_CATEGORY_COLUMN
-
- dat += "
Insect Fluff
"
-
- dat += "[features["insect_fluff"]]"
- mutant_category++
- if(mutant_category >= MAX_MUTANT_ROWS)
- dat += ""
- mutant_category = 0
- if(pref_species.mutant_bodyparts["taur"])
- if(!mutant_category)
- dat += APPEARANCE_CATEGORY_COLUMN
-
- dat += "
Tauric Body
"
-
- dat += "[features["taur"]]"
-
- if(pref_species.mutant_bodyparts["insect_markings"])
- if(!mutant_category)
- dat += APPEARANCE_CATEGORY_COLUMN
-
- dat += "
"
- for(var/V in categories[cat])
- var/datum/design/D = V
- dat += "[D.name]: Make"
- if(cat in timesFiveCategories)
- dat += "x5"
- if(ispath(D.build_path, /obj/item/stack))
- dat += "x10"
- dat += "([CEILING(D.materials[SSmaterials.GetMaterialRef(/datum/material/biomass)]/efficiency, 1)]) "
- dat += "
"
- else
- dat += "
No container inside, please insert container.
"
-
- var/datum/browser/popup = new(user, "biogen", name, 350, 520)
- popup.set_content(dat)
- popup.open()
-
/obj/machinery/biogenerator/AltClick(mob/living/user)
. = ..()
- if(istype(user) && user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK))
+ if(user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK) && can_interact(user))
detach(user)
-/obj/machinery/biogenerator/proc/activate()
- if (usr.stat != CONSCIOUS)
+/**
+ * activate: Activates biomass processing and converts all inserted grown products into biomass
+ *
+ * Arguments:
+ * * user The mob starting the biomass processing
+ */
+/obj/machinery/biogenerator/proc/activate(mob/user)
+ if(user.stat != CONSCIOUS)
return
- if (src.stat != NONE) //NOPOWER etc
+ if(stat != NONE)
return
if(processing)
- to_chat(usr, "The biogenerator is in the process of working.")
+ to_chat(user, "The biogenerator is in the process of working.")
return
var/S = 0
- var/total = 0
for(var/obj/item/reagent_containers/food/snacks/grown/I in contents)
S += 5
- var/nutri_amount = I.reagents.get_reagent_amount(/datum/reagent/consumable/nutriment)
- if(nutri_amount < 0.1)
- total += 1*productivity
+ if(I.reagents.get_reagent_amount(/datum/reagent/consumable/nutriment) < 0.1)
+ points += 1 * productivity
else
- total += nutri_amount*10*productivity
+ points += I.reagents.get_reagent_amount(/datum/reagent/consumable/nutriment) * 10 * productivity
qdel(I)
- points += round(total)
if(S)
processing = TRUE
update_icon()
- updateUsrDialog()
- playsound(src.loc, 'sound/machines/blender.ogg', 50, 1)
- use_power(S*30)
- sleep(S+15/productivity)
+ playsound(loc, 'sound/machines/blender.ogg', 50, TRUE)
+ use_power(S * 30)
+ sleep(S + 15 / productivity)
+ if(QDELETED(src)) //let's not.
+ return
processing = FALSE
update_icon()
- else
- menustat = "void"
/obj/machinery/biogenerator/proc/check_cost(list/materials, multiplier = 1, remove_points = TRUE)
if(materials.len != 1 || materials[1] != SSmaterials.GetMaterialRef(/datum/material/biomass))
return FALSE
- var/cost = CEILING(materials[SSmaterials.GetMaterialRef(/datum/material/biomass)]*multiplier/efficiency, 1)
- if (cost > points)
- menustat = "nopoints"
+ if (materials[SSmaterials.GetMaterialRef(/datum/material/biomass)]*multiplier/efficiency > points)
return FALSE
else
if(remove_points)
- points -= cost
+ points -= materials[SSmaterials.GetMaterialRef(/datum/material/biomass)]*multiplier/efficiency
update_icon()
- updateUsrDialog()
return TRUE
/obj/machinery/biogenerator/proc/check_container_volume(list/reagents, multiplier = 1)
@@ -262,7 +205,6 @@
sum_reagents *= multiplier
if(beaker.reagents.total_volume + sum_reagents > beaker.reagents.maximum_volume)
- menustat = "nobeakerspace"
return FALSE
return TRUE
@@ -284,6 +226,7 @@
var/i = amount
while(i > 0)
if(!check_container_volume(D.make_reagents))
+ say("Warning: Attached container does not have enough free capacity!")
return .
if(!check_cost(D.materials))
return .
@@ -293,51 +236,100 @@
beaker.reagents.add_reagent(R, D.make_reagents[R])
. = 1
--i
-
- menustat = "complete"
update_icon()
return .
/obj/machinery/biogenerator/proc/detach(mob/living/user)
if(beaker)
- user.put_in_hands(beaker)
+ if(can_interact(user))
+ user.put_in_hands(beaker)
+ else
+ beaker.drop_location(get_turf(src))
beaker = null
update_icon()
-/obj/machinery/biogenerator/Topic(href, href_list)
- if(..() || panel_open)
+/obj/machinery/biogenerator/ui_status(mob/user)
+ if(stat & BROKEN || panel_open)
+ return UI_CLOSE
+ return ..()
+
+/obj/machinery/biogenerator/ui_assets(mob/user)
+ return list(
+ get_asset_datum(/datum/asset/spritesheet/research_designs),
+ )
+
+/obj/machinery/biogenerator/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "Biogenerator", name)
+ ui.open()
+
+/obj/machinery/biogenerator/ui_data(mob/user)
+ var/list/data = list()
+ data["beaker"] = beaker ? TRUE : FALSE
+ data["biomass"] = points
+ data["processing"] = processing
+ if(locate(/obj/item/reagent_containers/food/snacks/grown) in contents)
+ data["can_process"] = TRUE
+ else
+ data["can_process"] = FALSE
+ return data
+
+/obj/machinery/biogenerator/ui_static_data(mob/user)
+ var/list/data = list()
+ data["categories"] = list()
+
+ var/categories = show_categories.Copy()
+ for(var/V in categories)
+ categories[V] = list()
+ for(var/V in stored_research.researched_designs)
+ var/datum/design/D = SSresearch.techweb_design_by_id(V)
+ for(var/C in categories)
+ if(C in D.category)
+ categories[C] += D
+
+ for(var/category in categories)
+ var/list/cat = list(
+ "name" = category,
+ "items" = (category == selected_cat ? list() : null))
+ for(var/item in categories[category])
+ var/datum/design/D = item
+ cat["items"] += list(list(
+ "id" = D.id,
+ "name" = D.name,
+ "cost" = D.materials[SSmaterials.GetMaterialRef(/datum/material/biomass)]/efficiency,
+ ))
+ data["categories"] += list(cat)
+
+ return data
+
+/obj/machinery/biogenerator/ui_act(action, list/params)
+ if(..())
return
- usr.set_machine(src)
-
- if(href_list["activate"])
- activate()
- updateUsrDialog()
-
- else if(href_list["detach"])
- detach(usr)
- updateUsrDialog()
-
- else if(href_list["create"])
- var/amount = (text2num(href_list["amount"]))
- //Can't be outside these (if you change this keep a sane limit)
- amount = clamp(amount, 1, 50)
- var/id = href_list["create"]
- if(!stored_research.researched_designs.Find(id))
- //naughty naughty
- stack_trace("ID did not map to a researched datum [id]")
- return
-
- //Get design by id (or may return error design)
- var/datum/design/D = SSresearch.techweb_design_by_id(id)
- //Valid design datum, amount and the datum is not the error design, lets proceed
- if(D && amount && !istype(D, /datum/design/error_design))
- create_product(D, amount)
- //This shouldnt happen normally but href forgery is real
- else
- stack_trace("ID could not be turned into a valid techweb design datum [id]")
- updateUsrDialog()
-
- else if(href_list["menu"])
- menustat = "menu"
- updateUsrDialog()
+ switch(action)
+ if("activate")
+ activate(usr)
+ return TRUE
+ if("detach")
+ detach(usr)
+ return TRUE
+ if("create")
+ var/amount = text2num(params["amount"])
+ amount = clamp(amount, 1, 10)
+ if(!amount)
+ return
+ var/id = params["id"]
+ if(!stored_research.researched_designs.Find(id))
+ stack_trace("ID did not map to a researched datum [id]")
+ return
+ var/datum/design/D = SSresearch.techweb_design_by_id(id)
+ if(D && !istype(D, /datum/design/error_design))
+ create_product(D, amount)
+ else
+ stack_trace("ID could not be turned into a valid techweb design datum [id]")
+ return
+ return TRUE
+ if("select")
+ selected_cat = params["category"]
+ return TRUE
diff --git a/code/modules/hydroponics/gene_modder.dm b/code/modules/hydroponics/gene_modder.dm
index 4e545c13ee..a0c273613f 100644
--- a/code/modules/hydroponics/gene_modder.dm
+++ b/code/modules/hydroponics/gene_modder.dm
@@ -3,9 +3,9 @@
desc = "An advanced device designed to manipulate plant genetic makeup."
icon = 'icons/obj/hydroponics/equipment.dmi'
icon_state = "dnamod"
- density = TRUE
circuit = /obj/item/circuitboard/machine/plantgenes
- pass_flags = PASSTABLE
+ pass_flags = PASSTABLE | LETPASSTHROW
+ flags_1 = DEFAULT_RICOCHET_1
var/obj/item/seeds/seed
var/obj/item/disk/plantgene/disk
diff --git a/code/modules/hydroponics/grown/cannabis.dm b/code/modules/hydroponics/grown/cannabis.dm
index 621e79fb77..6525ac42d4 100644
--- a/code/modules/hydroponics/grown/cannabis.dm
+++ b/code/modules/hydroponics/grown/cannabis.dm
@@ -14,9 +14,7 @@
icon_dead = "cannabis-dead" // Same for the dead icon
genes = list(/datum/plant_gene/trait/repeated_harvest)
mutatelist = list(/obj/item/seeds/cannabis/rainbow,
- /obj/item/seeds/cannabis/death,
- /obj/item/seeds/cannabis/white,
- /obj/item/seeds/cannabis/ultimate)
+ /obj/item/seeds/cannabis/death)
reagents_add = list(/datum/reagent/drug/space_drugs = 0.15, /datum/reagent/toxin/lipolicide = 0.35) // gives u the munchies
@@ -27,7 +25,7 @@
species = "megacannabis"
plantname = "Rainbow Weed"
product = /obj/item/reagent_containers/food/snacks/grown/cannabis/rainbow
- mutatelist = list()
+ mutatelist = list(/obj/item/seeds/cannabis/ultimate)
reagents_add = list(/datum/reagent/toxin/mindbreaker = 0.15, /datum/reagent/toxin/lipolicide = 0.35)
rarity = 40
@@ -38,7 +36,7 @@
species = "blackcannabis"
plantname = "Deathweed"
product = /obj/item/reagent_containers/food/snacks/grown/cannabis/death
- mutatelist = list()
+ mutatelist = list(/obj/item/seeds/cannabis/white)
reagents_add = list(/datum/reagent/toxin/cyanide = 0.35, /datum/reagent/drug/space_drugs = 0.15, /datum/reagent/toxin/lipolicide = 0.15)
rarity = 40
diff --git a/code/modules/hydroponics/grown/citrus.dm b/code/modules/hydroponics/grown/citrus.dm
index 6ac7bbcfcb..d130d50aa5 100644
--- a/code/modules/hydroponics/grown/citrus.dm
+++ b/code/modules/hydroponics/grown/citrus.dm
@@ -33,26 +33,6 @@
filling_color = "#00FF00"
juice_results = list(/datum/reagent/consumable/limejuice = 0)
-// Electric Lime
-/obj/item/seeds/lime/electric
- name = "pack of electric lime seeds"
- desc = "Electrically sour seeds."
- icon_state = "seed-electriclime"
- species = "electric lime"
- plantname = "Electric Lime Tree"
- growing_icon = 'icons/obj/hydroponics/growing_fruits.dmi'
- icon_grow = "lime-grow"
- icon_dead = "lime-dead"
- icon_harvest = "lime-harvest"
- product = /obj/item/reagent_containers/food/snacks/grown/citrus/lime/electric
- genes = list(/datum/plant_gene/trait/repeated_harvest, /datum/plant_gene/trait/cell_charge, /datum/plant_gene/trait/glow/green)
-
-/obj/item/reagent_containers/food/snacks/grown/citrus/lime/electric
- seed = /obj/item/seeds/lime/electric
- name = "electric lime"
- desc = "It's so sour, you'll be shocked!"
- icon_state = "electriclime"
-
// Orange
/obj/item/seeds/orange
name = "pack of orange seeds"
diff --git a/code/modules/hydroponics/grown/garlic.dm b/code/modules/hydroponics/grown/garlic.dm
index 4184b85008..2cc3f41860 100644
--- a/code/modules/hydroponics/grown/garlic.dm
+++ b/code/modules/hydroponics/grown/garlic.dm
@@ -9,6 +9,9 @@
potency = 25
growthstages = 3
growing_icon = 'icons/obj/hydroponics/growing_vegetables.dmi'
+ icon_grow = "garlic-grow"
+ icon_harvest = "garlic-harvest"
+ icon_dead = "garlic-dead"
reagents_add = list(/datum/reagent/consumable/garlic = 0.15, /datum/reagent/consumable/nutriment = 0.1)
/obj/item/reagent_containers/food/snacks/grown/garlic
diff --git a/code/modules/hydroponics/grown/grass_carpet.dm b/code/modules/hydroponics/grown/grass_carpet.dm
index 3b5159465c..a74850f3be 100644
--- a/code/modules/hydroponics/grown/grass_carpet.dm
+++ b/code/modules/hydroponics/grown/grass_carpet.dm
@@ -51,6 +51,7 @@
icon_grow = "fairygrass-grow"
icon_dead = "fairygrass-dead"
genes = list(/datum/plant_gene/trait/repeated_harvest, /datum/plant_gene/trait/glow/blue)
+ mutatelist = list (/obj/item/seeds/grass/carpet)
reagents_add = list(/datum/reagent/consumable/nutriment = 0.02, /datum/reagent/hydrogen = 0.05, /datum/reagent/drug/space_drugs = 0.15)
/obj/item/reagent_containers/food/snacks/grown/grass/fairy
@@ -99,7 +100,7 @@
species = "carpet"
plantname = "Carpet"
product = /obj/item/reagent_containers/food/snacks/grown/grass/carpet
- mutatelist = list()
+ mutatelist = list(/obj/item/seeds/grass/fairy)
rarity = 10
/obj/item/reagent_containers/food/snacks/grown/grass/carpet
diff --git a/code/modules/hydroponics/grown/tea_coffee.dm b/code/modules/hydroponics/grown/tea_coffee.dm
index de27d1eed7..223b2c7bce 100644
--- a/code/modules/hydroponics/grown/tea_coffee.dm
+++ b/code/modules/hydroponics/grown/tea_coffee.dm
@@ -44,7 +44,7 @@
filling_color = "#4582B4"
grind_results = list(/datum/reagent/toxin/teapowder = 0, /datum/reagent/medicine/salglu_solution = 0)
-// Kitty drugs
+// Catnip
/obj/item/seeds/tea/catnip
name = "pack of catnip seeds"
icon_state = "seed-catnip"
@@ -52,6 +52,9 @@
species = "catnip"
plantname = "Catnip Plant"
growthstages = 3
+ icon_grow = "catnip-grow"
+ icon_harvest = "catnip-harvest"
+ icon_dead = "tea-dead"
product = /obj/item/reagent_containers/food/snacks/grown/tea/catnip
reagents_add = list(/datum/reagent/pax/catnip = 0.1, /datum/reagent/consumable/nutriment/vitamin = 0.06, /datum/reagent/toxin/teapowder = 0.3)
rarity = 50
diff --git a/code/modules/hydroponics/hydroponics.dm b/code/modules/hydroponics/hydroponics.dm
index 06179d1087..a208f2de3c 100644
--- a/code/modules/hydroponics/hydroponics.dm
+++ b/code/modules/hydroponics/hydroponics.dm
@@ -30,7 +30,7 @@
var/self_sufficiency_req = 20 //Required total dose to make a self-sufficient hydro tray. 1:1 with earthsblood.
var/self_sufficiency_progress = 0
var/self_sustaining = FALSE //If the tray generates nutrients and water on its own
-
+ var/canirrigate = TRUE //tin
/obj/machinery/hydroponics/constructable
name = "hydroponics tray"
@@ -847,12 +847,13 @@
if (!anchored)
to_chat(user, "Anchor the tray first!")
return
- using_irrigation = !using_irrigation
- O.play_tool_sound(src)
- user.visible_message("[user] [using_irrigation ? "" : "dis"]connects [src]'s irrigation hoses.", \
- "You [using_irrigation ? "" : "dis"]connect [src]'s irrigation hoses.")
- for(var/obj/machinery/hydroponics/h in range(1,src))
- h.update_icon()
+ if(canirrigate)
+ using_irrigation = !using_irrigation
+ O.play_tool_sound(src)
+ user.visible_message("[user] [using_irrigation ? "" : "dis"]connects [src]'s irrigation hoses.", \
+ "You [using_irrigation ? "" : "dis"]connect [src]'s irrigation hoses.")
+ for(var/obj/machinery/hydroponics/h in range(1,src))
+ h.update_icon()
else if(istype(O, /obj/item/shovel/spade))
if(!myseed && !weedlevel)
@@ -910,11 +911,14 @@
harvest = 0
lastproduce = age
if(istype(myseed, /obj/item/seeds/replicapod))
- to_chat(user, "You harvest from the [myseed.plantname].")
+ if(user)//runtimes
+ to_chat(user, "You harvest from the [myseed.plantname].")
else if(myseed.getYield() <= 0)
- to_chat(user, "You fail to harvest anything useful!")
+ if(user)
+ to_chat(user, "You fail to harvest anything useful!")
else
- to_chat(user, "You harvest [myseed.getYield()] items from the [myseed.plantname].")
+ if(user)
+ to_chat(user, "You harvest [myseed.getYield()] items from the [myseed.plantname].")
if(!myseed.get_gene(/datum/plant_gene/trait/repeated_harvest))
qdel(myseed)
myseed = null
diff --git a/code/modules/hydroponics/seed_extractor.dm b/code/modules/hydroponics/seed_extractor.dm
index 63b96632e6..71701d9637 100644
--- a/code/modules/hydroponics/seed_extractor.dm
+++ b/code/modules/hydroponics/seed_extractor.dm
@@ -1,3 +1,18 @@
+/**
+ * Finds and extracts seeds from an object
+ *
+ * Checks if the object is such that creates a seed when extracted. Used by seed
+ * extractors or posably anything that would create seeds in some way. The seeds
+ * are dropped either at the extractor, if it exists, or where the original object
+ * was and it qdel's the object
+ *
+ * Arguments:
+ * * O - Object containing the seed, can be the loc of the dumping of seeds
+ * * t_max - Amount of seed copies to dump, -1 is ranomized
+ * * extractor - Seed Extractor, used as the dumping loc for the seeds and seed multiplier
+ * * user - checks if we can remove the object from the inventory
+ * *
+ */
/proc/seedify(obj/item/O, t_max, obj/machinery/seed_extractor/extractor, mob/living/user)
var/t_amount = 0
var/list/seeds = list()
@@ -46,20 +61,22 @@
icon_state = "sextractor"
density = TRUE
circuit = /obj/item/circuitboard/machine/seed_extractor
- var/piles = list()
+ /// Associated list of seeds, they are all weak refs. We check the len to see how many refs we have for each
+ // seed
+ var/list/piles = list()
var/max_seeds = 1000
var/seed_multiplier = 1
/obj/machinery/seed_extractor/RefreshParts()
for(var/obj/item/stock_parts/matter_bin/B in component_parts)
- max_seeds = 1000 * B.rating
+ max_seeds = initial(max_seeds) * B.rating
for(var/obj/item/stock_parts/manipulator/M in component_parts)
- seed_multiplier = M.rating
+ seed_multiplier = initial(seed_multiplier) * M.rating
/obj/machinery/seed_extractor/examine(mob/user)
. = ..()
if(in_range(user, src) || isobserver(user))
- . += "The status display reads: Extracting [seed_multiplier] seed(s) per piece of produce. Machine can store up to [max_seeds] seeds."
+ . += "The status display reads: Extracting [seed_multiplier] seed(s) per piece of produce. Machine can store up to [max_seeds]% seeds."
/obj/machinery/seed_extractor/attackby(obj/item/O, mob/user, params)
@@ -102,78 +119,26 @@
else
return ..()
-/datum/seed_pile
- var/name = ""
- var/lifespan = 0 //Saved stats
- var/endurance = 0
- var/maturation = 0
- var/production = 0
- var/yield = 0
- var/potency = 0
- var/amount = 0
+/**
+ * Generate seed string
+ *
+ * Creates a string based of the traits of a seed. We use this string as a bucket for all
+ * seeds that match as well as the key the ui uses to get the seed. We also use the key
+ * for the data shown in the ui. Javascript parses this string to display
+ *
+ * Arguments:
+ * * O - seed to generate the string from
+ */
+/obj/machinery/seed_extractor/proc/generate_seed_string(obj/item/seeds/O)
+ return "name=[O.name];lifespan=[O.lifespan];endurance=[O.endurance];maturation=[O.maturation];production=[O.production];yield=[O.yield];potency=[O.potency];instability=0"
-/datum/seed_pile/New(var/name, var/life, var/endur, var/matur, var/prod, var/yie, var/poten, var/am = 1)
- src.name = name
- src.lifespan = life
- src.endurance = endur
- src.maturation = matur
- src.production = prod
- src.yield = yie
- src.potency = poten
- src.amount = am
-
-/obj/machinery/seed_extractor/ui_interact(mob/user)
- . = ..()
- if (stat)
- return FALSE
-
- var/dat = "Stored seeds: "
-
- if (contents.len == 0)
- dat += "No seeds"
- else
- dat += "
Name
Lifespan
Endurance
Maturation
Production
Yield
Potency
Stock
"
- for (var/datum/seed_pile/O in piles)
- dat += "
Message from [ID.registered_name] to [R] -- \"[message]\" ")
+ to_chat(R.connected_ai, "
Message from [ID] to [R] -- \"[message]\" ")
SEND_SOUND(R.connected_ai, 'sound/machines/twobeep_high.ogg')
+ usr.log_talk(message, LOG_PDA, tag="Cyborg Monitor Program: ID name \"[ID]\" to [R]")
+
+///This proc is used to determin if a borg should be shown in the list (based on the borg's scrambledcodes var). Syndicate version overrides this to show only syndicate borgs.
+/datum/computer_file/program/borg_monitor/proc/evaluate_borg(mob/living/silicon/robot/R)
+ if((get_turf(computer)).z != (get_turf(R)).z)
+ return FALSE
+ if(R.scrambledcodes)
+ return FALSE
+ return TRUE
+
+///Gets the ID's name, if one is inserted into the device. This is a seperate proc solely to be overridden by the syndicate version of the app.
+/datum/computer_file/program/borg_monitor/proc/checkID()
+ var/obj/item/card/id/ID = computer.GetID()
+ if(!ID)
+ return FALSE
+ return ID.registered_name
+
+/datum/computer_file/program/borg_monitor/syndicate
+ filename = "scyborgmonitor"
+ filedesc = "Mission-Specific Cyborg Remote Monitoring"
+ ui_header = "borg_mon.gif"
+ program_icon_state = "generic"
+ extended_desc = "This program allows for remote monitoring of mission-assigned cyborgs."
+ requires_ntnet = FALSE
+ available_on_ntnet = FALSE
+ available_on_syndinet = TRUE
+ transfer_access = null
+ network_destination = "cyborg remote monitoring"
+ tgui_id = "NtosCyborgRemoteMonitorSyndicate"
+
+/datum/computer_file/program/borg_monitor/syndicate/evaluate_borg(mob/living/silicon/robot/R)
+ if((get_turf(computer)).z != (get_turf(R)).z)
+ return FALSE
+ if(!R.scrambledcodes)
+ return FALSE
+ return TRUE
+
+/datum/computer_file/program/borg_monitor/syndicate/checkID()
+ return "\[CLASSIFIED\]" //no ID is needed for the syndicate version's message function, and the borg will see "[CLASSIFIED]" as the message sender.
diff --git a/code/modules/modular_computers/file_system/programs/bounty_board.dm b/code/modules/modular_computers/file_system/programs/bounty_board.dm
new file mode 100644
index 0000000000..46fde84f65
--- /dev/null
+++ b/code/modules/modular_computers/file_system/programs/bounty_board.dm
@@ -0,0 +1,120 @@
+/datum/computer_file/program/bounty_board
+ filename = "bountyboard"
+ filedesc = "Bounty Board Request Network"
+ program_icon_state = "bountyboard"
+ extended_desc = "A multi-platform network for placing requests across the station, with payment across the network being possible.."
+ requires_ntnet = TRUE
+ network_destination = "bounty board interface"
+ size = 10
+ tgui_id = "NtosRequestKiosk"
+ ///Reference to the currently logged in user.
+ var/datum/bank_account/current_user
+ ///The station request datum being affected by UI actions.
+ var/datum/station_request/active_request
+ ///Value of the currently bounty input
+ var/bounty_value = 1
+ ///Text of the currently written bounty
+ var/bounty_text = ""
+ ///Has the app been added to the network yet?
+ var/networked = FALSE
+
+/datum/computer_file/program/bounty_board/ui_data(mob/user)
+ var/list/data = get_header_data()
+ var/list/formatted_requests = list()
+ var/list/formatted_applicants = list()
+ var/obj/item/computer_hardware/card_slot/card_slot = computer.all_components[MC_CARD]
+ if(!networked)
+ GLOB.allbountyboards += computer
+ networked = TRUE
+ if(card_slot && card_slot.stored_card && card_slot.stored_card.registered_account)
+ current_user = card_slot.stored_card.registered_account
+ for(var/i in GLOB.request_list)
+ if(!i)
+ continue
+ var/datum/station_request/request = i
+ formatted_requests += list(list("owner" = request.owner, "value" = request.value, "description" = request.description, "acc_number" = request.req_number))
+ if(request.applicants)
+ for(var/datum/bank_account/j in request.applicants)
+ formatted_applicants += list(list("name" = j.account_holder, "request_id" = request.owner_account.account_id, "requestee_id" = j.account_id))
+ if(current_user)
+ data["accountName"] = current_user.account_holder
+ data["requests"] = formatted_requests
+ data["applicants"] = formatted_applicants
+ data["bountyValue"] = bounty_value
+ data["bountyText"] = bounty_text
+ return data
+
+/datum/computer_file/program/bounty_board/ui_act(action, list/params)
+ if(..())
+ return
+ var/current_ref_num = params["request"]
+ var/current_app_num = params["applicant"]
+ var/datum/bank_account/request_target
+ if(current_ref_num)
+ for(var/datum/station_request/i in GLOB.request_list)
+ if("[i.req_number]" == "[current_ref_num]")
+ active_request = i
+ break
+ if(active_request)
+ for(var/datum/bank_account/j in active_request.applicants)
+ if("[j.account_id]" == "[current_app_num]")
+ request_target = j
+ break
+ switch(action)
+ if("createBounty")
+ if(!current_user || !bounty_text)
+ playsound(src, 'sound/machines/buzz-sigh.ogg', 20, TRUE)
+ return TRUE
+ for(var/datum/station_request/i in GLOB.request_list)
+ if("[i.req_number]" == "[current_user.account_id]")
+ computer.say("Account already has active bounty.")
+ return
+ var/datum/station_request/curr_request = new /datum/station_request(current_user.account_holder, bounty_value,bounty_text,current_user.account_id, current_user)
+ GLOB.request_list += list(curr_request)
+ for(var/obj/i in GLOB.allbountyboards)
+ i.say("New bounty has been added!")
+ playsound(i.loc, 'sound/effects/cashregister.ogg', 30, TRUE)
+ return TRUE
+ if("apply")
+ if(!current_user)
+ computer.say("Please swipe a valid ID first.")
+ return TRUE
+ if(current_user.account_holder == active_request.owner)
+ playsound(computer, 'sound/machines/buzz-sigh.ogg', 20, TRUE)
+ return TRUE
+ active_request.applicants += list(current_user)
+ if("payApplicant")
+ if(!current_user)
+ return
+ if(!current_user.has_money(active_request.value) || (current_user.account_holder != active_request.owner))
+ playsound(computer, 'sound/machines/buzz-sigh.ogg', 30, TRUE)
+ return
+ request_target.transfer_money(current_user, active_request.value)
+ computer.say("Paid out [active_request.value] credits.")
+ return TRUE
+ if("clear")
+ if(current_user)
+ current_user = null
+ computer.say("Account Reset.")
+ return TRUE
+ if("deleteRequest")
+ if(!current_user)
+ playsound(computer, 'sound/machines/buzz-sigh.ogg', 20, TRUE)
+ return TRUE
+ if(active_request.owner != current_user.account_holder)
+ playsound(computer, 'sound/machines/buzz-sigh.ogg', 20, TRUE)
+ return TRUE
+ computer.say("Deleted current request.")
+ GLOB.request_list.Remove(active_request)
+ return TRUE
+ if("bountyVal")
+ bounty_value = text2num(params["bountyval"])
+ if(!bounty_value)
+ bounty_value = 1
+ if("bountyText")
+ bounty_text = (params["bountytext"])
+ . = TRUE
+
+/datum/computer_file/program/bounty_board/Destroy()
+ GLOB.allbountyboards -= computer
+ . = ..()
diff --git a/code/modules/modular_computers/file_system/programs/card.dm b/code/modules/modular_computers/file_system/programs/card.dm
index 07d39438d8..842d6e2588 100644
--- a/code/modules/modular_computers/file_system/programs/card.dm
+++ b/code/modules/modular_computers/file_system/programs/card.dm
@@ -15,8 +15,6 @@
requires_ntnet = 0
size = 8
tgui_id = "NtosCard"
- ui_x = 450
- ui_y = 520
var/is_centcom = FALSE
var/minor = FALSE
@@ -275,7 +273,7 @@
departments = list("CentCom" = get_all_centcom_jobs())
else if(isnull(departments))
departments = list(
- CARDCON_DEPARTMENT_COMMAND = list("Captain"),
+ CARDCON_DEPARTMENT_COMMAND = list("Captain"),//lol
CARDCON_DEPARTMENT_ENGINEERING = GLOB.engineering_positions,
CARDCON_DEPARTMENT_MEDICAL = GLOB.medical_positions,
CARDCON_DEPARTMENT_SCIENCE = GLOB.science_positions,
diff --git a/code/modules/modular_computers/file_system/programs/cargobounty.dm b/code/modules/modular_computers/file_system/programs/cargobounty.dm
new file mode 100644
index 0000000000..d9bc65c98d
--- /dev/null
+++ b/code/modules/modular_computers/file_system/programs/cargobounty.dm
@@ -0,0 +1,48 @@
+/datum/computer_file/program/bounty
+ filename = "bounty"
+ filedesc = "Nanotrasen Bounty Hunter"
+ program_icon_state = "bounty"
+ extended_desc = "A basic interface for supply personnel to check and claim bounties."
+ requires_ntnet = TRUE
+ transfer_access = ACCESS_CARGO
+ network_destination = "cargo claims interface"
+ size = 10
+ tgui_id = "NtosBountyConsole"
+ ///cooldown var for printing paper sheets.
+ var/printer_ready = 0
+ ///The cargo account for grabbing the cargo account's credits.
+ var/static/datum/bank_account/cargocash
+
+/datum/computer_file/program/bounty/proc/print_paper()
+ new /obj/item/paper/bounty_printout(get_turf(computer))
+
+/datum/computer_file/program/bounty/ui_interact(mob/user, datum/tgui/ui)
+ if(!GLOB.bounties_list.len)
+ setup_bounties()
+ printer_ready = world.time + PRINTER_TIMEOUT
+ cargocash = SSeconomy.get_dep_account(ACCOUNT_CAR)
+ . = ..()
+
+/datum/computer_file/program/bounty/ui_data(mob/user)
+ var/list/data = get_header_data()
+ var/list/bountyinfo = list()
+ for(var/datum/bounty/B in GLOB.bounties_list)
+ bountyinfo += list(list("name" = B.name, "description" = B.description, "reward_string" = B.reward_string(), "completion_string" = B.completion_string() , "claimed" = B.claimed, "can_claim" = B.can_claim(), "priority" = B.high_priority, "bounty_ref" = REF(B)))
+ data["stored_cash"] = cargocash.account_balance
+ data["bountydata"] = bountyinfo
+ return data
+
+/datum/computer_file/program/bounty/ui_act(action,params)
+ if(..())
+ return
+ switch(action)
+ if("ClaimBounty")
+ var/datum/bounty/cashmoney = locate(params["bounty"]) in GLOB.bounties_list
+ if(cashmoney)
+ cashmoney.claim()
+ return TRUE
+ if("Print")
+ if(printer_ready < world.time)
+ printer_ready = world.time + PRINTER_TIMEOUT
+ print_paper()
+ return
diff --git a/code/modules/modular_computers/file_system/programs/cargoship.dm b/code/modules/modular_computers/file_system/programs/cargoship.dm
index 39543adfa5..3ba08a3719 100644
--- a/code/modules/modular_computers/file_system/programs/cargoship.dm
+++ b/code/modules/modular_computers/file_system/programs/cargoship.dm
@@ -6,11 +6,9 @@
network_destination = "ship scanner"
size = 6
tgui_id = "NtosShipping"
- ui_x = 450
- ui_y = 350
///Account used for creating barcodes.
var/datum/bank_account/payments_acc
- ///The amount which the tagger will recieve for the sale.
+ ///The amount which the tagger will receive for the sale.
var/percent_cut = 20
/datum/computer_file/program/shipping/ui_data(mob/user)
diff --git a/code/modules/modular_computers/file_system/programs/configurator.dm b/code/modules/modular_computers/file_system/programs/configurator.dm
index 76da58ea11..fae06544d5 100644
--- a/code/modules/modular_computers/file_system/programs/configurator.dm
+++ b/code/modules/modular_computers/file_system/programs/configurator.dm
@@ -10,8 +10,6 @@
unsendable = 1
undeletable = 1
size = 4
- ui_x = 420
- ui_y = 630
available_on_ntnet = 0
requires_ntnet = 0
tgui_id = "NtosConfiguration"
diff --git a/code/modules/modular_computers/file_system/programs/crewmanifest.dm b/code/modules/modular_computers/file_system/programs/crewmanifest.dm
index 662c867a39..a1503ce3a8 100644
--- a/code/modules/modular_computers/file_system/programs/crewmanifest.dm
+++ b/code/modules/modular_computers/file_system/programs/crewmanifest.dm
@@ -7,12 +7,10 @@
requires_ntnet = FALSE
size = 4
tgui_id = "NtosCrewManifest"
- ui_x = 400
- ui_y = 480
/datum/computer_file/program/crew_manifest/ui_static_data(mob/user)
var/list/data = list()
- data["manifest"] = GLOB.data_core.get_manifest()
+ data["manifest"] = GLOB.data_core.get_manifest_tg()
return data
/datum/computer_file/program/crew_manifest/ui_data(mob/user)
@@ -41,9 +39,9 @@
if(computer && printer) //This option should never be called if there is no printer
var/contents = {"
Crew Manifest
- [GLOB.data_core ? GLOB.data_core.get_manifest_html(0) : ""]
+ [GLOB.data_core ? GLOB.data_core.get_manifest() : ""]
"}
- if(!printer.print_text(contents,text("crew manifest ([])", station_time_timestamp())))
+ if(!printer.print_text(contents,text("crew manifest ([])", STATION_TIME_TIMESTAMP("hh:mm:ss", world.time))))
to_chat(usr, "Hardware error: Printer was unable to print the file. It may be out of paper.")
return
else
diff --git a/code/modules/modular_computers/file_system/programs/jobmanagement.dm b/code/modules/modular_computers/file_system/programs/jobmanagement.dm
index 7b847c123e..bccc6e4dbe 100644
--- a/code/modules/modular_computers/file_system/programs/jobmanagement.dm
+++ b/code/modules/modular_computers/file_system/programs/jobmanagement.dm
@@ -7,8 +7,6 @@
requires_ntnet = 0
size = 4
tgui_id = "NtosJobManager"
- ui_x = 400
- ui_y = 620
var/change_position_cooldown = 30
//Jobs you cannot open new positions for
diff --git a/code/modules/modular_computers/file_system/programs/ntdownloader.dm b/code/modules/modular_computers/file_system/programs/ntdownloader.dm
index 352f13f305..6401d6207f 100644
--- a/code/modules/modular_computers/file_system/programs/ntdownloader.dm
+++ b/code/modules/modular_computers/file_system/programs/ntdownloader.dm
@@ -11,8 +11,6 @@
available_on_ntnet = 0
ui_header = "downloader_finished.gif"
tgui_id = "NtosNetDownloader"
- ui_x = 480
- ui_y = 735
var/datum/computer_file/program/downloaded_file = null
var/hacked_download = 0
@@ -20,6 +18,21 @@
var/download_netspeed = 0
var/downloaderror = ""
var/obj/item/modular_computer/my_computer = null
+ var/emagged = FALSE
+ var/list/main_repo
+ var/list/antag_repo
+
+/datum/computer_file/program/ntnetdownload/run_program()
+ . = ..()
+ main_repo = SSnetworks.station_network.available_station_software
+ antag_repo = SSnetworks.station_network.available_antag_software
+
+/datum/computer_file/program/ntnetdownload/run_emag()
+ if(emagged)
+ return FALSE
+ emagged = TRUE
+ return TRUE
+
/datum/computer_file/program/ntnetdownload/proc/begin_file_download(filename)
if(downloaded_file)
@@ -30,8 +43,8 @@
if(!PRG || !istype(PRG))
return 0
- // Attempting to download antag only program, but without having emagged computer. No.
- if(PRG.available_on_syndinet && !(computer.obj_flags & EMAGGED))
+ // Attempting to download antag only program, but without having emagged/syndicate computer. No.
+ if(PRG.available_on_syndinet && !emagged)
return 0
var/obj/item/computer_hardware/hard_drive/hard_drive = computer.all_components[MC_HDD]
@@ -41,10 +54,10 @@
ui_header = "downloader_running.gif"
- if(PRG in SSnetworks.station_network.available_station_software)
+ if(PRG in main_repo)
generate_network_log("Began downloading file [PRG.filename].[PRG.filetype] from NTNet Software Repository.")
hacked_download = 0
- else if(PRG in SSnetworks.station_network.available_antag_software)
+ else if(PRG in antag_repo)
generate_network_log("Began downloading file **ENCRYPTED**.[PRG.filetype] from unspecified server.")
hacked_download = 1
else
@@ -130,7 +143,7 @@
data["disk_size"] = hard_drive.max_capacity
data["disk_used"] = hard_drive.used_capacity
var/list/all_entries[0]
- for(var/A in SSnetworks.station_network.available_station_software)
+ for(var/A in main_repo)
var/datum/computer_file/program/P = A
// Only those programs our user can run will show in the list
if(!P.can_run(user,transfer = 1) || hard_drive.find_file_by_name(P.filename))
@@ -143,9 +156,9 @@
"size" = P.size,
)))
data["hackedavailable"] = FALSE
- if(computer.obj_flags & EMAGGED) // If we are running on emagged computer we have access to some "bonus" software
+ if(emagged) // If we are running on emagged computer we have access to some "bonus" software
var/list/hacked_programs[0]
- for(var/S in SSnetworks.station_network.available_antag_software)
+ for(var/S in antag_repo)
var/datum/computer_file/program/P = S
if(hard_drive.find_file_by_name(P.filename))
continue
@@ -172,3 +185,24 @@
/datum/computer_file/program/ntnetdownload/kill_program(forced)
abort_file_download()
return ..(forced)
+
+////////////////////////
+//Syndicate Downloader//
+////////////////////////
+
+/// This app only lists programs normally found in the emagged section of the normal downloader app
+
+/datum/computer_file/program/ntnetdownload/syndicate
+ filename = "syndownloader"
+ filedesc = "Software Download Tool"
+ program_icon_state = "generic"
+ extended_desc = "This program allows downloads of software from shared Syndicate repositories"
+ requires_ntnet = 0
+ ui_header = "downloader_finished.gif"
+ tgui_id = "NtosNetDownloader"
+ emagged = TRUE
+
+/datum/computer_file/program/ntnetdownload/syndicate/run_program()
+ . = ..()
+ main_repo = SSnetworks.station_network.available_antag_software
+ antag_repo = null
diff --git a/code/modules/modular_computers/file_system/programs/ntnrc_client.dm b/code/modules/modular_computers/file_system/programs/ntnrc_client.dm
index 4ae5bd326b..df9b02d8ec 100644
--- a/code/modules/modular_computers/file_system/programs/ntnrc_client.dm
+++ b/code/modules/modular_computers/file_system/programs/ntnrc_client.dm
@@ -10,9 +10,6 @@
ui_header = "ntnrc_idle.gif"
available_on_ntnet = 1
tgui_id = "NtosNetChat"
- ui_x = 900
- ui_y = 675
-
var/last_message // Used to generate the toolbar icon
var/username
var/active_channel
diff --git a/code/modules/modular_computers/file_system/programs/nttransfer.dm b/code/modules/modular_computers/file_system/programs/nttransfer.dm
deleted file mode 100644
index 698e557941..0000000000
--- a/code/modules/modular_computers/file_system/programs/nttransfer.dm
+++ /dev/null
@@ -1,183 +0,0 @@
-/datum/computer_file/program/nttransfer
- filename = "nttransfer"
- filedesc = "P2P Transfer Client"
- extended_desc = "This program allows for simple file transfer via direct peer to peer connection."
- program_icon_state = "comm_logs"
- size = 7
- requires_ntnet = 1
- requires_ntnet_feature = NTNET_PEERTOPEER
- network_destination = "other device via P2P tunnel"
- available_on_ntnet = 1
- tgui_id = "ntos_net_transfer"
-
- var/error = "" // Error screen
- var/server_password = "" // Optional password to download the file.
- var/datum/computer_file/provided_file = null // File which is provided to clients.
- var/datum/computer_file/downloaded_file = null // File which is being downloaded
- var/list/connected_clients = list() // List of connected clients.
- var/datum/computer_file/program/nttransfer/remote // Client var, specifies who are we downloading from.
- var/download_completion = 0 // Download progress in GQ
- var/download_netspeed = 0 // Our connectivity speed in GQ/s
- var/actual_netspeed = 0 // Displayed in the UI, this is the actual transfer speed.
- var/unique_token // UID of this program
- var/upload_menu = 0 // Whether we show the program list and upload menu
- var/static/nttransfer_uid = 0
-
-/datum/computer_file/program/nttransfer/New()
- unique_token = nttransfer_uid++
- ..()
-
-/datum/computer_file/program/nttransfer/process_tick()
- // Server mode
- update_netspeed()
- if(provided_file)
- for(var/datum/computer_file/program/nttransfer/C in connected_clients)
- // Transfer speed is limited by device which uses slower connectivity.
- // We can have multiple clients downloading at same time, but let's assume we use some sort of multicast transfer
- // so they can all run on same speed.
- C.actual_netspeed = min(C.download_netspeed, download_netspeed)
- C.download_completion += C.actual_netspeed
- if(C.download_completion >= provided_file.size)
- C.finish_download()
- else if(downloaded_file) // Client mode
- if(!remote)
- crash_download("Connection to remote server lost")
-
-/datum/computer_file/program/nttransfer/kill_program(forced = FALSE)
- if(downloaded_file) // Client mode, clean up variables for next use
- finalize_download()
-
- if(provided_file) // Server mode, disconnect all clients
- for(var/datum/computer_file/program/nttransfer/P in connected_clients)
- P.crash_download("Connection terminated by remote server")
- downloaded_file = null
- ..(forced)
-
-/datum/computer_file/program/nttransfer/proc/update_netspeed()
- download_netspeed = 0
- switch(ntnet_status)
- if(1)
- download_netspeed = NTNETSPEED_LOWSIGNAL
- if(2)
- download_netspeed = NTNETSPEED_HIGHSIGNAL
- if(3)
- download_netspeed = NTNETSPEED_ETHERNET
-
-// Finishes download and attempts to store the file on HDD
-/datum/computer_file/program/nttransfer/proc/finish_download()
- var/obj/item/computer_hardware/hard_drive/hard_drive = computer.all_components[MC_HDD]
- if(!computer || !hard_drive || !hard_drive.store_file(downloaded_file))
- error = "I/O Error: Unable to save file. Check your hard drive and try again."
- finalize_download()
-
-// Crashes the download and displays specific error message
-/datum/computer_file/program/nttransfer/proc/crash_download(var/message)
- error = message ? message : "An unknown error has occurred during download"
- finalize_download()
-
-// Cleans up variables for next use
-/datum/computer_file/program/nttransfer/proc/finalize_download()
- if(remote)
- remote.connected_clients.Remove(src)
- downloaded_file = null
- remote = null
- download_completion = 0
-
-/datum/computer_file/program/nttransfer/ui_act(action, params)
- if(..())
- return 1
- switch(action)
- if("PRG_downloadfile")
- for(var/datum/computer_file/program/nttransfer/P in SSnetworks.station_network.fileservers)
- if("[P.unique_token]" == params["id"])
- remote = P
- break
- if(!remote || !remote.provided_file)
- return
- if(remote.server_password)
- var/pass = reject_bad_text(input(usr, "Code 401 Unauthorized. Please enter password:", "Password required"))
- if(pass != remote.server_password)
- error = "Incorrect Password"
- return
- downloaded_file = remote.provided_file.clone()
- remote.connected_clients.Add(src)
- return 1
- if("PRG_reset")
- error = ""
- upload_menu = 0
- finalize_download()
- if(src in SSnetworks.station_network.fileservers)
- SSnetworks.station_network.fileservers.Remove(src)
- for(var/datum/computer_file/program/nttransfer/T in connected_clients)
- T.crash_download("Remote server has forcibly closed the connection")
- provided_file = null
- return 1
- if("PRG_setpassword")
- var/pass = reject_bad_text(input(usr, "Enter new server password. Leave blank to cancel, input 'none' to disable password.", "Server security", "none"))
- if(!pass)
- return
- if(pass == "none")
- server_password = ""
- return
- server_password = pass
- return 1
- if("PRG_uploadfile")
- var/obj/item/computer_hardware/hard_drive/hard_drive = computer.all_components[MC_HDD]
- for(var/datum/computer_file/F in hard_drive.stored_files)
- if("[F.uid]" == params["id"])
- if(F.unsendable)
- error = "I/O Error: File locked."
- return
- if(istype(F, /datum/computer_file/program))
- var/datum/computer_file/program/P = F
- if(!P.can_run(usr,transfer = 1))
- error = "Access Error: Insufficient rights to upload file."
- provided_file = F
- SSnetworks.station_network.fileservers.Add(src)
- return
- error = "I/O Error: Unable to locate file on hard drive."
- return 1
- if("PRG_uploadmenu")
- upload_menu = 1
-
-
-/datum/computer_file/program/nttransfer/ui_data(mob/user)
-
- var/list/data = get_header_data()
-
- if(error)
- data["error"] = error
- else if(downloaded_file)
- data["downloading"] = 1
- data["download_size"] = downloaded_file.size
- data["download_progress"] = download_completion
- data["download_netspeed"] = actual_netspeed
- data["download_name"] = "[downloaded_file.filename].[downloaded_file.filetype]"
- else if (provided_file)
- data["uploading"] = 1
- data["upload_uid"] = unique_token
- data["upload_clients"] = connected_clients.len
- data["upload_haspassword"] = server_password ? 1 : 0
- data["upload_filename"] = "[provided_file.filename].[provided_file.filetype]"
- else if (upload_menu)
- var/list/all_files[0]
- var/obj/item/computer_hardware/hard_drive/hard_drive = computer.all_components[MC_HDD]
- for(var/datum/computer_file/F in hard_drive.stored_files)
- all_files.Add(list(list(
- "uid" = F.uid,
- "filename" = "[F.filename].[F.filetype]",
- "size" = F.size
- )))
- data["upload_filelist"] = all_files
- else
- var/list/all_servers[0]
- for(var/datum/computer_file/program/nttransfer/P in SSnetworks.station_network.fileservers)
- all_servers.Add(list(list(
- "uid" = P.unique_token,
- "filename" = "[P.provided_file.filename].[P.provided_file.filetype]",
- "size" = P.provided_file.size,
- "haspassword" = P.server_password ? 1 : 0
- )))
- data["servers"] = all_servers
-
- return data
\ No newline at end of file
diff --git a/code/modules/modular_computers/file_system/programs/powermonitor.dm b/code/modules/modular_computers/file_system/programs/powermonitor.dm
index d2d57b1447..bd11474858 100644
--- a/code/modules/modular_computers/file_system/programs/powermonitor.dm
+++ b/code/modules/modular_computers/file_system/programs/powermonitor.dm
@@ -12,8 +12,6 @@
network_destination = "power monitoring system"
size = 9
tgui_id = "NtosPowerMonitor"
- ui_x = 550
- ui_y = 700
var/has_alert = 0
var/obj/structure/cable/attached_wire
diff --git a/code/modules/modular_computers/file_system/programs/radar.dm b/code/modules/modular_computers/file_system/programs/radar.dm
index 7a6e80267c..9b0e09ef99 100644
--- a/code/modules/modular_computers/file_system/programs/radar.dm
+++ b/code/modules/modular_computers/file_system/programs/radar.dm
@@ -2,31 +2,49 @@
filename = "genericfinder"
filedesc = "debug_finder"
ui_header = "borg_mon.gif" //DEBUG -- new icon before PR
- program_icon_state = "generic"
- extended_desc = "generic"
+ program_icon_state = "radarntos"
requires_ntnet = TRUE
transfer_access = null
available_on_ntnet = FALSE
+ usage_flags = PROGRAM_LAPTOP | PROGRAM_TABLET
network_destination = "tracking program"
size = 5
tgui_id = "NtosRadar"
- ui_x = 800
- ui_y = 600
- special_assets = list(
- /datum/asset/simple/radar_assets,
- )
///List of trackable entities. Updated by the scan() proc.
var/list/objects
///Ref of the last trackable object selected by the user in the tgui window. Updated in the ui_act() proc.
var/atom/selected
///Used to store when the next scan is available. Updated by the scan() proc.
var/next_scan = 0
+ ///Used to keep track of the last value program_icon_state was set to, to prevent constant unnecessary update_icon() calls
+ var/last_icon_state = ""
+ ///Used by the tgui interface, themed NT or Syndicate.
+ var/arrowstyle = "ntosradarpointer.png"
+ ///Used by the tgui interface, themed for NT or Syndicate colors.
+ var/pointercolor = "green"
+
+/datum/computer_file/program/radar/run_program(mob/living/user)
+ . = ..()
+ if(.)
+ START_PROCESSING(SSfastprocess, src)
+ return
+ return FALSE
/datum/computer_file/program/radar/kill_program(forced = FALSE)
objects = list()
selected = null
+ STOP_PROCESSING(SSfastprocess, src)
return ..()
+/datum/computer_file/program/radar/Destroy()
+ STOP_PROCESSING(SSfastprocess, src)
+ return ..()
+
+/datum/computer_file/program/radar/ui_assets(mob/user)
+ return list(
+ get_asset_datum(/datum/asset/simple/radar_assets),
+ )
+
/datum/computer_file/program/radar/ui_data(mob/user)
var/list/data = get_header_data()
data["selected"] = selected
@@ -65,7 +83,37 @@
*
*/
/datum/computer_file/program/radar/proc/track()
- return
+ var/atom/movable/signal = find_atom()
+ if(!trackable(signal))
+ return
+
+ var/turf/here_turf = (get_turf(computer))
+ var/turf/target_turf = (get_turf(signal))
+ var/userot = FALSE
+ var/rot = 0
+ var/pointer="crosshairs"
+ var/locx = (target_turf.x - here_turf.x) + 24
+ var/locy = (here_turf.y - target_turf.y) + 24
+
+ if(get_dist_euclidian(here_turf, target_turf) > 24)
+ userot = TRUE
+ rot = round(Get_Angle(here_turf, target_turf))
+ else
+ if(target_turf.z > here_turf.z)
+ pointer="caret-up"
+ else if(target_turf.z < here_turf.z)
+ pointer="caret-down"
+
+ var/list/trackinfo = list(
+ "locx" = locx,
+ "locy" = locy,
+ "userot" = userot,
+ "rot" = rot,
+ "arrowstyle" = arrowstyle,
+ "color" = pointercolor,
+ "pointer" = pointer,
+ )
+ return trackinfo
/**
*
@@ -77,10 +125,12 @@
**arg1 is the atom being evaluated.
*/
/datum/computer_file/program/radar/proc/trackable(atom/movable/signal)
- if(!signal)
+ if(!signal || !computer)
return FALSE
var/turf/here = get_turf(computer)
var/turf/there = get_turf(signal)
+ if(!here || !there)
+ return FALSE //I was still getting a runtime even after the above check while scanning, so fuck it
return (there.z == here.z) || (is_station_level(here.z) && is_station_level(there.z))
/**
@@ -98,6 +148,59 @@
/datum/computer_file/program/radar/proc/scan()
return
+/**
+ *
+ *Finds the atom in the appropriate list that the `selected` var indicates
+ *
+ *The `selected` var holds a REF, which is a string. A mob REF may be
+ *something like "mob_209". In order to find the actual atom, we need
+ *to search the appropriate list for the REF string. This is dependant
+ *on the program (Lifeline uses GLOB.human_list, while Fission360 uses
+ *GLOB.poi_list), but the result will be the same; evaluate the string and
+ *return an atom reference.
+*/
+/datum/computer_file/program/radar/proc/find_atom()
+ return
+
+//We use SSfastprocess for the program icon state because it runs faster than process_tick() does.
+/datum/computer_file/program/radar/process()
+ if(computer.active_program != src)
+ STOP_PROCESSING(SSfastprocess, src) //We're not the active program, it's time to stop.
+ return
+ if(!selected)
+ return
+
+ var/atom/movable/signal = find_atom()
+ if(!trackable(signal))
+ program_icon_state = "[initial(program_icon_state)]lost"
+ if(last_icon_state != program_icon_state)
+ computer.update_icon()
+ last_icon_state = program_icon_state
+ return
+
+ var/here_turf = get_turf(computer)
+ var/target_turf = get_turf(signal)
+ var/trackdistance = get_dist_euclidian(here_turf, target_turf)
+ switch(trackdistance)
+ if(0)
+ program_icon_state = "[initial(program_icon_state)]direct"
+ if(1 to 12)
+ program_icon_state = "[initial(program_icon_state)]close"
+ if(13 to 24)
+ program_icon_state = "[initial(program_icon_state)]medium"
+ if(25 to INFINITY)
+ program_icon_state = "[initial(program_icon_state)]far"
+
+ if(last_icon_state != program_icon_state)
+ computer.update_icon()
+ last_icon_state = program_icon_state
+ computer.setDir(get_dir(here_turf, target_turf))
+
+//We can use process_tick to restart fast processing, since the computer will be running this constantly either way.
+/datum/computer_file/program/radar/process_tick()
+ if(computer.active_program == src)
+ START_PROCESSING(SSfastprocess, src)
+
///////////////////
//Suit Sensor App//
///////////////////
@@ -106,44 +209,13 @@
/datum/computer_file/program/radar/lifeline
filename = "Lifeline"
filedesc = "Lifeline"
- program_icon_state = "generic"
extended_desc = "This program allows for tracking of crew members via their suit sensors."
requires_ntnet = TRUE
transfer_access = ACCESS_MEDICAL
available_on_ntnet = TRUE
-/datum/computer_file/program/radar/lifeline/track()
- var/mob/living/carbon/human/humanoid = locate(selected) in GLOB.human_list
- if(!istype(humanoid) || !trackable(humanoid))
- return
-
- var/turf/here_turf = (get_turf(computer))
- var/turf/target_turf = (get_turf(humanoid))
- var/userot = FALSE
- var/rot = 0
- var/pointer="crosshairs"
- var/locx = (target_turf.x - here_turf.x)
- var/locy = (here_turf.y - target_turf.y)
- if(get_dist_euclidian(here_turf, target_turf) > 24) //If they're too far away, we need the angle for the arrow along the edge of the radar display
- userot = TRUE
- rot = round(Get_Angle(here_turf, target_turf))
- else
- locx = locx + 24
- locy = locy + 24
- if(target_turf.z > here_turf.z)
- pointer="caret-up"
- else if(target_turf.z < here_turf.z)
- pointer="caret-down"
- var/list/trackinfo = list(
- locx = locx,
- locy = locy,
- userot = userot,
- rot = rot,
- arrowstyle = "ntosradarpointer.png", //For the rotation arrow, it's stupid I know
- color = "green",
- pointer = pointer,
- )
- return trackinfo
+/datum/computer_file/program/radar/lifeline/find_atom()
+ return locate(selected) in GLOB.human_list
/datum/computer_file/program/radar/lifeline/scan()
if(world.time < next_scan)
@@ -184,46 +256,18 @@
/datum/computer_file/program/radar/fission360
filename = "Fission360"
filedesc = "Fission360"
- program_icon_state = "generic"
+ program_icon_state = "radarsyndicate"
extended_desc = "This program allows for tracking of nuclear authorization disks and warheads."
requires_ntnet = FALSE
transfer_access = null
available_on_ntnet = FALSE
available_on_syndinet = TRUE
tgui_id = "NtosRadarSyndicate"
+ arrowstyle = "ntosradarpointerS.png"
+ pointercolor = "red"
-/datum/computer_file/program/radar/fission360/track()
- var/obj/nuke = locate(selected) in GLOB.poi_list
- if(!trackable(nuke))
- return
-
- var/turf/here_turf = (get_turf(computer))
- var/turf/target_turf = (get_turf(nuke))
- var/userot = FALSE
- var/rot = 0
- var/pointer="crosshairs"
- var/locx = (target_turf.x - here_turf.x)
- var/locy = (here_turf.y - target_turf.y)
- if(get_dist_euclidian(here_turf, target_turf) > 24) //If they're too far away, we need the angle for the arrow along the edge of the radar display
- userot = TRUE
- rot = round(Get_Angle(here_turf, target_turf))
- else
- locx = locx + 24
- locy = locy + 24
- if(target_turf.z > here_turf.z)
- pointer="caret-up"
- else if(target_turf.z < here_turf.z)
- pointer="caret-down"
- var/list/trackinfo = list(
- locx = locx,
- locy = locy,
- userot = userot,
- rot = rot,
- arrowstyle = "ntosradarpointerS.png",
- color = "red",
- pointer = pointer,
- )
- return trackinfo
+/datum/computer_file/program/radar/fission360/find_atom()
+ return locate(selected) in GLOB.poi_list
/datum/computer_file/program/radar/fission360/scan()
if(world.time < next_scan)
diff --git a/code/modules/modular_computers/file_system/programs/robocontrol.dm b/code/modules/modular_computers/file_system/programs/robocontrol.dm
index 910f923327..8644ce09b4 100644
--- a/code/modules/modular_computers/file_system/programs/robocontrol.dm
+++ b/code/modules/modular_computers/file_system/programs/robocontrol.dm
@@ -9,8 +9,6 @@
network_destination = "robotics control network"
size = 12
tgui_id = "NtosRoboControl"
- ui_x = 550
- ui_y = 550
///Number of simple robots on-station.
var/botcount = 0
///Used to find the location of the user for the purposes of summoning robots.
diff --git a/code/modules/modular_computers/file_system/programs/sm_monitor.dm b/code/modules/modular_computers/file_system/programs/sm_monitor.dm
index 0a675a7abc..32ad102871 100644
--- a/code/modules/modular_computers/file_system/programs/sm_monitor.dm
+++ b/code/modules/modular_computers/file_system/programs/sm_monitor.dm
@@ -9,8 +9,6 @@
network_destination = "supermatter monitoring system"
size = 5
tgui_id = "NtosSupermatterMonitor"
- ui_x = 600
- ui_y = 350
var/last_status = SUPERMATTER_INACTIVE
var/list/supermatters
var/obj/machinery/power/supermatter_crystal/active // Currently selected supermatter crystal.
diff --git a/code/modules/modular_computers/hardware/CPU.dm b/code/modules/modular_computers/hardware/CPU.dm
index d08d65ff8b..f13081e1f3 100644
--- a/code/modules/modular_computers/hardware/CPU.dm
+++ b/code/modules/modular_computers/hardware/CPU.dm
@@ -37,4 +37,4 @@
icon_state = "cpu_super"
w_class = WEIGHT_CLASS_TINY
power_usage = 75
- max_idle_programs = 2
\ No newline at end of file
+ max_idle_programs = 2
diff --git a/code/modules/modular_computers/hardware/_hardware.dm b/code/modules/modular_computers/hardware/_hardware.dm
index 37f3fc434e..b33442f99b 100644
--- a/code/modules/modular_computers/hardware/_hardware.dm
+++ b/code/modules/modular_computers/hardware/_hardware.dm
@@ -32,28 +32,29 @@
/obj/item/computer_hardware/attackby(obj/item/I, mob/living/user)
- // Multitool. Runs diagnostics
- if(istype(I, /obj/item/multitool))
- to_chat(user, "***** DIAGNOSTICS REPORT *****")
- diagnostics(user)
- to_chat(user, "******************************")
- return 1
-
// Cable coil. Works as repair method, but will probably require multiple applications and more cable.
if(istype(I, /obj/item/stack/cable_coil))
+ var/obj/item/stack/S = I
if(obj_integrity == max_integrity)
to_chat(user, "\The [src] doesn't seem to require repairs.")
return 1
- if(I.use_tool(src, user, 0, 1))
+ if(S.use(1))
to_chat(user, "You patch up \the [src] with a bit of \the [I].")
obj_integrity = min(obj_integrity + 10, max_integrity)
return 1
if(try_insert(I, user))
- return 1
+ return TRUE
return ..()
+/obj/item/computer_hardware/multitool_act(mob/living/user, obj/item/I)
+ ..()
+ to_chat(user, "***** DIAGNOSTICS REPORT *****")
+ diagnostics(user)
+ to_chat(user, "******************************")
+ return TRUE
+
// Called on multitool click, prints diagnostic information to the user.
/obj/item/computer_hardware/proc/diagnostics(var/mob/user)
to_chat(user, "Hardware Integrity Test... (Corruption: [damage]/[max_damage]) [damage > damage_failure ? "FAIL" : damage > damage_malfunction ? "WARN" : "PASS"]")
diff --git a/code/modules/modular_computers/hardware/ai_slot.dm b/code/modules/modular_computers/hardware/ai_slot.dm
index 8428467a87..0ad157afcb 100644
--- a/code/modules/modular_computers/hardware/ai_slot.dm
+++ b/code/modules/modular_computers/hardware/ai_slot.dm
@@ -9,6 +9,10 @@
var/obj/item/aicard/stored_card = null
var/locked = FALSE
+/obj/item/computer_hardware/ai_slot/handle_atom_del(atom/A)
+ if(A == stored_card)
+ try_eject(0, null, TRUE)
+ . = ..()
/obj/item/computer_hardware/ai_slot/examine(mob/user)
. = ..()
@@ -41,13 +45,6 @@
/obj/item/computer_hardware/ai_slot/try_eject(slot=0,mob/living/user = null,forced = 0)
- if (get_dist(src,user) > 1)
- if (iscarbon(user))
- var/mob/living/carbon/H = user
- if (!(H.dna && H.dna.check_mutation(TK) && tkMaxRangeCheck(src,H)))
- return FALSE
- else
- return FALSE
if(!stored_card)
to_chat(user, "There is no card in \the [src].")
return FALSE
@@ -57,19 +54,21 @@
return FALSE
if(stored_card)
- stored_card.forceMove(get_turf(src))
+ to_chat(user, "You remove [stored_card] from [src].")
locked = FALSE
- stored_card.verb_pickup()
+ if(user)
+ user.put_in_hands(stored_card)
+ else
+ stored_card.forceMove(drop_location())
stored_card = null
- to_chat(user, "You remove the card from \the [src].")
return TRUE
return FALSE
/obj/item/computer_hardware/ai_slot/attackby(obj/item/I, mob/living/user)
if(..())
return
- if(istype(I, /obj/item/screwdriver))
+ if(I.tool_behaviour == TOOL_SCREWDRIVER)
to_chat(user, "You press down on the manual eject button with \the [I].")
try_eject(,user,1)
- return
\ No newline at end of file
+ return
diff --git a/code/modules/modular_computers/hardware/battery_module.dm b/code/modules/modular_computers/hardware/battery_module.dm
index e03427cc9c..6e3193abfd 100644
--- a/code/modules/modular_computers/hardware/battery_module.dm
+++ b/code/modules/modular_computers/hardware/battery_module.dm
@@ -7,6 +7,9 @@
var/obj/item/stock_parts/cell/battery = null
device_type = MC_CELL
+/obj/item/computer_hardware/battery/get_cell()
+ return battery
+
/obj/item/computer_hardware/battery/New(loc, battery_type = null)
if(battery_type)
battery = new battery_type(src)
@@ -16,6 +19,11 @@
. = ..()
QDEL_NULL(battery)
+/obj/item/computer_hardware/battery/handle_atom_del(atom/A)
+ if(A == battery)
+ try_eject(0, null, TRUE)
+ . = ..()
+
/obj/item/computer_hardware/battery/try_insert(obj/item/I, mob/living/user = null)
if(!holder)
return FALSE
@@ -45,7 +53,10 @@
to_chat(user, "There is no power cell connected to \the [src].")
return FALSE
else
- battery.forceMove(get_turf(src))
+ if(user)
+ user.put_in_hands(battery)
+ else
+ battery.forceMove(drop_location())
to_chat(user, "You detach \the [battery] from \the [src].")
battery = null
diff --git a/code/modules/modular_computers/hardware/card_slot.dm b/code/modules/modular_computers/hardware/card_slot.dm
index e4bc45dbc5..18b423a42e 100644
--- a/code/modules/modular_computers/hardware/card_slot.dm
+++ b/code/modules/modular_computers/hardware/card_slot.dm
@@ -9,6 +9,13 @@
var/obj/item/card/id/stored_card = null
var/obj/item/card/id/stored_card2 = null
+/obj/item/computer_hardware/card_slot/handle_atom_del(atom/A)
+ if(A == stored_card)
+ try_eject(1, null, TRUE)
+ if(A == stored_card2)
+ try_eject(2, null, TRUE)
+ . = ..()
+
/obj/item/computer_hardware/card_slot/Destroy()
try_eject()
return ..()
@@ -67,19 +74,15 @@
else
stored_card2 = I
to_chat(user, "You insert \the [I] into \the [src].")
- playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, 0)
+ playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, FALSE)
+ if(ishuman(user))
+ var/mob/living/carbon/human/H = user
+ H.sec_hud_set_ID()
return TRUE
/obj/item/computer_hardware/card_slot/try_eject(slot=0, mob/living/user = null, forced = 0)
- if (get_dist(src,user) > 1)
- if (iscarbon(user))
- var/mob/living/carbon/H = user
- if (!(H.dna && H.dna.check_mutation(TK) && tkMaxRangeCheck(src,H)))
- return FALSE
- else
- return FALSE
if(!stored_card && !stored_card2)
to_chat(user, "There are no cards in \the [src].")
return FALSE
@@ -89,7 +92,7 @@
if(user)
user.put_in_hands(stored_card)
else
- stored_card.forceMove(get_turf(src))
+ stored_card.forceMove(drop_location())
stored_card = null
ejected++
@@ -97,7 +100,7 @@
if(user)
user.put_in_hands(stored_card2)
else
- stored_card2.forceMove(get_turf(src))
+ stored_card2.forceMove(drop_location())
stored_card2 = null
ejected++
@@ -109,16 +112,18 @@
for(var/I in holder.idle_threads)
var/datum/computer_file/program/P = I
P.event_idremoved(1, slot)
-
+ if(ishuman(user))
+ var/mob/living/carbon/human/H = user
+ H.sec_hud_set_ID()
to_chat(user, "You remove the card[ejected>1 ? "s" : ""] from \the [src].")
- playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, 0)
+ playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, FALSE)
return TRUE
return FALSE
/obj/item/computer_hardware/card_slot/attackby(obj/item/I, mob/living/user)
if(..())
return
- if(istype(I, /obj/item/screwdriver))
+ if(I.tool_behaviour == TOOL_SCREWDRIVER)
to_chat(user, "You press down on the manual eject button with \the [I].")
try_eject(0,user)
return
diff --git a/code/modules/modular_computers/hardware/hard_drive.dm b/code/modules/modular_computers/hardware/hard_drive.dm
index e27eaa53ae..b8b9624388 100644
--- a/code/modules/modular_computers/hardware/hard_drive.dm
+++ b/code/modules/modular_computers/hardware/hard_drive.dm
@@ -157,18 +157,30 @@
max_capacity = 64
icon_state = "ssd_mini"
w_class = WEIGHT_CLASS_TINY
- custom_price = PRICE_ABOVE_NORMAL
+ custom_price = 150
-/obj/item/computer_hardware/hard_drive/small/syndicate // Syndicate variant - very slight better
+// Syndicate variant - very slight better
+/obj/item/computer_hardware/hard_drive/small/syndicate
desc = "An efficient SSD for portable devices developed by a rival organisation."
power_usage = 8
max_capacity = 70
var/datum/antagonist/traitor/traitor_data // Syndicate hard drive has the user's data baked directly into it on creation
+/// For tablets given to nuke ops
+/obj/item/computer_hardware/hard_drive/small/nukeops
+ power_usage = 8
+ max_capacity = 70
+
+/obj/item/computer_hardware/hard_drive/small/nukeops/install_default_programs()
+ store_file(new/datum/computer_file/program/computerconfig(src))
+ store_file(new/datum/computer_file/program/ntnetdownload/syndicate(src)) // Syndicate version; automatic access to syndicate apps and no NT apps
+ store_file(new/datum/computer_file/program/filemanager(src))
+ store_file(new/datum/computer_file/program/radar/fission360(src)) //I am legitimately afraid if I don't do this, Ops players will think they just don't get a pinpointer anymore.
+
/obj/item/computer_hardware/hard_drive/micro
name = "micro solid state drive"
desc = "A highly efficient SSD chip for portable devices."
power_usage = 2
max_capacity = 32
icon_state = "ssd_micro"
- w_class = WEIGHT_CLASS_TINY
\ No newline at end of file
+ w_class = WEIGHT_CLASS_TINY
diff --git a/code/modules/modular_computers/hardware/printer.dm b/code/modules/modular_computers/hardware/printer.dm
index 36c666e5d6..ebe40c1922 100644
--- a/code/modules/modular_computers/hardware/printer.dm
+++ b/code/modules/modular_computers/hardware/printer.dm
@@ -10,14 +10,14 @@
/obj/item/computer_hardware/printer/diagnostics(mob/living/user)
..()
- to_chat(user, "Paper level: [stored_paper]/[max_paper].")
+ to_chat(user, "Paper level: [stored_paper]/[max_paper].")
/obj/item/computer_hardware/printer/examine(mob/user)
. = ..()
. += "Paper level: [stored_paper]/[max_paper]."
-/obj/item/computer_hardware/printer/proc/print_text(var/text_to_print, var/paper_title = "")
+/obj/item/computer_hardware/printer/proc/print_text(text_to_print, paper_title = "")
if(!stored_paper)
return FALSE
if(!check_functionality())
@@ -27,11 +27,12 @@
// Damaged printer causes the resulting paper to be somewhat harder to read.
if(damage > damage_malfunction)
- P.setText(stars(text_to_print, 100-malfunction_probability))
+ P.info = stars(text_to_print, 100-malfunction_probability)
else
- P.setText(text_to_print)
+ P.info = text_to_print
if(paper_title)
P.name = paper_title
+ P.update_icon()
stored_paper--
P = null
return TRUE
diff --git a/code/modules/modular_computers/laptop_vendor.dm b/code/modules/modular_computers/laptop_vendor.dm
index ee9def4191..a8d30bad21 100644
--- a/code/modules/modular_computers/laptop_vendor.dm
+++ b/code/modules/modular_computers/laptop_vendor.dm
@@ -27,9 +27,6 @@
var/dev_printer = 0 // 0: None, 1: Standard
var/dev_card = 0 // 0: None, 1: Standard
- ui_x = 500
- ui_y = 400
-
// Removes all traces of old order and allows you to begin configuration from scratch.
/obj/machinery/lapvend/proc/reset_order()
state = 0
@@ -224,15 +221,15 @@
return TRUE
return FALSE
-/obj/machinery/lapvend/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state)
+/obj/machinery/lapvend/ui_interact(mob/user, datum/tgui/ui)
if(stat & (BROKEN | NOPOWER | MAINT))
if(ui)
ui.close()
return FALSE
- ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+ ui = SStgui.try_update_ui(user, src, ui)
if (!ui)
- ui = new(user, src, ui_key, "ComputerFabricator", "Personal Computer Vendor", ui_x, ui_y, state = state)
+ ui = new(user, src, "ComputerFabricator")
ui.open()
/obj/machinery/lapvend/attackby(obj/item/I, mob/user)
@@ -241,7 +238,7 @@
if(!user.temporarilyRemoveItemFromInventory(c))
return
credits += c.value
- visible_message("[user] inserts [c.value] credits into [src].")
+ visible_message("[user] inserts [c.value] cr into [src].")
qdel(c)
return
else if(istype(I, /obj/item/holochip))
@@ -257,10 +254,10 @@
var/datum/bank_account/account = ID.registered_account
var/target_credits = total_price - credits
if(!account.adjust_money(-target_credits))
- say("Insufficient money on card to purchase!")
+ say("Insufficient credits on card to purchase!")
return
credits += target_credits
- say("[target_credits] cr has been desposited from your account.")
+ say("[target_credits] cr has been deposited from your account.")
return
return ..()
@@ -308,4 +305,4 @@
state = 3
addtimer(CALLBACK(src, .proc/reset_order), 100)
return TRUE
- return FALSE
\ No newline at end of file
+ return FALSE
diff --git a/code/modules/newscaster/ghostread.dm b/code/modules/newscaster/ghostread.dm
index 77cb1a03c8..ff51f5268c 100644
--- a/code/modules/newscaster/ghostread.dm
+++ b/code/modules/newscaster/ghostread.dm
@@ -3,7 +3,7 @@
set desc = "Open a list of available news channels"
set category = "Ghost"
- var/datum/browser/B = new(src, "ghost_news_list", "Chanenl List", 450, 600)
+ var/datum/browser/B = new(src, "ghost_news_list", "Channel List", 450, 600)
B.set_content(render_news_channel_list())
B.open()
diff --git a/code/modules/paperwork/paper.dm b/code/modules/paperwork/paper.dm
index 01c25b57f1..5d842ef11a 100644
--- a/code/modules/paperwork/paper.dm
+++ b/code/modules/paperwork/paper.dm
@@ -1,4 +1,4 @@
-/*
+/**
* Paper
* also scraps of paper
*
@@ -11,12 +11,11 @@
#define MODE_WRITING 1
#define MODE_STAMPING 2
-
/**
- ** This is a custom ui state. All it really does is keep track of pen
- ** being used and if they are editing it or not. This way we can keep
- ** the data with the ui rather than on the paper
- **/
+ * This is a custom ui state. All it really does is keep track of pen
+ * being used and if they are editing it or not. This way we can keep
+ * the data with the ui rather than on the paper
+ */
/datum/ui_state/default/paper_state
/// What edit mode we are in and who is
/// writing on it right now
@@ -33,7 +32,6 @@
var/stamp_name = ""
var/stamp_class = ""
-
/datum/ui_state/default/paper_state/proc/copy_from(datum/ui_state/default/paper_state/from)
switch(from.edit_mode)
if(MODE_READING)
@@ -49,12 +47,11 @@
stamp_class = from.stamp_class
stamp_name = from.stamp_name
-
/**
- ** Paper is now using markdown (like in github pull notes) for ALL rendering
- ** so we do loose a bit of functionality but we gain in easy of use of
- ** paper and getting rid of that crashing bug
- **/
+ * Paper is now using markdown (like in github pull notes) for ALL rendering
+ * so we do loose a bit of functionality but we gain in easy of use of
+ * paper and getting rid of that crashing bug
+ */
/obj/item/paper
name = "paper"
gender = NEUTER
@@ -71,6 +68,8 @@
resistance_flags = FLAMMABLE
max_integrity = 50
dog_fashion = /datum/dog_fashion/head
+ // drop_sound = 'sound/items/handling/paper_drop.ogg'
+ // pickup_sound = 'sound/items/handling/paper_pickup.ogg'
grind_results = list(/datum/reagent/cellulose = 3)
color = "white"
/// What's actually written on the paper.
@@ -89,9 +88,6 @@
var/contact_poison // Reagent ID to transfer on contact
var/contact_poison_volume = 0
- // ui stuff
- var/ui_x = 600
- var/ui_y = 800
// Ok, so WHY are we caching the ui's?
// Since we are not using autoupdate we
// need some way to update the ui's of
@@ -103,7 +99,6 @@
// people look at it
var/list/viewing_ui = list()
-
/// When the sheet can be "filled out"
/// This is an associated list
var/list/form_fields = list()
@@ -116,10 +111,10 @@
. = ..()
/**
- ** This proc copies this sheet of paper to a new
- ** sheet, Makes it nice and easy for carbon and
- ** the copyer machine
- **/
+ * This proc copies this sheet of paper to a new
+ * sheet, Makes it nice and easy for carbon and
+ * the copyer machine
+ */
/obj/item/paper/proc/copy()
var/obj/item/paper/N = new(arglist(args))
N.info = info
@@ -133,10 +128,10 @@
return N
/**
- ** This proc sets the text of the paper and updates the
- ** icons. You can modify the pen_color after if need
- ** be.
- **/
+ * This proc sets the text of the paper and updates the
+ * icons. You can modify the pen_color after if need
+ * be.
+ */
/obj/item/paper/proc/setText(text)
info = text
form_fields = null
@@ -152,30 +147,16 @@
contact_poison = null
. = ..()
-
/obj/item/paper/Initialize()
. = ..()
pixel_y = rand(-8, 8)
pixel_x = rand(-9, 9)
update_icon()
-
/obj/item/paper/update_icon_state()
if(info && show_written_words)
icon_state = "[initial(icon_state)]_words"
-/obj/item/paper/ui_base_html(html)
- /// This might change in a future PR
- var/datum/asset/spritesheet/assets = get_asset_datum(/datum/asset/spritesheet/simple/paper)
- . = replacetext(html, "", assets.css_tag())
-
-
-/obj/item/paper/examine_more(mob/user)
- ui_interact(user)
-
-/obj/item/paper/proc/show_content(mob/user)
- user.examinate(src)
-
/obj/item/paper/verb/rename()
set name = "Rename paper"
set category = "Object"
@@ -195,17 +176,14 @@
name = "paper[(n_name ? text("- '[n_name]'") : null)]"
add_fingerprint(usr)
-
/obj/item/paper/suicide_act(mob/user)
user.visible_message("[user] scratches a grid on [user.p_their()] wrist with the paper! It looks like [user.p_theyre()] trying to commit sudoku...")
return (BRUTELOSS)
-
/// ONLY USED FOR APRIL FOOLS
/obj/item/paper/proc/reset_spamflag()
spam_flag = FALSE
-
/obj/item/paper/attack_self(mob/user)
if(rigged && (SSevents.holidays && SSevents.holidays[APRIL_FOOLS]))
if(!spam_flag)
@@ -214,7 +192,6 @@
addtimer(CALLBACK(src, .proc/reset_spamflag), 20)
. = ..()
-
/obj/item/paper/proc/clearpaper()
info = ""
stamps = null
@@ -222,29 +199,28 @@
cut_overlays()
update_icon_state()
-
-/obj/item/paper/examine(mob/user)
+/obj/item/paper/examine_more(mob/user)
ui_interact(user)
-
+ return list("You try to read [src]...")
/obj/item/paper/can_interact(mob/user)
if(!..())
return FALSE
- if(resistance_flags & ON_FIRE) // Are we on fire? Hard ot read if so
+ // Are we on fire? Hard ot read if so
+ if(resistance_flags & ON_FIRE)
return FALSE
- if(user.is_blind()) // Even harder to read if your blind...braile? humm
+ // Even harder to read if your blind...braile? humm
+ if(user.is_blind())
return FALSE
- return user.can_read(src) // checks if the user can read.
-
+ // checks if the user can read.
+ return user.can_read(src)
/**
- ** This creates the ui, since we are using a custom state but not much else
- ** just makes it easyer to make it. Also we make a custom ui_key as I am
- ** not sure how tgui handles many producers?
-**/
+ * This creates the ui, since we are using a custom state but not much else
+ * just makes it easyer to make it.
+ */
/obj/item/paper/proc/create_ui(mob/user, datum/ui_state/default/paper_state/state)
- ui_interact(user, "main", null, FALSE, null, state)
-
+ ui_interact(user, state = state)
/obj/item/proc/burn_paper_product_attackby_check(obj/item/I, mob/living/user, bypass_clumsy)
var/ignition_message = I.ignition_effect(src, user)
@@ -320,24 +296,27 @@
if(.)
info = "[stars(info)]"
-/obj/item/paper/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/default/paper_state/state = new)
- ui_key = "main-[REF(user)]"
- ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+/obj/item/paper/ui_assets(mob/user)
+ return list(
+ get_asset_datum(/datum/asset/spritesheet/simple/paper),
+ )
+
+/obj/item/paper/ui_interact(mob/user, datum/tgui/ui,
+ datum/ui_state/default/paper_state/state)
+ // Update the state
+ ui = ui || SStgui.get_open_ui(user, src)
+ if(ui && state)
+ var/datum/ui_state/default/paper_state/current_state = ui.state
+ current_state.copy_from(state)
+ // Update the UI
+ ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
- var/datum/asset/assets = get_asset_datum(/datum/asset/spritesheet/simple/paper)
- assets.send(user)
- // The x size is because we double the width for the editor
- ui = new(user, src, ui_key, "PaperSheet", name, ui_x, ui_y, master_ui, state)
+ ui = new(user, src, "PaperSheet", name)
+ state = new
+ ui.set_state(state)
ui.set_autoupdate(FALSE)
viewing_ui[user] = ui
ui.open()
- else
- var/datum/ui_state/default/paper_state/last_state = ui.state
- if(last_state)
- last_state.copy_from(state)
- else
- ui.state = state
-
/obj/item/paper/ui_close(mob/user)
/// close the editing window and change the mode
@@ -347,7 +326,7 @@
// Again, we have to do this as autoupdate is off
/obj/item/paper/proc/update_all_ui()
for(var/datum/tgui/ui in viewing_ui)
- ui.update()
+ ui.process(force = TRUE)
// Again, we have to do this as autoupdate is off
/obj/item/paper/proc/close_all_ui()
@@ -383,7 +362,6 @@
return data
-
/obj/item/paper/ui_act(action, params, datum/tgui/ui, datum/ui_state/default/paper_state/state)
if(..())
return
@@ -445,21 +423,18 @@
. = TRUE
-
-/*
+/**
* Construction paper
*/
-
/obj/item/paper/construction
/obj/item/paper/construction/Initialize()
. = ..()
color = pick("FF0000", "#33cc33", "#ffb366", "#551A8B", "#ff80d5", "#4d94ff")
-/*
+/**
* Natural paper
*/
-
/obj/item/paper/natural/Initialize()
. = ..()
color = "#FFF5ED"
diff --git a/code/modules/paperwork/pen.dm b/code/modules/paperwork/pen.dm
index 997ada6b21..91b8a6719b 100644
--- a/code/modules/paperwork/pen.dm
+++ b/code/modules/paperwork/pen.dm
@@ -147,7 +147,7 @@
log_game("[user] [key_name(user)] has renamed [O] to [input]")
if(penchoice == "Change description")
- var/input = stripped_input(user,"Describe \the [O.name] here", ,"", 100)
+ var/input = stripped_input(user,"Describe \the [O.name] here", ,"", 2048)
if(QDELETED(O) || !user.canUseTopic(O, BE_CLOSE))
return
O.desc = input
diff --git a/code/modules/plumbing/ducts.dm b/code/modules/plumbing/ducts.dm
new file mode 100644
index 0000000000..8a27f2669c
--- /dev/null
+++ b/code/modules/plumbing/ducts.dm
@@ -0,0 +1,433 @@
+/*
+All the important duct code:
+/code/datums/components/plumbing/plumbing.dm
+/code/datums/ductnet.dm
+*/
+/obj/machinery/duct
+ name = "fluid duct"
+ icon = 'icons/obj/plumbing/fluid_ducts.dmi'
+ icon_state = "nduct"
+
+ ///bitfield with the directions we're connected in
+ var/connects
+ ///set to TRUE to disable smart duct behaviour
+ var/dumb = FALSE
+ ///wheter we allow our connects to be changed after initialization or not
+ var/lock_connects = FALSE
+ ///our ductnet, wich tracks what we're connected to
+ var/datum/ductnet/duct
+ ///amount we can transfer per process. note that the ductnet can carry as much as the lowest capacity duct
+ var/capacity = 10
+
+ ///the color of our duct
+ var/duct_color = null
+ ///TRUE to ignore colors, so yeah we also connect with other colors without issue
+ var/ignore_colors = FALSE
+ ///1,2,4,8,16
+ var/duct_layer = DUCT_LAYER_DEFAULT
+ ///whether we allow our layers to be altered
+ var/lock_layers = FALSE
+ ///TRUE to let colors connect when forced with a wrench, false to just not do that at all
+ var/color_to_color_support = TRUE
+ ///wheter to even bother with plumbing code or not
+ var/active = TRUE
+ ///track ducts we're connected to. Mainly for ducts we connect to that we normally wouldn't, like different layers and colors, for when we regenerate the ducts
+ var/list/neighbours = list()
+ ///wheter we just unanchored or drop whatever is in the variable. either is safe
+ var/drop_on_wrench = /obj/item/stack/ducts
+
+/obj/machinery/duct/Initialize(mapload, no_anchor, color_of_duct = "#ffffff", layer_of_duct = DUCT_LAYER_DEFAULT, force_connects)
+ . = ..()
+
+ if(no_anchor)
+ active = FALSE
+ set_anchored(FALSE)
+ else if(!can_anchor())
+ qdel(src)
+ CRASH("Overlapping ducts detected")
+
+ if(force_connects)
+ connects = force_connects //skip change_connects() because we're still initializing and we need to set our connects at one point
+ if(!lock_layers)
+ duct_layer = layer_of_duct
+ if(!ignore_colors)
+ duct_color = color_of_duct
+ if(duct_color)
+ add_atom_colour(duct_color, FIXED_COLOUR_PRIORITY)
+
+ handle_layer()
+
+ for(var/obj/machinery/duct/D in loc)
+ if(D == src)
+ continue
+ if(D.duct_layer & duct_layer)
+ disconnect_duct()
+
+ if(active)
+ attempt_connect()
+
+
+///start looking around us for stuff to connect to
+/obj/machinery/duct/proc/attempt_connect()
+
+ for(var/atom/movable/AM in loc)
+ var/datum/component/plumbing/P = AM.GetComponent(/datum/component/plumbing)
+ if(P?.active)
+ disconnect_duct() //let's not built under plumbing machinery
+ return
+ for(var/D in GLOB.cardinals)
+ if(dumb && !(D & connects))
+ continue
+ for(var/atom/movable/AM in get_step(src, D))
+ if(connect_network(AM, D))
+ add_connects(D)
+ update_icon()
+
+///see if whatever we found can be connected to
+/obj/machinery/duct/proc/connect_network(atom/movable/AM, direction, ignore_color)
+ if(istype(AM, /obj/machinery/duct))
+ return connect_duct(AM, direction, ignore_color)
+
+ var/plumber = AM.GetComponent(/datum/component/plumbing)
+ if(!plumber)
+ return
+ return connect_plumber(plumber, direction)
+
+///connect to a duct
+/obj/machinery/duct/proc/connect_duct(obj/machinery/duct/D, direction, ignore_color)
+ var/opposite_dir = turn(direction, 180)
+ if(!active || !D.active)
+ return
+
+ if(!dumb && D.dumb && !(opposite_dir & D.connects))
+ return
+ if(dumb && D.dumb && !(connects & D.connects)) //we eliminated a few more scenarios in attempt connect
+ return
+
+ if((duct == D.duct) && duct)//check if we're not just comparing two null values
+ add_neighbour(D, direction)
+
+ D.add_connects(opposite_dir)
+ D.update_icon()
+ return TRUE //tell the current pipe to also update it's sprite
+ if(!(D in neighbours)) //we cool
+ if((duct_color != D.duct_color) && !(ignore_colors || D.ignore_colors))
+ return
+ if(!(duct_layer & D.duct_layer))
+ return
+
+ if(D.duct)
+ if(duct)
+ duct.assimilate(D.duct)
+ else
+ D.duct.add_duct(src)
+ else
+ if(duct)
+ duct.add_duct(D)
+ else
+ create_duct()
+ duct.add_duct(D)
+ add_neighbour(D, direction)
+ //tell our buddy its time to pass on the torch of connecting to pipes. This shouldn't ever infinitely loop since it only works on pipes that havent been inductrinated
+ D.attempt_connect()
+
+ return TRUE
+
+///connect to a plumbing object
+/obj/machinery/duct/proc/connect_plumber(datum/component/plumbing/P, direction)
+ var/opposite_dir = turn(direction, 180)
+ if(duct_layer != DUCT_LAYER_DEFAULT) //plumbing devices don't support multilayering. 3 is the default layer so we only use that. We can change this later
+ return FALSE
+
+ if(!P.active)
+ return
+
+ var/comp_directions = P.supply_connects + P.demand_connects //they should never, ever have supply and demand connects overlap or catastrophic failure
+ if(opposite_dir & comp_directions)
+ if(!duct)
+ create_duct()
+ if(duct.add_plumber(P, opposite_dir))
+ neighbours[P.parent] = direction
+ return TRUE
+
+///we disconnect ourself from our neighbours. we also destroy our ductnet and tell our neighbours to make a new one
+/obj/machinery/duct/proc/disconnect_duct(skipanchor)
+ if(!skipanchor) //since set_anchored calls us too.
+ set_anchored(FALSE)
+ active = FALSE
+ if(duct)
+ duct.remove_duct(src)
+ lose_neighbours()
+ reset_connects(0)
+ update_icon()
+ if(ispath(drop_on_wrench) && !QDELING(src))
+ new drop_on_wrench(drop_location())
+ qdel(src)
+
+///''''''''''''''''optimized''''''''''''''''' proc for quickly reconnecting after a duct net was destroyed
+/obj/machinery/duct/proc/reconnect()
+ if(neighbours.len && !duct)
+ create_duct()
+ for(var/atom/movable/AM in neighbours)
+ if(istype(AM, /obj/machinery/duct))
+ var/obj/machinery/duct/D = AM
+ if(D.duct)
+ if(D.duct == duct) //we're already connected
+ continue
+ else
+ duct.assimilate(D.duct)
+ continue
+ else
+ duct.add_duct(D)
+ D.reconnect()
+ else
+ var/datum/component/plumbing/P = AM.GetComponent(/datum/component/plumbing)
+ if(AM in get_step(src, neighbours[AM])) //did we move?
+ if(P)
+ connect_plumber(P, neighbours[AM])
+ else
+ neighbours -= AM //we moved
+
+///Special proc to draw a new connect frame based on neighbours. not the norm so we can support multiple duct kinds
+/obj/machinery/duct/proc/generate_connects()
+ if(lock_connects)
+ return
+ connects = 0
+ for(var/A in neighbours)
+ connects |= neighbours[A]
+ update_icon()
+
+///create a new duct datum
+/obj/machinery/duct/proc/create_duct()
+ duct = new()
+ duct.add_duct(src)
+
+///add a duct as neighbour. this means we're connected and will connect again if we ever regenerate
+/obj/machinery/duct/proc/add_neighbour(obj/machinery/duct/D, direction)
+ if(!(D in neighbours))
+ neighbours[D] = direction
+ if(!(src in D.neighbours))
+ D.neighbours[src] = turn(direction, 180)
+
+///remove all our neighbours, and remove us from our neighbours aswell
+/obj/machinery/duct/proc/lose_neighbours()
+ for(var/obj/machinery/duct/D in neighbours)
+ D.neighbours.Remove(src)
+ neighbours = list()
+
+///add a connect direction
+/obj/machinery/duct/proc/add_connects(new_connects) //make this a define to cut proc calls?
+ if(!lock_connects)
+ connects |= new_connects
+
+///remove a connect direction
+/obj/machinery/duct/proc/remove_connects(dead_connects)
+ if(!lock_connects)
+ connects &= ~dead_connects
+
+///remove our connects
+/obj/machinery/duct/proc/reset_connects()
+ if(!lock_connects)
+ connects = 0
+
+///get a list of the ducts we can connect to if we are dumb
+/obj/machinery/duct/proc/get_adjacent_ducts()
+ var/list/adjacents = list()
+ for(var/A in GLOB.cardinals)
+ if(A & connects)
+ for(var/obj/machinery/duct/D in get_step(src, A))
+ if((turn(A, 180) & D.connects) && D.active)
+ adjacents += D
+ return adjacents
+
+/obj/machinery/duct/update_icon_state()
+ var/temp_icon = initial(icon_state)
+ for(var/D in GLOB.cardinals)
+ if(D & connects)
+ if(D == NORTH)
+ temp_icon += "_n"
+ if(D == SOUTH)
+ temp_icon += "_s"
+ if(D == EAST)
+ temp_icon += "_e"
+ if(D == WEST)
+ temp_icon += "_w"
+ icon_state = temp_icon
+
+///update the layer we are on
+/obj/machinery/duct/proc/handle_layer()
+ var/offset
+ switch(duct_layer)//it's a bitfield, but it's fine because it only works when there's one layer, and multiple layers should be handled differently
+ if(FIRST_DUCT_LAYER)
+ offset = -10
+ if(SECOND_DUCT_LAYER)
+ offset = -5
+ if(THIRD_DUCT_LAYER)
+ offset = 0
+ if(FOURTH_DUCT_LAYER)
+ offset = 5
+ if(FIFTH_DUCT_LAYER)
+ offset = 10
+ pixel_x = offset
+ pixel_y = offset
+
+
+/obj/machinery/duct/set_anchored(anchorvalue)
+ . = ..()
+ if(isnull(.))
+ return
+ if(anchorvalue)
+ active = TRUE
+ attempt_connect()
+ else
+ disconnect_duct(TRUE)
+
+/obj/machinery/duct/wrench_act(mob/living/user, obj/item/I) //I can also be the RPD
+ ..()
+ add_fingerprint(user)
+ I.play_tool_sound(src)
+ if(anchored || can_anchor())
+ set_anchored(!anchored)
+ user.visible_message( \
+ "[user] [anchored ? null : "un"]fastens \the [src].", \
+ "You [anchored ? null : "un"]fasten \the [src].", \
+ "You hear ratcheting.")
+ return TRUE
+///collection of all the sanity checks to prevent us from stacking ducts that shouldn't be stacked
+/obj/machinery/duct/proc/can_anchor(turf/T)
+ if(!T)
+ T = get_turf(src)
+ for(var/obj/machinery/duct/D in T)
+ if(!anchored || D == src)
+ continue
+ for(var/A in GLOB.cardinals)
+ if(A & connects && A & D.connects)
+ return FALSE
+ return TRUE
+
+/obj/machinery/duct/doMove(destination)
+ . = ..()
+ disconnect_duct()
+ anchored = FALSE
+
+/obj/machinery/duct/Destroy()
+ disconnect_duct()
+ return ..()
+
+/obj/machinery/duct/MouseDrop_T(atom/A, mob/living/user)
+ if(!istype(A, /obj/machinery/duct))
+ return
+ var/obj/machinery/duct/D = A
+ var/obj/item/I = user.get_active_held_item()
+ if(I?.tool_behaviour != TOOL_WRENCH)
+ to_chat(user, "You need to be holding a wrench in your active hand to do that!")
+ return
+ if(get_dist(src, D) != 1)
+ return
+ var/direction = get_dir(src, D)
+ if(!(direction in GLOB.cardinals))
+ return
+ if(duct_layer != D.duct_layer)
+ return
+
+ add_connects(direction) //the connect of the other duct is handled in connect_network, but do this here for the parent duct because it's not necessary in normal cases
+ add_neighbour(D, direction)
+ connect_network(D, direction, TRUE)
+ update_icon()
+
+///has a total of 5 layers and doesnt give a shit about color. its also dumb so doesnt autoconnect.
+/obj/machinery/duct/multilayered
+ name = "duct layer-manifold"
+ icon = 'icons/obj/2x2.dmi'
+ icon_state = "multiduct"
+ pixel_x = -15
+ pixel_y = -15
+
+ color_to_color_support = FALSE
+ duct_layer = FIRST_DUCT_LAYER | SECOND_DUCT_LAYER | THIRD_DUCT_LAYER | FOURTH_DUCT_LAYER | FIFTH_DUCT_LAYER
+ drop_on_wrench = null
+
+ lock_connects = TRUE
+ lock_layers = TRUE
+ ignore_colors = TRUE
+ dumb = TRUE
+
+ active = FALSE
+ anchored = FALSE
+
+/obj/machinery/duct/multilayered/Initialize(mapload, no_anchor, color_of_duct, layer_of_duct = DUCT_LAYER_DEFAULT, force_connects)
+ . = ..()
+ update_connects()
+
+/obj/machinery/duct/multilayered/ComponentInitialize()
+ . = ..()
+ AddElement(/datum/element/update_icon_blocker)
+
+/obj/machinery/duct/multilayered/wrench_act(mob/living/user, obj/item/I)
+ . = ..()
+ update_connects()
+
+/obj/machinery/duct/multilayered/proc/update_connects()
+ if(dir & NORTH || dir & SOUTH)
+ connects = NORTH | SOUTH
+ else
+ connects = EAST | WEST
+
+///don't connect to other multilayered stuff because honestly it shouldn't be done and I dont wanna deal with it
+/obj/machinery/duct/multilayered/connect_duct(obj/machinery/duct/D, direction, ignore_color)
+ if(istype(D, /obj/machinery/duct/multilayered))
+ return
+ return ..()
+
+/obj/machinery/duct/multilayered/handle_layer()
+ return
+
+/obj/item/stack/ducts
+ name = "stack of duct"
+ desc = "A stack of fluid ducts."
+ singular_name = "duct"
+ icon = 'icons/obj/plumbing/fluid_ducts.dmi'
+ icon_state = "ducts"
+ custom_materials = list(/datum/material/iron=500)
+ w_class = WEIGHT_CLASS_TINY
+ novariants = FALSE
+ max_amount = 50
+ item_flags = NOBLUDGEON
+ merge_type = /obj/item/stack/ducts
+ ///Color of our duct
+ var/duct_color = "grey"
+ ///Default layer of our duct
+ var/duct_layer = "Default Layer"
+ ///Assoc index with all the available layers. yes five might be a bit much. Colors uses a global by the way
+ var/list/layers = list("First Layer" = FIRST_DUCT_LAYER, "Second Layer" = SECOND_DUCT_LAYER, "Default Layer" = DUCT_LAYER_DEFAULT,
+ "Fourth Layer" = FOURTH_DUCT_LAYER, "Fifth Layer" = FIFTH_DUCT_LAYER)
+
+/obj/item/stack/ducts/examine(mob/user)
+ . = ..()
+ . += "It's current color and layer are [duct_color] and [duct_layer]. Use in-hand to change."
+
+/obj/item/stack/ducts/attack_self(mob/user)
+ var/new_layer = input("Select a layer", "Layer") as null|anything in layers
+ if(new_layer)
+ duct_layer = new_layer
+ var/new_color = input("Select a color", "Color") as null|anything in GLOB.pipe_paint_colors
+ if(new_color)
+ duct_color = new_color
+ add_atom_colour(GLOB.pipe_paint_colors[new_color], FIXED_COLOUR_PRIORITY)
+
+/obj/item/stack/ducts/afterattack(atom/A, user, proximity)
+ . = ..()
+ if(!proximity)
+ return
+ if(istype(A, /obj/machinery/duct))
+ var/obj/machinery/duct/D = A
+ if(!D.anchored)
+ add(1)
+ qdel(D)
+ if(istype(A, /turf/open) && use(1))
+ var/turf/open/OT = A
+ new /obj/machinery/duct(OT, FALSE, GLOB.pipe_paint_colors[duct_color], layers[duct_layer])
+ playsound(get_turf(src), 'sound/machines/click.ogg', 50, TRUE)
+
+/obj/item/stack/ducts/fifty
+ amount = 50
diff --git a/code/modules/plumbing/plumbers/_plumb_machinery.dm b/code/modules/plumbing/plumbers/_plumb_machinery.dm
new file mode 100644
index 0000000000..0566945e3b
--- /dev/null
+++ b/code/modules/plumbing/plumbers/_plumb_machinery.dm
@@ -0,0 +1,98 @@
+/**Basic plumbing object.
+* It doesn't really hold anything special, YET.
+* Objects that are plumbing but not a subtype are as of writing liquid pumps and the reagent_dispenser tank
+* Also please note that the plumbing component is toggled on and off by the component using a signal from default_unfasten_wrench, so dont worry about it
+*/
+/obj/machinery/plumbing
+ name = "pipe thing"
+ icon = 'icons/obj/plumbing/plumbers.dmi'
+ icon_state = "pump"
+ density = TRUE
+ active_power_usage = 30
+ use_power = ACTIVE_POWER_USE
+ resistance_flags = FIRE_PROOF | UNACIDABLE | ACID_PROOF
+ ///Plumbing machinery is always gonna need reagents, so we might aswell put it here
+ var/buffer = 50
+ ///Flags for reagents, like INJECTABLE, TRANSPARENT bla bla everything thats in DEFINES/reagents.dm
+ var/reagent_flags = TRANSPARENT
+ ///wheter we partake in rcd construction or not
+ var/rcd_constructable = TRUE
+ ///cost of the plumbing rcd construction
+ var/rcd_cost = 15
+ ///delay of constructing it throught the plumbing rcd
+ var/rcd_delay = 10
+
+/obj/machinery/plumbing/Initialize(mapload, bolt = TRUE)
+ . = ..()
+ anchored = bolt
+ create_reagents(buffer, reagent_flags)
+ AddComponent(/datum/component/simple_rotation, ROTATION_ALTCLICK | ROTATION_CLOCKWISE | ROTATION_COUNTERCLOCKWISE | ROTATION_VERBS, null, CALLBACK(src, .proc/can_be_rotated))
+
+/obj/machinery/plumbing/proc/can_be_rotated(mob/user,rotation_type)
+ return TRUE
+
+
+/obj/machinery/plumbing/examine(mob/user)
+ . = ..()
+ . += "The maximum volume display reads: [reagents.maximum_volume] units."
+
+/obj/machinery/plumbing/wrench_act(mob/living/user, obj/item/I)
+ ..()
+ default_unfasten_wrench(user, I)
+ return TRUE
+
+/obj/machinery/plumbing/plunger_act(obj/item/plunger/P, mob/living/user, reinforced)
+ to_chat(user, "You start furiously plunging [name].")
+ if(do_after(user, 30, target = src))
+ to_chat(user, "You finish plunging the [name].")
+ reagents.reaction(get_turf(src), TOUCH) //splash on the floor
+ reagents.clear_reagents()
+
+/obj/machinery/plumbing/welder_act(mob/living/user, obj/item/I)
+ . = ..()
+ if(anchored)
+ to_chat(user, "The [name] needs to be unbolted to do that!You start slicing the [name] apart.You slice the [name] apart. target_temperature && acclimate_state != COOLING)
+ acclimate_state = COOLING
+ update_icon()
+ if(!emptying)
+ if(reagents.chem_temp >= target_temperature && target_temperature + allowed_temperature_difference >= reagents.chem_temp) //cooling here
+ emptying = TRUE
+ if(reagents.chem_temp <= target_temperature && target_temperature - allowed_temperature_difference <= reagents.chem_temp) //heating here
+ emptying = TRUE
+
+ reagents.adjust_thermal_energy((target_temperature - reagents.chem_temp) * heater_coefficient * SPECIFIC_HEAT_DEFAULT * reagents.total_volume) //keep constant with chem heater
+ reagents.handle_reactions()
+
+/obj/machinery/plumbing/acclimator/update_icon()
+ icon_state = initial(icon_state)
+ switch(acclimate_state)
+ if(COOLING)
+ icon_state += "_cold"
+ if(HEATING)
+ icon_state += "_hot"
+
+/obj/machinery/plumbing/acclimator/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "ChemAcclimator", name)
+ ui.open()
+
+/obj/machinery/plumbing/acclimator/ui_data(mob/user)
+ var/list/data = list()
+
+ data["enabled"] = enabled
+ data["chem_temp"] = reagents.chem_temp
+ data["target_temperature"] = target_temperature
+ data["allowed_temperature_difference"] = allowed_temperature_difference
+ data["acclimate_state"] = acclimate_state
+ data["max_volume"] = reagents.maximum_volume
+ data["reagent_volume"] = reagents.total_volume
+ data["emptying"] = emptying
+ return data
+
+/obj/machinery/plumbing/acclimator/ui_act(action, params)
+ if(..())
+ return
+ . = TRUE
+ switch(action)
+ if("set_target_temperature")
+ var/target = text2num(params["temperature"])
+ target_temperature = clamp(target, 0, 1000)
+ if("set_allowed_temperature_difference")
+ var/target = text2num(params["temperature"])
+ allowed_temperature_difference = clamp(target, 0, 1000)
+ if("toggle_power")
+ enabled = !enabled
+ if("change_volume")
+ var/target = text2num(params["volume"])
+ reagents.maximum_volume = clamp(round(target), 1, buffer)
+
+#undef COOLING
+#undef HEATING
+#undef NEUTRAL
diff --git a/code/modules/plumbing/plumbers/autohydro.dm b/code/modules/plumbing/plumbers/autohydro.dm
new file mode 100644
index 0000000000..dbc70dfcf5
--- /dev/null
+++ b/code/modules/plumbing/plumbers/autohydro.dm
@@ -0,0 +1,65 @@
+/obj/machinery/hydroponics/constructable/automagic
+ name = "automated hydroponics system"
+ desc = "The bane of botanists everywhere. Accepts chemical reagents via plumbing, automatically harvests and removes dead plants."
+ obj_flags = CAN_BE_HIT | UNIQUE_RENAME
+ circuit = /obj/item/circuitboard/machine/hydroponics/automagic
+ self_sufficiency_req = 400 //automating hydroponics makes gaia sad so she needs more drugs to turn they tray godly.
+ canirrigate = FALSE
+
+
+/obj/machinery/hydroponics/constructable/automagic/attackby(obj/item/O, mob/user, params)
+ if(istype(O, /obj/item/reagent_containers))
+ return FALSE //avoid fucky wuckies
+ ..()
+
+/obj/machinery/hydroponics/constructable/automagic/default_unfasten_wrench(mob/user, obj/item/I, time = 20)
+ . = ..()
+ if(. == SUCCESSFUL_UNFASTEN)
+ user.visible_message("[user.name] [anchored ? "fasten" : "unfasten"] [src]", \
+ "You [anchored ? "fasten" : "unfasten"] [src]")
+ var/datum/component/plumbing/CP = GetComponent(/datum/component/plumbing)
+ if(anchored)
+ CP.enable()
+ else
+ CP.disable()
+
+/obj/machinery/hydroponics/constructable/automagic/Destroy()
+ . = ..()
+ STOP_PROCESSING(SSobj, src)
+
+/obj/machinery/hydroponics/constructable/automagic/Initialize(mapload)
+ . = ..()
+ START_PROCESSING(SSobj, src)
+ create_reagents(100 , AMOUNT_VISIBLE)
+
+/obj/machinery/hydroponics/constructable/automagic/ComponentInitialize()
+ . = ..()
+ AddComponent(/datum/component/simple_rotation, ROTATION_ALTCLICK | ROTATION_CLOCKWISE | ROTATION_COUNTERCLOCKWISE | ROTATION_VERBS, null, CALLBACK(src, .proc/can_be_rotated))
+ AddComponent(/datum/component/plumbing/simple_demand)
+
+/obj/machinery/hydroponics/constructable/proc/can_be_rotated(mob/user, rotation_type)
+ return !anchored
+
+/obj/machinery/hydroponics/constructable/automagic/process()
+ if(reagents)
+ applyChemicals(reagents)
+ reagents.clear_reagents()
+ if(dead)
+ dead = 0
+ qdel(myseed)
+ myseed = null
+ update_icon()
+ name = initial(name)
+ desc = initial(desc)
+ if(harvest)
+ myseed.harvest_userless()
+ harvest = 0
+ lastproduce = age
+ if(!myseed.get_gene(/datum/plant_gene/trait/repeated_harvest))
+ qdel(myseed)
+ myseed = null
+ dead = 0
+ name = initial(name)
+ desc = initial(desc)
+ update_icon()
+ ..()
diff --git a/code/modules/plumbing/plumbers/bottler.dm b/code/modules/plumbing/plumbers/bottler.dm
new file mode 100644
index 0000000000..396c7cac22
--- /dev/null
+++ b/code/modules/plumbing/plumbers/bottler.dm
@@ -0,0 +1,79 @@
+/obj/machinery/plumbing/bottler
+ name = "chemical bottler"
+ desc = "Puts reagents into containers, like bottles and beakers."
+ icon_state = "bottler"
+ layer = ABOVE_ALL_MOB_LAYER
+ reagent_flags = TRANSPARENT | DRAINABLE
+ rcd_cost = 50
+ rcd_delay = 50
+ buffer = 100
+ ///how much do we fill
+ var/wanted_amount = 10
+ ///where things are sent
+ var/turf/goodspot = null
+ ///where things are taken
+ var/turf/inputspot = null
+ ///where beakers that are already full will be sent
+ var/turf/badspot = null
+
+/obj/machinery/plumbing/bottler/Initialize(mapload, bolt)
+ . = ..()
+ AddComponent(/datum/component/plumbing/simple_demand, bolt)
+ setDir(dir)
+
+/obj/machinery/plumbing/bottler/can_be_rotated(mob/user, rotation_type)
+ if(anchored)
+ to_chat(user, "It is fastened to the floor!")
+ return FALSE
+ return TRUE
+
+///changes the tile array
+/obj/machinery/plumbing/bottler/setDir(newdir)
+ . = ..()
+ switch(dir)
+ if(NORTH)
+ goodspot = get_step(get_turf(src), NORTH)
+ inputspot = get_step(get_turf(src), SOUTH)
+ badspot = get_step(get_turf(src), EAST)
+ if(SOUTH)
+ goodspot = get_step(get_turf(src), SOUTH)
+ inputspot = get_step(get_turf(src), NORTH)
+ badspot = get_step(get_turf(src), WEST)
+ if(WEST)
+ goodspot = get_step(get_turf(src), WEST)
+ inputspot = get_step(get_turf(src), EAST)
+ badspot = get_step(get_turf(src), NORTH)
+ if(EAST)
+ goodspot = get_step(get_turf(src), EAST)
+ inputspot = get_step(get_turf(src), WEST)
+ badspot = get_step(get_turf(src), SOUTH)
+
+///changing input ammount with a window
+/obj/machinery/plumbing/bottler/interact(mob/user)
+ . = ..()
+ wanted_amount = clamp(round(input(user,"maximum is 100u","set ammount to fill with") as num|null, 1), 1, 100)
+ reagents.clear_reagents()
+ to_chat(user, " The [src] will now fill for [wanted_amount]u.")
+
+/obj/machinery/plumbing/bottler/process()
+ if(stat & NOPOWER)
+ return
+ ///see if machine has enough to fill
+ if(reagents.total_volume >= wanted_amount && anchored)
+ var/obj/AM = pick(inputspot.contents)///pick a reagent_container that could be used
+ if(istype(AM, /obj/item/reagent_containers) && (!istype(AM, /obj/item/reagent_containers/hypospray/medipen)))
+ var/obj/item/reagent_containers/B = AM
+ ///see if it would overflow else inject
+ if((B.reagents.total_volume + wanted_amount) <= B.reagents.maximum_volume)
+ reagents.trans_to(B, wanted_amount)
+ B.forceMove(goodspot)
+ return
+ ///glass was full so we move it away
+ AM.forceMove(badspot)
+ if(istype(AM, /obj/item/slime_extract)) ///slime extracts need inject
+ AM.forceMove(goodspot)
+ reagents.trans_to(AM, wanted_amount)
+ return
+ if(istype(AM, /obj/item/slimecross/industrial)) ///no need to move slimecross industrial things
+ reagents.trans_to(AM, wanted_amount)
+ return
diff --git a/code/modules/plumbing/plumbers/destroyer.dm b/code/modules/plumbing/plumbers/destroyer.dm
new file mode 100644
index 0000000000..b61383ea4a
--- /dev/null
+++ b/code/modules/plumbing/plumbers/destroyer.dm
@@ -0,0 +1,21 @@
+/obj/machinery/plumbing/disposer
+ name = "chemical disposer"
+ desc = "Breaks down chemicals and annihilates them."
+ icon_state = "disposal"
+ ///we remove 10 reagents per second
+ var/disposal_rate = 10
+
+/obj/machinery/plumbing/disposer/Initialize(mapload, bolt)
+ . = ..()
+ AddComponent(/datum/component/plumbing/simple_demand, bolt)
+
+/obj/machinery/plumbing/disposer/process()
+ if(stat & NOPOWER)
+ return
+ if(reagents.total_volume)
+ if(icon_state != initial(icon_state) + "_working") //threw it here instead of update icon since it only has two states
+ icon_state = initial(icon_state) + "_working"
+ reagents.remove_any(disposal_rate)
+ else
+ if(icon_state != initial(icon_state))
+ icon_state = initial(icon_state)
diff --git a/code/modules/plumbing/plumbers/fermenter.dm b/code/modules/plumbing/plumbers/fermenter.dm
new file mode 100644
index 0000000000..b1e1e4b676
--- /dev/null
+++ b/code/modules/plumbing/plumbers/fermenter.dm
@@ -0,0 +1,59 @@
+/obj/machinery/plumbing/fermenter //FULLY AUTOMATIC BEER BREWING. TRULY, THE FUTURE.
+ name = "chemical fermenter"
+ desc = "Turns plants into various types of booze."
+ icon_state = "fermenter"
+ layer = ABOVE_ALL_MOB_LAYER
+ reagent_flags = TRANSPARENT | DRAINABLE
+ rcd_cost = 30
+ rcd_delay = 30
+ buffer = 400
+ ///input dir
+ var/eat_dir = SOUTH
+
+/obj/machinery/plumbing/fermenter/Initialize(mapload, bolt)
+ . = ..()
+ AddComponent(/datum/component/plumbing/simple_supply, bolt)
+
+/obj/machinery/plumbing/fermenter/can_be_rotated(mob/user,rotation_type)
+ if(anchored)
+ to_chat(user, "It is fastened to the floor!")
+ return FALSE
+ switch(eat_dir)
+ if(WEST)
+ eat_dir = NORTH
+ return TRUE
+ if(EAST)
+ eat_dir = SOUTH
+ return TRUE
+ if(NORTH)
+ eat_dir = EAST
+ return TRUE
+ if(SOUTH)
+ eat_dir = WEST
+ return TRUE
+
+/obj/machinery/plumbing/fermenter/CanPass(atom/movable/AM)
+ . = ..()
+ if(!anchored)
+ return
+ var/move_dir = get_dir(loc, AM.loc)
+ if(move_dir == eat_dir)
+ return TRUE
+
+/obj/machinery/plumbing/fermenter/Crossed(atom/movable/AM)
+ . = ..()
+ ferment(AM)
+
+/obj/machinery/plumbing/fermenter/proc/ferment(atom/AM)
+ if(stat & NOPOWER)
+ return
+ if(reagents.holder_full())
+ return
+ if(!isitem(AM))
+ return
+ if(istype(AM, /obj/item/reagent_containers/food/snacks/grown))
+ var/obj/item/reagent_containers/food/snacks/grown/G = AM
+ if(G.distill_reagent)
+ var/amount = G.seed.potency * 0.25
+ reagents.add_reagent(G.distill_reagent, amount)
+ qdel(G)
diff --git a/code/modules/plumbing/plumbers/filter.dm b/code/modules/plumbing/plumbers/filter.dm
new file mode 100644
index 0000000000..1ffd170507
--- /dev/null
+++ b/code/modules/plumbing/plumbers/filter.dm
@@ -0,0 +1,65 @@
+///chemical plumbing filter. If it's not filtered by left and right, it goes straight.
+/obj/machinery/plumbing/filter
+ name = "chemical filter"
+ desc = "A chemical filter for filtering chemicals. The left and right outputs appear to be from the perspective of the input port."
+ icon_state = "filter"
+ density = FALSE
+
+ ///whitelist of chems id's that go to the left side. Empty to disable port
+ var/list/left = list()
+ ///whitelist of chem id's that go to the right side. Empty to disable port
+ var/list/right = list()
+ ///whitelist of chems but their name instead of path
+ var/list/english_left = list()
+ ///whitelist of chems but their name instead of path
+ var/list/english_right = list()
+
+/obj/machinery/plumbing/filter/Initialize(mapload, bolt)
+ . = ..()
+ AddComponent(/datum/component/plumbing/filter, bolt)
+
+/obj/machinery/plumbing/filter/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "ChemFilter", name)
+ ui.open()
+
+/obj/machinery/plumbing/filter/ui_data(mob/user)
+ var/list/data = list()
+ data["left"] = english_left
+ data["right"] = english_right
+ return data
+
+/obj/machinery/plumbing/filter/ui_act(action, params)
+ if(..())
+ return
+ . = TRUE
+ switch(action)
+ if("add")
+ var/new_chem_name = params["name"]
+ var/chem_id = get_chem_id(new_chem_name)
+ if(chem_id)
+ switch(params["which"])
+ if("left")
+ if(!left.Find(chem_id))
+ english_left += new_chem_name
+ left += chem_id
+ if("right")
+ if(!right.Find(chem_id))
+ english_right += new_chem_name
+ right += chem_id
+ else
+ to_chat(usr, "No such known reagent exists!")
+
+ if("remove")
+ var/chem_name = params["reagent"]
+ var/chem_id = get_chem_id(chem_name)
+ switch(params["which"])
+ if("left")
+ if(english_left.Find(chem_name))
+ english_left -= chem_name
+ left -= chem_id
+ if("right")
+ if(english_right.Find(chem_name))
+ english_right -= chem_name
+ right -= chem_id
diff --git a/code/modules/plumbing/plumbers/grinder_chemical.dm b/code/modules/plumbing/plumbers/grinder_chemical.dm
new file mode 100644
index 0000000000..f39c79f906
--- /dev/null
+++ b/code/modules/plumbing/plumbers/grinder_chemical.dm
@@ -0,0 +1,64 @@
+/obj/machinery/plumbing/grinder_chemical
+ name = "chemical grinder"
+ desc = "chemical grinder."
+ icon_state = "grinder_chemical"
+ layer = ABOVE_ALL_MOB_LAYER
+ reagent_flags = TRANSPARENT | DRAINABLE
+ rcd_cost = 30
+ rcd_delay = 30
+ buffer = 400
+ var/eat_dir = NORTH
+
+/obj/machinery/plumbing/grinder_chemical/Initialize(mapload, bolt)
+ . = ..()
+ AddComponent(/datum/component/plumbing/simple_supply, bolt)
+
+/obj/machinery/plumbing/grinder_chemical/can_be_rotated(mob/user,rotation_type)
+ if(anchored)
+ to_chat(user, "It is fastened to the floor!")
+ return FALSE
+ switch(eat_dir)
+ if(WEST)
+ eat_dir = NORTH
+ return TRUE
+ if(EAST)
+ eat_dir = SOUTH
+ return TRUE
+ if(NORTH)
+ eat_dir = EAST
+ return TRUE
+ if(SOUTH)
+ eat_dir = WEST
+ return TRUE
+
+/obj/machinery/plumbing/grinder_chemical/CanPass(atom/movable/AM)
+ . = ..()
+ if(!anchored)
+ return
+ var/move_dir = get_dir(loc, AM.loc)
+ if(move_dir == eat_dir)
+ return TRUE
+
+/obj/machinery/plumbing/grinder_chemical/Crossed(atom/movable/AM)
+ . = ..()
+ grind(AM)
+
+/obj/machinery/plumbing/grinder_chemical/proc/grind(atom/AM)
+ if(stat & NOPOWER)
+ return
+ if(reagents.holder_full())
+ return
+ if(!isitem(AM))
+ return
+ var/obj/item/I = AM
+ if(I.juice_results || I.grind_results)
+ if(I.juice_results)
+ I.on_juice()
+ reagents.add_reagent_list(I.juice_results)
+ if(I.reagents)
+ I.reagents.trans_to(src, I.reagents.total_volume)
+ qdel(I)
+ return
+ I.on_grind()
+ reagents.add_reagent_list(I.grind_results)
+ qdel(I)
diff --git a/code/modules/plumbing/plumbers/medipenrefill.dm b/code/modules/plumbing/plumbers/medipenrefill.dm
new file mode 100644
index 0000000000..fb7553a4d5
--- /dev/null
+++ b/code/modules/plumbing/plumbers/medipenrefill.dm
@@ -0,0 +1,94 @@
+/obj/machinery/medipen_refiller
+ name = "Medipen Refiller"
+ desc = "A machine that refills used medipens with chemicals."
+ icon = 'icons/obj/machines/medipen_refiller.dmi'
+ icon_state = "medipen_refiller"
+ density = TRUE
+ circuit = /obj/item/circuitboard/machine/medipen_refiller
+ idle_power_usage = 100
+ /// list of medipen subtypes it can refill
+ var/list/allowed = list(/obj/item/reagent_containers/hypospray/medipen = /datum/reagent/medicine/epinephrine,
+ /obj/item/reagent_containers/hypospray/medipen/ekit = /datum/reagent/medicine/epinephrine,
+ /obj/item/reagent_containers/hypospray/medipen/firelocker = /datum/reagent/medicine/oxandrolone,
+ /obj/item/reagent_containers/hypospray/medipen/stimpack = /datum/reagent/medicine/ephedrine,
+ /obj/item/reagent_containers/hypospray/medipen/blood_loss = /datum/reagent/medicine/coagulant/weak)
+ /// var to prevent glitches in the animation
+ var/busy = FALSE
+
+/obj/machinery/medipen_refiller/Initialize()
+ . = ..()
+ create_reagents(100, TRANSPARENT)
+ for(var/obj/item/stock_parts/matter_bin/B in component_parts)
+ reagents.maximum_volume += 100 * B.rating
+ AddComponent(/datum/component/plumbing/simple_demand)
+
+
+/obj/machinery/medipen_refiller/RefreshParts()
+ var/new_volume = 100
+ for(var/obj/item/stock_parts/matter_bin/B in component_parts)
+ new_volume += 100 * B.rating
+ if(!reagents)
+ create_reagents(new_volume, TRANSPARENT)
+ reagents.maximum_volume = new_volume
+ return TRUE
+
+/// handles the messages and animation, calls refill to end the animation
+/obj/machinery/medipen_refiller/attackby(obj/item/I, mob/user, params)
+ if(busy)
+ to_chat(user, "The machine is busy.")
+ return
+ 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)
+ if(units)
+ to_chat(user, "You transfer [units] units of the solution to the [name].")
+ return
+ else
+ to_chat(user, "The [name] is full.")
+ return
+ if(istype(I, /obj/item/reagent_containers/hypospray/medipen))
+ var/obj/item/reagent_containers/hypospray/medipen/P = I
+ if(!(LAZYFIND(allowed, P.type)))
+ to_chat(user, "Error! Unknown schematics.")
+ return
+ if(P.reagents?.reagent_list.len)
+ to_chat(user, "The medipen is already filled.")
+ return
+ if(reagents.has_reagent(allowed[P.type], 10))
+ busy = TRUE
+ add_overlay("active")
+ addtimer(CALLBACK(src, .proc/refill, P, user), 20)
+ qdel(P)
+ return
+ to_chat(user, "There aren't enough reagents to finish this operation.")
+ return
+ ..()
+
+/obj/machinery/medipen_refiller/plunger_act(obj/item/plunger/P, mob/living/user, reinforced)
+ to_chat(user, "You start furiously plunging [name].")
+ if(do_after(user, 30, target = src))
+ to_chat(user, "You finish plunging the [name].")
+ reagents.clear_reagents()
+
+/obj/machinery/medipen_refiller/wrench_act(mob/living/user, obj/item/I)
+ ..()
+ default_unfasten_wrench(user, I)
+ return TRUE
+
+/obj/machinery/medipen_refiller/crowbar_act(mob/user, obj/item/I)
+ ..()
+ default_deconstruction_crowbar(I)
+ return TRUE
+
+/obj/machinery/medipen_refiller/screwdriver_act(mob/living/user, obj/item/I)
+ . = ..()
+ if(!.)
+ return default_deconstruction_screwdriver(user, "medipen_refiller_open", "medipen_refiller", I)
+
+/// refills the medipen
+/obj/machinery/medipen_refiller/proc/refill(obj/item/reagent_containers/hypospray/medipen/P, mob/user)
+ new P.type(loc)
+ reagents.remove_reagent(allowed[P.type], 10)
+ cut_overlays()
+ busy = FALSE
+ to_chat(user, "Medipen refilled.")
diff --git a/code/modules/plumbing/plumbers/pill_press.dm b/code/modules/plumbing/plumbers/pill_press.dm
new file mode 100644
index 0000000000..56510fac87
--- /dev/null
+++ b/code/modules/plumbing/plumbers/pill_press.dm
@@ -0,0 +1,127 @@
+///We take a constant input of reagents, and produce a pill once a set volume is reached
+/obj/machinery/plumbing/pill_press
+ name = "chemical press"
+ desc = "A press that makes pills, patches and bottles."
+ icon_state = "pill_press"
+ ///maximum size of a pill
+ var/max_pill_volume = 50
+ ///maximum size of a patch
+ var/max_patch_volume = 40
+ ///maximum size of a bottle
+ var/max_bottle_volume = 30
+ ///current operating product (pills or patches)
+ var/product = "pill"
+ ///the minimum size a pill or patch can be
+ var/min_volume = 5
+ ///the maximum size a pill or patch can be
+ var/max_volume = 50
+ ///selected size of the product
+ var/current_volume = 10
+ ///prefix for the product name
+ var/product_name = "factory"
+ ///the icon_state number for the pill.
+ var/pill_number = RANDOM_PILL_STYLE
+ ///list of id's and icons for the pill selection of the ui
+ var/list/pill_styles
+ ///list of products stored in the machine, so we dont have 610 pills on one tile
+ var/list/stored_products = list()
+ ///max amount of pills allowed on our tile before we start storing them instead
+ var/max_floor_products = 50 //haha massive pill piles
+
+/obj/machinery/plumbing/pill_press/examine(mob/user)
+ . = ..()
+ . += "The [name] currently has [stored_products.len] stored. There needs to be less than [max_floor_products ] on the floor to continue dispensing."
+
+/obj/machinery/plumbing/pill_press/Initialize(mapload, bolt)
+ . = ..()
+ AddComponent(/datum/component/plumbing/simple_demand, bolt)
+
+ //expertly copypasted from chemmasters
+ var/datum/asset/spritesheet/simple/assets = get_asset_datum(/datum/asset/spritesheet/simple/pills)
+ pill_styles = list()
+ for (var/x in 1 to PILL_STYLE_COUNT)
+ var/list/SL = list()
+ SL["id"] = x
+ SL["htmltag"] = assets.icon_tag("pill[x]")
+ pill_styles += list(SL)
+
+
+/obj/machinery/plumbing/pill_press/process()
+ if(stat & NOPOWER)
+ return
+ if(reagents.total_volume >= current_volume)
+ if (product == "pill")
+ var/obj/item/reagent_containers/pill/P = new(src)
+ reagents.trans_to(P, current_volume)
+ P.name = trim("[product_name] pill")
+ stored_products += P
+ if(pill_number == RANDOM_PILL_STYLE)
+ P.icon_state = "pill[rand(1,21)]"
+ else
+ P.icon_state = "pill[pill_number]"
+ if(P.icon_state == "pill4") //mirrored from chem masters
+ 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."
+ else if (product == "patch")
+ var/obj/item/reagent_containers/pill/patch/P = new(src)
+ reagents.trans_to(P, current_volume)
+ P.name = trim("[product_name] patch")
+ stored_products += P
+ else if (product == "bottle")
+ var/obj/item/reagent_containers/glass/bottle/P = new(src)
+ reagents.trans_to(P, current_volume)
+ P.name = trim("[product_name] bottle")
+ stored_products += P
+ if(stored_products.len)
+ var/pill_amount = 0
+ for(var/obj/item/reagent_containers/pill/P in loc)
+ pill_amount++
+ if(pill_amount >= max_floor_products) //too much so just stop
+ break
+ if(pill_amount < max_floor_products)
+ var/atom/movable/AM = stored_products[1] //AM because forceMove is all we need
+ stored_products -= AM
+ AM.forceMove(drop_location())
+
+
+/obj/machinery/plumbing/pill_press/ui_assets(mob/user)
+ return list(
+ get_asset_datum(/datum/asset/spritesheet/simple/pills),
+ )
+
+/obj/machinery/plumbing/pill_press/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "ChemPress", name)
+ ui.open()
+
+/obj/machinery/plumbing/pill_press/ui_data(mob/user)
+ var/list/data = list()
+ data["pill_style"] = pill_number
+ data["current_volume"] = current_volume
+ data["product_name"] = product_name
+ data["pill_styles"] = pill_styles
+ data["product"] = product
+ data["min_volume"] = min_volume
+ data["max_volume"] = max_volume
+ return data
+
+/obj/machinery/plumbing/pill_press/ui_act(action, params)
+ if(..())
+ return
+ . = TRUE
+ switch(action)
+ if("change_pill_style")
+ pill_number = clamp(text2num(params["id"]), 1 , PILL_STYLE_COUNT)
+ if("change_current_volume")
+ current_volume = clamp(text2num(params["volume"]), min_volume, max_volume)
+ if("change_product_name")
+ product_name = html_encode(params["name"])
+ if("change_product")
+ product = params["product"]
+ if (product == "pill")
+ max_volume = max_pill_volume
+ else if (product == "patch")
+ max_volume = max_patch_volume
+ else if (product == "bottle")
+ max_volume = max_bottle_volume
+ current_volume = clamp(current_volume, min_volume, max_volume)
diff --git a/code/modules/plumbing/plumbers/pumps.dm b/code/modules/plumbing/plumbers/pumps.dm
new file mode 100644
index 0000000000..c24e48098d
--- /dev/null
+++ b/code/modules/plumbing/plumbers/pumps.dm
@@ -0,0 +1,64 @@
+///We pump liquids from activated(plungerated) geysers to a plumbing outlet. We don't need to be wired.
+/obj/machinery/plumbing/liquid_pump
+ name = "liquid pump"
+ desc = "Pump up those sweet liquids from under the surface. Uses thermal energy from geysers to power itself." //better than placing 200 cables, because it wasnt fun
+ icon = 'icons/obj/plumbing/plumbers.dmi'
+ icon_state = "pump"
+ anchored = FALSE
+ density = TRUE
+ idle_power_usage = 10
+ active_power_usage = 1000
+
+ rcd_cost = 30
+ rcd_delay = 40
+
+ ///units we pump per process (2 seconds)
+ var/pump_power = 2
+ ///set to true if the loop couldnt find a geyser in process, so it remembers and stops checking every loop until moved. more accurate name would be absolutely_no_geyser_under_me_so_dont_try
+ var/geyserless = FALSE
+ ///The geyser object
+ var/obj/structure/geyser/geyser
+ ///volume of our internal buffer
+ var/volume = 200
+
+/obj/machinery/plumbing/liquid_pump/Initialize(mapload, bolt)
+ . = ..()
+ AddComponent(/datum/component/plumbing/simple_supply, bolt)
+
+///please note that the component has a hook in the parent call, wich handles activating and deactivating
+/obj/machinery/plumbing/liquid_pump/default_unfasten_wrench(mob/user, obj/item/I, time = 20)
+ . = ..()
+ if(. == SUCCESSFUL_UNFASTEN)
+ geyser = null
+ update_icon()
+ geyserless = FALSE //we switched state, so lets just set this back aswell
+
+/obj/machinery/plumbing/liquid_pump/process()
+ if(!anchored || panel_open || geyserless)
+ return
+
+ if(!geyser)
+ for(var/obj/structure/geyser/G in loc.contents)
+ geyser = G
+ update_icon()
+ if(!geyser) //we didnt find one, abort
+ geyserless = TRUE
+ visible_message("The [name] makes a sad beep!")
+ playsound(src, 'sound/machines/buzz-sigh.ogg', 50)
+ return
+
+ pump()
+
+///pump up that sweet geyser nectar
+/obj/machinery/plumbing/liquid_pump/proc/pump()
+ if(!geyser || !geyser.reagents)
+ return
+ geyser.reagents.trans_to(src, pump_power)
+
+/obj/machinery/plumbing/liquid_pump/update_icon_state()
+ if(geyser)
+ icon_state = initial(icon_state) + "-on"
+ else if(panel_open)
+ icon_state = initial(icon_state) + "-open"
+ else
+ icon_state = initial(icon_state)
diff --git a/code/modules/plumbing/plumbers/reaction_chamber.dm b/code/modules/plumbing/plumbers/reaction_chamber.dm
new file mode 100644
index 0000000000..949543c300
--- /dev/null
+++ b/code/modules/plumbing/plumbers/reaction_chamber.dm
@@ -0,0 +1,63 @@
+///a reaction chamber for plumbing. pretty much everything can react, but this one keeps the reagents seperated and only reacts under your given terms
+/obj/machinery/plumbing/reaction_chamber
+ name = "reaction chamber"
+ desc = "Keeps chemicals seperated until given conditions are met."
+ icon_state = "reaction_chamber"
+ buffer = 200
+ reagent_flags = TRANSPARENT | NO_REACT
+
+ /**list of set reagents that the reaction_chamber allows in, and must all be present before mixing is enabled.
+ * example: list(/datum/reagent/water = 20, /datum/reagent/fuel/oil = 50)
+ */
+ var/list/required_reagents = list()
+ ///our reagent goal has been reached, so now we lock our inputs and start emptying
+ var/emptying = FALSE
+
+/obj/machinery/plumbing/reaction_chamber/Initialize(mapload, bolt)
+ . = ..()
+ AddComponent(/datum/component/plumbing/reaction_chamber, bolt)
+
+/obj/machinery/plumbing/reaction_chamber/on_reagent_change()
+ if(reagents.total_volume == 0 && emptying) //we were emptying, but now we aren't
+ emptying = FALSE
+ reagent_flags |= NO_REACT
+
+/obj/machinery/plumbing/reaction_chamber/power_change()
+ . = ..()
+ if(use_power != NO_POWER_USE)
+ icon_state = initial(icon_state) + "_on"
+ else
+ icon_state = initial(icon_state)
+
+/obj/machinery/plumbing/reaction_chamber/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "ChemReactionChamber", name)
+ ui.open()
+
+/obj/machinery/plumbing/reaction_chamber/ui_data(mob/user)
+ var/list/data = list()
+ var/list/text_reagents = list()
+ for(var/A in required_reagents) //make a list where the key is text, because that looks alot better in the ui than a typepath
+ var/datum/reagent/R = A
+ text_reagents[initial(R.name)] = required_reagents[R]
+
+ data["reagents"] = text_reagents
+ data["emptying"] = emptying
+ return data
+
+/obj/machinery/plumbing/reaction_chamber/ui_act(action, params)
+ if(..())
+ return
+ . = TRUE
+ switch(action)
+ if("remove")
+ var/reagent = get_chem_id(params["chem"])
+ if(reagent)
+ required_reagents.Remove(reagent)
+ if("add")
+ var/input_reagent = get_chem_id(params["chem"])
+ if(input_reagent && !required_reagents.Find(input_reagent))
+ var/input_amount = text2num(params["amount"])
+ if(input_amount)
+ required_reagents[input_reagent] = input_amount
diff --git a/code/modules/plumbing/plumbers/splitters.dm b/code/modules/plumbing/plumbers/splitters.dm
new file mode 100644
index 0000000000..a26813486c
--- /dev/null
+++ b/code/modules/plumbing/plumbers/splitters.dm
@@ -0,0 +1,50 @@
+///it splits the reagents however you want. So you can "every 60 units, 45 goes left and 15 goes straight". The side direction is EAST, you can change this in the component
+/obj/machinery/plumbing/splitter
+ name = "Chemical Splitter"
+ desc = "A chemical splitter for smart chemical factorization. Waits till a set of conditions is met and then stops all input and splits the buffer evenly or other in two ducts."
+ icon_state = "splitter"
+ buffer = 100
+ density = FALSE
+
+ ///constantly switches between TRUE and FALSE. TRUE means the batch tick goes straight, FALSE means the next batch goes in the side duct.
+ var/turn_straight = TRUE
+ ///how much we must transfer straight. note input can be as high as 10 reagents per process, usually
+ var/transfer_straight = 5
+ ///how much we must transfer to the side
+ var/transfer_side = 5
+ //the maximum you can set the transfer to
+ var/max_transfer = 9
+
+
+/obj/machinery/plumbing/splitter/Initialize(mapload, bolt)
+ . = ..()
+ AddComponent(/datum/component/plumbing/splitter, bolt)
+
+/obj/machinery/plumbing/splitter/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "ChemSplitter", name)
+ ui.open()
+
+/obj/machinery/plumbing/splitter/ui_data(mob/user)
+ var/list/data = list()
+ data["straight"] = transfer_straight
+ data["side"] = transfer_side
+ data["max_transfer"] = max_transfer
+ return data
+
+/obj/machinery/plumbing/splitter/ui_act(action, params)
+ if(..())
+ return
+ . = TRUE
+ switch(action)
+ if("set_amount")
+ var/direction = params["target"]
+ var/value = clamp(text2num(params["amount"]), 1, max_transfer)
+ switch(direction)
+ if("straight")
+ transfer_straight = value
+ if("side")
+ transfer_side = value
+ else
+ return FALSE
diff --git a/code/modules/plumbing/plumbers/synthesizer.dm b/code/modules/plumbing/plumbers/synthesizer.dm
new file mode 100644
index 0000000000..c2bc3439ff
--- /dev/null
+++ b/code/modules/plumbing/plumbers/synthesizer.dm
@@ -0,0 +1,111 @@
+///A single machine that produces a single chem. Can be placed in unison with others through plumbing to create chemical factories
+/obj/machinery/plumbing/synthesizer
+ name = "chemical synthesizer"
+ desc = "Produces a single chemical at a given volume. Must be plumbed. Most effective when working in unison with other chemical synthesizers, heaters and filters."
+
+ icon_state = "synthesizer"
+ icon = 'icons/obj/plumbing/plumbers.dmi'
+ rcd_cost = 25
+ rcd_delay = 15
+
+ ///Amount we produce for every process. Ideally keep under 5 since thats currently the standard duct capacity
+ var/amount = 1
+ ///The maximum we can produce for every process
+ buffer = 5
+ ///I track them here because I have no idea how I'd make tgui loop like that
+ var/static/list/possible_amounts = list(0,1,2,3,4,5)
+ ///The reagent we are producing. We are a typepath, but are also typecast because there's several occations where we need to use initial.
+ var/datum/reagent/reagent_id = null
+ ///straight up copied from chem dispenser. Being a subtype would be extremely tedious and making it global would restrict potential subtypes using different dispensable_reagents
+ var/list/dispensable_reagents = list(
+ /datum/reagent/aluminium,
+ /datum/reagent/bromine,
+ /datum/reagent/carbon,
+ /datum/reagent/chlorine,
+ /datum/reagent/copper,
+ /datum/reagent/consumable/ethanol,
+ /datum/reagent/fluorine,
+ /datum/reagent/hydrogen,
+ /datum/reagent/iodine,
+ /datum/reagent/iron,
+ /datum/reagent/lithium,
+ /datum/reagent/mercury,
+ /datum/reagent/nitrogen,
+ /datum/reagent/oxygen,
+ /datum/reagent/phosphorus,
+ /datum/reagent/potassium,
+ /datum/reagent/radium,
+ /datum/reagent/silicon,
+ /datum/reagent/silver,
+ /datum/reagent/sodium,
+ /datum/reagent/stable_plasma,
+ /datum/reagent/consumable/sugar,
+ /datum/reagent/sulfur,
+ /datum/reagent/toxin/acid,
+ /datum/reagent/water,
+ /datum/reagent/fuel,
+ )
+
+/obj/machinery/plumbing/synthesizer/Initialize(mapload, bolt)
+ . = ..()
+ AddComponent(/datum/component/plumbing/simple_supply, bolt)
+
+/obj/machinery/plumbing/synthesizer/process()
+ if(stat & NOPOWER || !reagent_id || !amount)
+ return
+ if(reagents.total_volume >= amount) //otherwise we get leftovers, and we need this to be precise
+ return
+ reagents.add_reagent(reagent_id, amount)
+
+/obj/machinery/plumbing/synthesizer/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "ChemSynthesizer", name)
+ ui.open()
+
+/obj/machinery/plumbing/synthesizer/ui_data(mob/user)
+ var/list/data = list()
+
+ var/is_hallucinating = user.hallucinating()
+ var/list/chemicals = list()
+
+ for(var/A in dispensable_reagents)
+ var/datum/reagent/R = GLOB.chemical_reagents_list[A]
+ if(R)
+ var/chemname = R.name
+ if(is_hallucinating && prob(5))
+ chemname = "[pick_list_replacements("hallucination.json", "chemicals")]"
+ chemicals.Add(list(list("title" = chemname, "id" = ckey(R.name))))
+ data["chemicals"] = chemicals
+ data["amount"] = amount
+ data["possible_amounts"] = possible_amounts
+
+ data["current_reagent"] = ckey(initial(reagent_id.name))
+ return data
+
+/obj/machinery/plumbing/synthesizer/ui_act(action, params)
+ if(..())
+ return
+ . = TRUE
+ switch(action)
+ if("amount")
+ var/new_amount = text2num(params["target"])
+ if(new_amount in possible_amounts)
+ amount = new_amount
+ . = TRUE
+ if("select")
+ var/new_reagent = GLOB.name2reagent[params["reagent"]]
+ if(new_reagent in dispensable_reagents)
+ reagent_id = new_reagent
+ . = TRUE
+ update_icon()
+ reagents.clear_reagents()
+
+/obj/machinery/plumbing/synthesizer/update_overlays()
+ . = ..()
+ var/mutable_appearance/r_overlay = mutable_appearance(icon, "[icon_state]_overlay")
+ if(reagent_id)
+ r_overlay.color = initial(reagent_id.color)
+ else
+ r_overlay.color = "#FFFFFF"
+ . += r_overlay
diff --git a/code/modules/power/apc.dm b/code/modules/power/apc.dm
index ba51eb3bef..2480288484 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/supermatter/supermatter.dm b/code/modules/power/supermatter/supermatter.dm
index 8559b8ba45..fd8f900552 100644
--- a/code/modules/power/supermatter/supermatter.dm
+++ b/code/modules/power/supermatter/supermatter.dm
@@ -2,34 +2,54 @@
//Please do not bother them with bugs from this port, however, as it has been modified quite a bit.
//Modifications include removing the world-ending full supermatter variation, and leaving only the shard.
+//Zap constants, speeds up targeting
+
+#define BIKE (COIL + 1)
+#define COIL (ROD + 1)
+#define ROD (LIVING + 1)
+#define LIVING (MACHINERY + 1)
+#define MACHINERY (OBJECT + 1)
+#define OBJECT (LOWEST + 1)
+#define LOWEST (1)
+
#define PLASMA_HEAT_PENALTY 15 // Higher == Bigger heat and waste penalty from having the crystal surrounded by this gas. Negative numbers reduce penalty.
#define OXYGEN_HEAT_PENALTY 1
-#define CO2_HEAT_PENALTY 0.1
#define PLUOXIUM_HEAT_PENALTY -1
#define TRITIUM_HEAT_PENALTY 10
+#define CO2_HEAT_PENALTY 0.1
#define NITROGEN_HEAT_PENALTY -1.5
#define BZ_HEAT_PENALTY 5
+#define H2O_HEAT_PENALTY 8
+//#define FREON_HEAT_PENALTY -10 //very good heat absorbtion and less plasma and o2 generation
+//#define HYDROGEN_HEAT_PENALTY 10 // similar heat penalty as tritium (dangerous)
+
+//All of these get divided by 10-bzcomp * 5 before having 1 added and being multiplied with power to determine rads
+//Keep the negative values here above -10 and we won't get negative rads
#define OXYGEN_TRANSMIT_MODIFIER 1.5 //Higher == Bigger bonus to power generation.
#define PLASMA_TRANSMIT_MODIFIER 4
#define BZ_TRANSMIT_MODIFIER -2
+#define TRITIUM_TRANSMIT_MODIFIER 30 //We divide by 10, so this works out to 3
+#define PLUOXIUM_TRANSMIT_MODIFIER -5 //Should halve the power output
+#define H2O_TRANSMIT_MODIFIER 2
+//#define HYDROGEN_TRANSMIT_MODIFIER 25 //increase the radiation emission, but less than the trit (2.5)
-#define TRITIUM_RADIOACTIVITY_MODIFIER 3 //Higher == Crystal spews out more radiation
-#define BZ_RADIOACTIVITY_MODIFIER 5
-#define PLUOXIUM_RADIOACTIVITY_MODIFIER -2
+#define BZ_RADIOACTIVITY_MODIFIER 5 //Improves the effect of transmit modifiers
#define N2O_HEAT_RESISTANCE 6 //Higher == Gas makes the crystal more resistant against heat damage.
#define PLUOXIUM_HEAT_RESISTANCE 3
+//#define HYDROGEN_HEAT_RESISTANCE 2 // just a bit of heat resistance to spice it up
#define POWERLOSS_INHIBITION_GAS_THRESHOLD 0.20 //Higher == Higher percentage of inhibitor gas needed before the charge inertia chain reaction effect starts.
#define POWERLOSS_INHIBITION_MOLE_THRESHOLD 20 //Higher == More moles of the gas are needed before the charge inertia chain reaction effect starts. //Scales powerloss inhibition down until this amount of moles is reached
#define POWERLOSS_INHIBITION_MOLE_BOOST_THRESHOLD 500 //bonus powerloss inhibition boost if this amount of moles is reached
-#define MOLE_PENALTY_THRESHOLD 1800 //Higher == Shard can absorb more moles before triggering the high mole penalties.
+#define MOLE_PENALTY_THRESHOLD 1800 //Above this value we can get lord singulo and independent mol damage, below it we can heal damage
#define MOLE_HEAT_PENALTY 350 //Heat damage scales around this. Too hot setups with this amount of moles do regular damage, anything above and below is scaled
-#define POWER_PENALTY_THRESHOLD 5000 //Higher == Engine can generate more power before triggering the high power penalties.
-#define SEVERE_POWER_PENALTY_THRESHOLD 7000 //Same as above, but causes more dangerous effects
-#define CRITICAL_POWER_PENALTY_THRESHOLD 12000 //Even more dangerous effects, threshold for tesla delamination
+//Along with damage_penalty_point, makes flux anomalies.
+#define POWER_PENALTY_THRESHOLD 5000 //The cutoff on power properly doing damage, pulling shit around, and delamming into a tesla. Low chance of pyro anomalies, +2 bolts of electricity
+#define SEVERE_POWER_PENALTY_THRESHOLD 7000 //+1 bolt of electricity, allows for gravitational anomalies, and higher chances of pyro anomalies
+#define CRITICAL_POWER_PENALTY_THRESHOLD 9000 //+1 bolt of electricity.
#define HEAT_PENALTY_THRESHOLD 40 //Higher == Crystal safe operational temperature is higher.
#define DAMAGE_HARDCAP 0.002
#define DAMAGE_INCREASE_MULTIPLIER 0.25
@@ -66,6 +86,13 @@
#define SUPERMATTER_COUNTDOWN_TIME 30 SECONDS
+///to prevent accent sounds from layering
+#define SUPERMATTER_ACCENT_SOUND_MIN_COOLDOWN 2 SECONDS
+
+#define DEFAULT_ZAP_ICON_STATE "sm_arc"
+#define SLIGHTLY_CHARGED_ZAP_ICON_STATE "sm_arc_supercharged"
+#define OVER_9000_ZAP_ICON_STATE "sm_arc_dbz_referance" //Witty I know
+
GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal)
/obj/machinery/power/supermatter_crystal
@@ -76,89 +103,183 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal)
density = TRUE
anchored = TRUE
flags_1 = PREVENT_CONTENTS_EXPLOSION_1
- var/uid = 1
- var/static/gl_uid = 1
light_range = 4
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF | FREEZE_PROOF
-
critical_machine = TRUE
+ ///The id of our supermatter
+ var/uid = 1
+ ///The amount of supermatters that have been created this round
+ var/static/gl_uid = 1
+ ///Tracks the bolt color we are using
+ var/zap_icon = DEFAULT_ZAP_ICON_STATE
+ ///The portion of the gasmix we're on that we should remove
var/gasefficency = 0.15
-
+ ///Used for changing icon states for diff base sprites
var/base_icon_state = "darkmatter"
+ ///Are we exploding?
var/final_countdown = FALSE
+ ///The amount of damage we have currently
var/damage = 0
+ ///The damage we had before this cycle. Used to limit the damage we can take each cycle, and for safe_alert
var/damage_archived = 0
+ ///Our "Shit is no longer fucked" message. We send it when damage is less then damage_archived
var/safe_alert = "Crystalline hyperstructure returning to safe operating parameters."
+ ///The point at which we should start sending messeges about the damage to the engi channels.
var/warning_point = 50
+ ///The alert we send when we've reached warning_point
var/warning_alert = "Danger! Crystal hyperstructure integrity faltering!"
- var/damage_penalty_point = 550
+ ///The point at which we start sending messages to the common channel
var/emergency_point = 700
+ ///The alert we send when we've reached emergency_point
var/emergency_alert = "CRYSTAL DELAMINATION IMMINENT."
+ ///The point at which we delam
var/explosion_point = 900
+ ///When we pass this amount of damage we start shooting bolts
+ var/damage_penalty_point = 550
- var/emergency_issued = FALSE
-
+ ///A scaling value that affects the severity of explosions.
var/explosion_power = 35
- var/temp_factor = 30
-
- var/lastwarning = 0 // Time in 1/10th of seconds since the last sent warning
+ ///Time in 1/10th of seconds since the last sent warning
+ var/lastwarning = 0
+ ///Refered to as eer on the moniter. This value effects gas output, heat, damage, and radiation.
var/power = 0
-
- var/n2comp = 0 // raw composition of each gas in the chamber, ranges from 0 to 1
-
- var/plasmacomp = 0
- var/o2comp = 0
- var/co2comp = 0
- var/n2ocomp = 0
- var/pluoxiumcomp = 0
- var/tritiumcomp = 0
- var/bzcomp = 0
-
- var/pluoxiumbonus = 0
-
+ ///Determines the rate of positve change in gas comp values
+ var/gas_change_rate = 0.05
+ ///The list of gases we will be interacting with in process_atoms()
+ var/list/gases_we_care_about = list(
+ /datum/gas/oxygen,
+ /datum/gas/water_vapor,
+ /datum/gas/plasma,
+ /datum/gas/carbon_dioxide,
+ /datum/gas/nitrous_oxide,
+ /datum/gas/nitrogen,
+ /datum/gas/pluoxium,
+ /datum/gas/tritium,
+ /datum/gas/bz,
+// /datum/gas/freon,
+// /datum/gas/hydrogen,
+ )
+ ///The list of gases mapped against their current comp. We use this to calculate different values the supermatter uses, like power or heat resistance. It doesn't perfectly match the air around the sm, instead moving up at a rate determined by gas_change_rate per call. Ranges from 0 to 1
+ var/list/gas_comp = list(
+ /datum/gas/oxygen = 0,
+ /datum/gas/water_vapor = 0,
+ /datum/gas/plasma = 0,
+ /datum/gas/carbon_dioxide = 0,
+ /datum/gas/nitrous_oxide = 0,
+ /datum/gas/nitrogen = 0,
+ /datum/gas/pluoxium = 0,
+ /datum/gas/tritium = 0,
+ /datum/gas/bz = 0,
+// /datum/gas/freon = 0,
+// /datum/gas/hydrogen = 0,
+ )
+ ///The list of gases mapped against their transmit values. We use it to determine the effect different gases have on radiation
+ var/list/gas_trans = list(
+ /datum/gas/oxygen = OXYGEN_TRANSMIT_MODIFIER,
+ /datum/gas/water_vapor = H2O_TRANSMIT_MODIFIER,
+ /datum/gas/plasma = PLASMA_TRANSMIT_MODIFIER,
+ /datum/gas/pluoxium = PLUOXIUM_TRANSMIT_MODIFIER,
+ /datum/gas/tritium = TRITIUM_TRANSMIT_MODIFIER,
+ /datum/gas/bz = BZ_TRANSMIT_MODIFIER,
+// /datum/gas/hydrogen = HYDROGEN_TRANSMIT_MODIFIER,
+ )
+ ///The list of gases mapped against their heat penaltys. We use it to determin molar and heat output
+ var/list/gas_heat = list(
+ /datum/gas/oxygen = OXYGEN_HEAT_PENALTY,
+ /datum/gas/water_vapor = H2O_HEAT_PENALTY,
+ /datum/gas/plasma = PLASMA_HEAT_PENALTY,
+ /datum/gas/carbon_dioxide = CO2_HEAT_PENALTY,
+ /datum/gas/nitrogen = NITROGEN_HEAT_PENALTY,
+ /datum/gas/pluoxium = PLUOXIUM_HEAT_PENALTY,
+ /datum/gas/tritium = TRITIUM_HEAT_PENALTY,
+ /datum/gas/bz = BZ_HEAT_PENALTY,
+// /datum/gas/freon = FREON_HEAT_PENALTY,
+// /datum/gas/hydrogen = HYDROGEN_HEAT_PENALTY,
+ )
+ ///The list of gases mapped against their heat resistance. We use it to moderate heat damage.
+ var/list/gas_resist = list(
+ /datum/gas/nitrous_oxide = N2O_HEAT_RESISTANCE,
+ /datum/gas/pluoxium = PLUOXIUM_HEAT_RESISTANCE,
+// /datum/gas/hydrogen = HYDROGEN_HEAT_RESISTANCE,
+ )
+ ///The list of gases mapped against their powermix ratio
+ var/list/gas_powermix = list(
+ /datum/gas/oxygen = 1,
+ /datum/gas/water_vapor = 1,
+ /datum/gas/plasma = 1,
+ /datum/gas/carbon_dioxide = 1,
+ /datum/gas/nitrogen = -1,
+ /datum/gas/pluoxium = -1,
+ /datum/gas/tritium = 1,
+ /datum/gas/bz = 1,
+// /datum/gas/freon = -1,
+// /datum/gas/hydrogen = 1,
+ )
+ ///The last air sample's total molar count, will always be above or equal to 0
var/combined_gas = 0
+ ///Affects the power gain the sm experiances from heat
var/gasmix_power_ratio = 0
+ ///Affects the amount of o2 and plasma the sm outputs, along with the heat it makes.
var/dynamic_heat_modifier = 1
+ ///Affects the amount of damage and minimum point at which the sm takes heat damage
var/dynamic_heat_resistance = 1
+ ///Uses powerloss_dynamic_scaling and combined_gas to lessen the effects of our powerloss functions
var/powerloss_inhibitor = 1
+ ///Based on co2 percentage, slowly moves between 0 and 1. We use it to calc the powerloss_inhibitor
var/powerloss_dynamic_scaling= 0
+ ///Affects the amount of radiation the sm makes. We multiply this with power to find the rads.
var/power_transmission_bonus = 0
+ ///Used to increase or lessen the amount of damage the sm takes from heat based on molar counts.
var/mole_heat_penalty = 0
-
-
+ ///Takes the energy throwing things into the sm generates and slowly turns it into actual power
var/matter_power = 0
+ ///The cutoff for a bolt jumping, grows with heat, lowers with higher mol count,
+ var/zap_cutoff = 1500
+ ///How much the bullets damage should be multiplied by when it is added to the internal variables
+ var/bullet_energy = 2
+ ///How much hallucination should we produce per unit of power?
+ var/hallucination_power = 0.1
- //Temporary values so that we can optimize this
- //How much the bullets damage should be multiplied by when it is added to the internal variables
- var/config_bullet_energy = 2
- //How much of the power is left after processing is finished?
-// var/config_power_reduction_per_tick = 0.5
- //How much hallucination should it produce per unit of power?
- var/config_hallucination_power = 0.1
-
+ ///Our internal radio
var/obj/item/radio/radio
+ ///The key our internal radio uses
var/radio_key = /obj/item/encryptionkey/headset_eng
+ ///The engineering channel
var/engineering_channel = "Engineering"
+ ///The common channel
var/common_channel = null
- //for logging
+ ///Boolean used for logging if we've been powered
var/has_been_powered = FALSE
+ ///Boolean used for logging if we've passed the emergency point
var/has_reached_emergency = FALSE
- // For making hugbox supermatter
- var/takes_damage = TRUE
- var/produces_gas = TRUE
+ ///An effect we show to admins and ghosts the percentage of delam we're at
var/obj/effect/countdown/supermatter/countdown
+ ///Used along with a global var to track if we can give out the sm sliver stealing objective
var/is_main_engine = FALSE
-
+ ///Our soundloop
var/datum/looping_sound/supermatter/soundloop
-
+ ///Can it be moved?
var/moveable = FALSE
+ ///cooldown tracker for accent sounds
+ var/last_accent_sound = 0
+
+ //For making hugbox supermatters
+ ///Disables all methods of taking damage
+ var/takes_damage = TRUE
+ ///Disables the production of gas, and pretty much any handling of it we do.
+ var/produces_gas = TRUE
+ ///Disables power changes
+ var/power_changes = TRUE
+ ///Disables the sm's proccessing totally.
+ var/processes = TRUE
+
/obj/machinery/power/supermatter_crystal/Initialize()
. = ..()
uid = gl_uid++
@@ -174,6 +295,9 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal)
if(is_main_engine)
GLOB.main_supermatter_engine = src
+ AddElement(/datum/element/bsa_blocker)
+ RegisterSignal(src, COMSIG_ATOM_BSA_BEAM, .proc/call_explode)
+
soundloop = new(list(src), TRUE)
/obj/machinery/power/supermatter_crystal/Destroy()
@@ -184,12 +308,11 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal)
QDEL_NULL(countdown)
if(is_main_engine && GLOB.main_supermatter_engine == src)
GLOB.main_supermatter_engine = null
- QDEL_NULL(soundloop)
return ..()
/obj/machinery/power/supermatter_crystal/examine(mob/user)
. = ..()
- if (iscarbon(user))
+ if (istype(user, /mob/living/carbon))
var/mob/living/carbon/C = user
if (!istype(C.glasses, /obj/item/clothing/glasses/meson) && (get_dist(user, src) < HALLUCINATION_RANGE(power)))
. += "You get headaches just from looking at it."
@@ -202,16 +325,17 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal)
if(!air)
return SUPERMATTER_ERROR
- if(get_integrity() < SUPERMATTER_DELAM_PERCENT)
+ var/integrity = get_integrity()
+ if(integrity < SUPERMATTER_DELAM_PERCENT)
return SUPERMATTER_DELAMINATING
- if(get_integrity() < SUPERMATTER_EMERGENCY_PERCENT)
+ if(integrity < SUPERMATTER_EMERGENCY_PERCENT)
return SUPERMATTER_EMERGENCY
- if(get_integrity() < SUPERMATTER_DANGER_PERCENT)
+ if(integrity < SUPERMATTER_DANGER_PERCENT)
return SUPERMATTER_DANGER
- if((get_integrity() < SUPERMATTER_WARNING_PERCENT) || (air.return_temperature() > CRITICAL_TEMPERATURE))
+ if((integrity < SUPERMATTER_WARNING_PERCENT) || (air.return_temperature() > CRITICAL_TEMPERATURE))
return SUPERMATTER_WARNING
if(air.return_temperature() > (CRITICAL_TEMPERATURE * 0.8))
@@ -238,23 +362,26 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal)
integrity = integrity < 0 ? 0 : integrity
return integrity
+/obj/machinery/power/supermatter_crystal/update_overlays()
+ . = ..()
+ if(final_countdown)
+ . += "casuality_field"
+
/obj/machinery/power/supermatter_crystal/proc/countdown()
set waitfor = FALSE
if(final_countdown) // We're already doing it go away
return
final_countdown = TRUE
-
- var/image/causality_field = image(icon, null, "causality_field")
- add_overlay(causality_field, TRUE)
+ update_icon()
var/speaking = "[emergency_alert] The supermatter has reached critical integrity failure. Emergency causality destabilization field has been activated."
radio.talk_into(src, speaking, common_channel, language = get_selected_language())
for(var/i in SUPERMATTER_COUNTDOWN_TIME to 0 step -10)
if(damage < explosion_point) // Cutting it a bit close there engineers
radio.talk_into(src, "[safe_alert] Failsafe has been disengaged.", common_channel)
- cut_overlay(causality_field, TRUE)
final_countdown = FALSE
+ update_icon()
return
else if((i % 50) != 0 && i > 50) // A message once every 5 seconds until the final 5 seconds which count down individualy
sleep(10)
@@ -287,48 +414,65 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal)
SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, "delam", /datum/mood_event/delam)
if(combined_gas > MOLE_PENALTY_THRESHOLD)
investigate_log("has collapsed into a singularity.", INVESTIGATE_SUPERMATTER)
- if(T)
+ if(T) //If something fucks up we blow anyhow. This fix is 4 years old and none ever said why it's here. help.
var/obj/singularity/S = new(T)
S.energy = 800
S.consume(src)
- else
- investigate_log("has exploded.", INVESTIGATE_SUPERMATTER)
- explosion(get_turf(T), explosion_power * max(gasmix_power_ratio, 0.205) * 0.5 , explosion_power * max(gasmix_power_ratio, 0.205) + 2, explosion_power * max(gasmix_power_ratio, 0.205) + 4 , explosion_power * max(gasmix_power_ratio, 0.205) + 6, 1, 1)
- if(power > POWER_PENALTY_THRESHOLD)
- investigate_log("has spawned additional energy balls.", INVESTIGATE_SUPERMATTER)
+ return //No boom for me sir
+ else if(power > POWER_PENALTY_THRESHOLD)
+ investigate_log("has spawned additional energy balls.", INVESTIGATE_SUPERMATTER)
+ if(T)
var/obj/singularity/energy_ball/E = new(T)
E.energy = power
- qdel(src)
+ investigate_log("has exploded.", INVESTIGATE_SUPERMATTER)
+ //Dear mappers, balance the sm max explosion radius to 17.5, 37, 39, 41
+ explosion(get_turf(T), explosion_power * max(gasmix_power_ratio, 0.205) * 0.5 , explosion_power * max(gasmix_power_ratio, 0.205) + 2, explosion_power * max(gasmix_power_ratio, 0.205) + 4 , explosion_power * max(gasmix_power_ratio, 0.205) + 6, 1, 1)
+ qdel(src)
-/obj/machinery/power/supermatter_crystal/proc/consume_turf(turf/T)
- var/oldtype = T.type
- var/turf/newT = T.ScrapeAway()
- if(newT.type == oldtype)
- return
- playsound(T, 'sound/effects/supermatter.ogg', 50, 1)
- T.visible_message("[T] smacks into [src] and rapidly flashes to ash.",\
- "You hear a loud crack as you are washed with a wave of heat.")
- CALCULATE_ADJACENT_TURFS(T)
+
+//this is here to eat arguments
+/obj/machinery/power/supermatter_crystal/proc/call_explode()
+ explode()
/obj/machinery/power/supermatter_crystal/process_atmos()
+ if(!processes) //Just fuck me up bro
+ return
var/turf/T = loc
- if(isnull(T)) // We have a null turf...something is wrong, stop processing this entity.
+ if(isnull(T))// We have a null turf...something is wrong, stop processing this entity.
return PROCESS_KILL
- if(!istype(T)) //We are in a crate or somewhere that isn't turf, if we return to turf resume processing but for now.
+ if(!istype(T))//We are in a crate or somewhere that isn't turf, if we return to turf resume processing but for now.
return //Yeah just stop.
- if(istype(T, /turf/closed))
- consume_turf(T)
+ if(isclosedturf(T))
+ var/turf/did_it_melt = T.Melt()
+ if(!isclosedturf(did_it_melt)) //In case some joker finds way to place these on indestructible walls
+ visible_message("[src] melts through [T]!")
+ return
+
+ //We vary volume by power, and handle OH FUCK FUSION IN COOLING LOOP noises.
if(power)
- soundloop.volume = min(40, (round(power/100)/50)+1) // 5 +1 volume per 20 power. 2500 power is max
+ soundloop.volume = clamp((50 + (power / 50)), 50, 100)
+ if(damage >= 300)
+ soundloop.mid_sounds = list('sound/machines/sm/loops/delamming.ogg' = 1)
+ else
+ soundloop.mid_sounds = list('sound/machines/sm/loops/calm.ogg' = 1)
+
+ //We play delam/neutral sounds at a rate determined by power and damage
+ if(last_accent_sound < world.time && prob(20))
+ var/aggression = min(((damage / 800) * (power / 2500)), 1.0) * 100
+ if(damage >= 300)
+ playsound(src, "smdelam", max(50, aggression), FALSE, 10)
+ else
+ playsound(src, "smcalm", max(50, aggression), FALSE, 10)
+ var/next_sound = round((100 - aggression) * 5)
+ last_accent_sound = world.time + max(SUPERMATTER_ACCENT_SOUND_MIN_COOLDOWN, next_sound)
//Ok, get the air from the turf
var/datum/gas_mixture/env = T.return_air()
var/datum/gas_mixture/removed
-
if(produces_gas)
//Remove gas from surrounding area
removed = env.remove(gasefficency * env.total_moles())
@@ -336,141 +480,240 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal)
// Pass all the gas related code an empty gas container
removed = new()
damage_archived = damage
+
+ /********
+ EXPERIMENTAL, HUGBOXY AS HELL CITADEL CHANGES: Even in a vaccum, update gas composition and modifiers.
+ This means that the SM will usually have a very small explosion if it ends up being breached to space,
+ and CO2 tesla delaminations basically require multiple grounding rods to stabilize it long enough to not have it vent.
+ *********/
+
if(!removed || !removed.total_moles() || isspaceturf(T)) //we're in space or there is no gas to process
if(takes_damage)
damage += max((power / 1000) * DAMAGE_INCREASE_MULTIPLIER, 0.1) // always does at least some damage
+ combined_gas = max(0, combined_gas - 0.5) // Slowly wear off.
+ for(var/gasID in gases_we_care_about)
+ gas_comp[gasID] = max(0, gas_comp[gasID] - 0.05) //slowly ramp down
else
if(takes_damage)
//causing damage
+ //Due to DAMAGE_INCREASE_MULTIPLIER, we only deal one 4th of the damage the statements otherwise would cause
+
+ //((((some value between 0.5 and 1 * temp - ((273.15 + 40) * some values between 1 and 10)) * some number between 0.25 and knock your socks off / 150) * 0.25
+ //Heat and mols account for each other, a lot of hot mols are more damaging then a few
+ //Mols start to have a positive effect on damage after 350
damage = max(damage + (max(clamp(removed.total_moles() / 200, 0.5, 1) * removed.return_temperature() - ((T0C + HEAT_PENALTY_THRESHOLD)*dynamic_heat_resistance), 0) * mole_heat_penalty / 150 ) * DAMAGE_INCREASE_MULTIPLIER, 0)
+ //Power only starts affecting damage when it is above 5000
damage = max(damage + (max(power - POWER_PENALTY_THRESHOLD, 0)/500) * DAMAGE_INCREASE_MULTIPLIER, 0)
+ //Molar count only starts affecting damage when it is above 1800
damage = max(damage + (max(combined_gas - MOLE_PENALTY_THRESHOLD, 0)/80) * DAMAGE_INCREASE_MULTIPLIER, 0)
+ //There might be a way to integrate healing and hurting via heat
//healing damage
if(combined_gas < MOLE_PENALTY_THRESHOLD)
- damage = max(damage + (min(removed.return_temperature() - (T0C + HEAT_PENALTY_THRESHOLD), 0) / 150 ), 0)
+ //Only has a net positive effect when the temp is below 313.15, heals up to 2 damage. Psycologists increase this temp min by up to 45
+ damage = max(damage + (min(removed.return_temperature() - (T0C + HEAT_PENALTY_THRESHOLD), 0) / 150), 0)
- //capping damage
- damage = min(damage_archived + (DAMAGE_HARDCAP * explosion_point),damage)
- if(damage > damage_archived && prob(10))
- playsound(get_turf(src), 'sound/effects/empulse.ogg', 50, 1)
+ //caps damage rate
- //calculating gas related values
- combined_gas = max(removed.total_moles(), 0)
+ //Takes the lower number between archived damage + (1.8) and damage
+ //This means we can only deal 1.8 damage per function call
+ damage = min(damage_archived + (DAMAGE_HARDCAP * explosion_point), damage)
- plasmacomp = max(removed.get_moles(/datum/gas/plasma)/combined_gas, 0)
- o2comp = max(removed.get_moles(/datum/gas/oxygen)/combined_gas, 0)
- co2comp = max(removed.get_moles(/datum/gas/carbon_dioxide)/combined_gas, 0)
- tritiumcomp = max(removed.get_moles(/datum/gas/tritium)/combined_gas, 0)
- bzcomp = max(removed.get_moles(/datum/gas/bz)/combined_gas, 0)
+ //calculating gas related values
+ //Wanna know a secret? See that max() to zero? it's used for error checking. If we get a mol count in the negative, we'll get a divide by zero error
+ combined_gas = max(removed.total_moles(), 0)
- pluoxiumcomp = max(removed.get_moles(/datum/gas/pluoxium)/combined_gas, 0)
- n2ocomp = max(removed.get_moles(/datum/gas/nitrous_oxide)/combined_gas, 0)
- n2comp = max(removed.get_moles(/datum/gas/nitrogen)/combined_gas, 0)
+ //This is more error prevention, according to all known laws of atmos, gas_mix.remove() should never make negative mol values.
+ //But this is tg
- if(pluoxiumcomp >= 0.15)
- pluoxiumbonus = 1 //makes pluoxium only work at 15%+
- else
- pluoxiumbonus = 0
+ //Lets get the proportions of the gasses in the mix and then slowly move our comp to that value
+ //Can cause an overestimation of mol count, should stabalize things though.
+ //Prevents huge bursts of gas/heat when a large amount of something is introduced
+ //They range between 0 and 1
+ for(var/gasID in gases_we_care_about)
+ gas_comp[gasID] += clamp(max(removed.get_moles(gasID)/combined_gas, 0) - gas_comp[gasID], -1, gas_change_rate)
- gasmix_power_ratio = min(max(plasmacomp + o2comp + co2comp + tritiumcomp + bzcomp - pluoxiumcomp - n2comp, 0), 1)
+ var/list/heat_mod = gases_we_care_about.Copy()
+ var/list/transit_mod = gases_we_care_about.Copy()
+ var/list/resistance_mod = gases_we_care_about.Copy()
- dynamic_heat_modifier = max((plasmacomp * PLASMA_HEAT_PENALTY) + (o2comp * OXYGEN_HEAT_PENALTY) + (co2comp * CO2_HEAT_PENALTY) + (tritiumcomp * TRITIUM_HEAT_PENALTY) + ((pluoxiumcomp * PLUOXIUM_HEAT_PENALTY) * pluoxiumbonus) + (n2comp * NITROGEN_HEAT_PENALTY) + (bzcomp * BZ_HEAT_PENALTY), 0.5)
- dynamic_heat_resistance = max((n2ocomp * N2O_HEAT_RESISTANCE) + ((pluoxiumcomp * PLUOXIUM_HEAT_RESISTANCE) * pluoxiumbonus), 1)
+ //We're concerned about pluoxium being too easy to abuse at low percents, so we make sure there's a substantial amount.
+ var/pluoxiumbonus = (gas_comp[/datum/gas/pluoxium] >= 0.15) //makes pluoxium only work at 15%+
+ var/h2obonus = 1 - (gas_comp[/datum/gas/water_vapor] * 0.25)//At max this value should be 0.75
+// var/freonbonus = (gas_comp[/datum/gas/freon] <= 0.03) //Let's just yeet power output if this shit is high
- power_transmission_bonus = max((plasmacomp * PLASMA_TRANSMIT_MODIFIER) + (o2comp * OXYGEN_TRANSMIT_MODIFIER) + (bzcomp * BZ_TRANSMIT_MODIFIER), 0)
+ heat_mod[/datum/gas/pluoxium] = pluoxiumbonus
+ transit_mod[/datum/gas/pluoxium] = pluoxiumbonus
+ resistance_mod[/datum/gas/pluoxium] = pluoxiumbonus
- //more moles of gases are harder to heat than fewer, so let's scale heat damage around them
- mole_heat_penalty = max(combined_gas / MOLE_HEAT_PENALTY, 0.25)
+ //No less then zero, and no greater then one, we use this to do explosions and heat to power transfer
+ //Be very careful with modifing this var by large amounts, and for the love of god do not push it past 1
+ gasmix_power_ratio = 0
+ for(var/gasID in gas_powermix)
+ gasmix_power_ratio += gas_comp[gasID] * gas_powermix[gasID]
+ gasmix_power_ratio = clamp(gasmix_power_ratio, 0, 1)
- if (combined_gas > POWERLOSS_INHIBITION_MOLE_THRESHOLD && co2comp > POWERLOSS_INHIBITION_GAS_THRESHOLD)
- powerloss_dynamic_scaling = clamp(powerloss_dynamic_scaling + clamp(co2comp - powerloss_dynamic_scaling, -0.02, 0.02), 0, 1)
- else
- powerloss_dynamic_scaling = clamp(powerloss_dynamic_scaling - 0.05,0, 1)
- powerloss_inhibitor = clamp(1-(powerloss_dynamic_scaling * clamp(combined_gas/POWERLOSS_INHIBITION_MOLE_BOOST_THRESHOLD,1 ,1.5)),0 ,1)
+ //Minimum value of -10, maximum value of 23. Effects plasma and o2 output and the output heat
+ dynamic_heat_modifier = 0
+ for(var/gasID in gas_heat)
+ dynamic_heat_modifier += gas_comp[gasID] * gas_heat[gasID] * (isnull(heat_mod[gasID]) ? 1 : heat_mod[gasID])
+ dynamic_heat_modifier *= h2obonus
+ dynamic_heat_modifier = max(dynamic_heat_modifier, 0.5)
- if(matter_power)
- var/removed_matter = max(matter_power/MATTER_POWER_CONVERSION, 40)
- power = max(power + removed_matter, 0)
- matter_power = max(matter_power - removed_matter, 0)
+ //Value between 1 and 10. Effects the damage heat does to the crystal
+ dynamic_heat_resistance = 0
+ for(var/gasID in gas_resist)
+ dynamic_heat_resistance += gas_comp[gasID] * gas_resist[gasID] * (isnull(resistance_mod[gasID]) ? 1 : resistance_mod[gasID])
+ dynamic_heat_resistance = max(dynamic_heat_resistance, 1)
- var/temp_factor = 50
+ //Value between -5 and 30, used to determine radiation output as it concerns things like collectors.
+ power_transmission_bonus = 0
+ for(var/gasID in gas_trans)
+ power_transmission_bonus += gas_comp[gasID] * gas_trans[gasID] * (isnull(transit_mod[gasID]) ? 1 : transit_mod[gasID])
+ power_transmission_bonus *= h2obonus
- if(gasmix_power_ratio > 0.8)
- // with a perfect gas mix, make the power less based on heat
- icon_state = "[base_icon_state]_glow"
- else
- // in normal mode, base the produced energy around the heat
- temp_factor = 30
- icon_state = base_icon_state
+ //more moles of gases are harder to heat than fewer, so let's scale heat damage around them
+ mole_heat_penalty = max(combined_gas / MOLE_HEAT_PENALTY, 0.25)
- power = max( (removed.return_temperature() * temp_factor / T0C) * gasmix_power_ratio + power, 0) //Total laser power plus an overload
+ //Ramps up or down in increments of 0.02 up to the proportion of co2
+ //Given infinite time, powerloss_dynamic_scaling = co2comp
+ //Some value between 0 and 1
+ if (combined_gas > POWERLOSS_INHIBITION_MOLE_THRESHOLD && gas_comp[/datum/gas/carbon_dioxide] > POWERLOSS_INHIBITION_GAS_THRESHOLD) //If there are more then 20 mols, and more then 20% co2
+ powerloss_dynamic_scaling = clamp(powerloss_dynamic_scaling + clamp(gas_comp[/datum/gas/carbon_dioxide] - powerloss_dynamic_scaling, -0.02, 0.02), 0, 1)
+ else
+ powerloss_dynamic_scaling = clamp(powerloss_dynamic_scaling - 0.05, 0, 1)
+ //Ranges from 0 to 1(1-(value between 0 and 1 * ranges from 1 to 1.5(mol / 500)))
+ //We take the mol count, and scale it to be our inhibitor
+ powerloss_inhibitor = clamp(1-(powerloss_dynamic_scaling * clamp(combined_gas/POWERLOSS_INHIBITION_MOLE_BOOST_THRESHOLD, 1, 1.5)), 0, 1)
- if(prob(50))
- radiation_pulse(src, power * (1 + (tritiumcomp * TRITIUM_RADIOACTIVITY_MODIFIER) + ((pluoxiumcomp * PLUOXIUM_RADIOACTIVITY_MODIFIER) * pluoxiumbonus) * (power_transmission_bonus/(10-(bzcomp * BZ_RADIOACTIVITY_MODIFIER))))) // Rad Modifiers BZ(500%), Tritium(300%), and Pluoxium(-200%)
- if(bzcomp >= 0.4 && prob(30 * bzcomp))
- fire_nuclear_particle() // Start to emit radballs at a maximum of 30% chance per tick
+ //Releases stored power into the general pool
+ //We get this by consuming shit or being scalpeled
+ if(matter_power && power_changes)
+ //We base our removed power off one 10th of the matter_power.
+ var/removed_matter = max(matter_power/MATTER_POWER_CONVERSION, 40)
+ //Adds at least 40 power
+ power = max(power + removed_matter, 0)
+ //Removes at least 40 matter power
+ matter_power = max(matter_power - removed_matter, 0)
- var/device_energy = power * REACTION_POWER_MODIFIER
+ var/temp_factor = 50
+ if(gasmix_power_ratio > 0.8)
+ //with a perfect gas mix, make the power more based on heat
+ icon_state = "[base_icon_state]_glow"
+ else
+ //in normal mode, power is less effected by heat
+ temp_factor = 30
+ icon_state = base_icon_state
- //To figure out how much temperature to add each tick, consider that at one atmosphere's worth
- //of pure oxygen, with all four lasers firing at standard energy and no N2 present, at room temperature
- //that the device energy is around 2140. At that stage, we don't want too much heat to be put out
- //Since the core is effectively "cold"
+ //if there is more pluox and n2 then anything else, we receive no power increase from heat
+ if(power_changes)
+ power = max((removed.return_temperature() * temp_factor / T0C) * gasmix_power_ratio + power, 0)
- //Also keep in mind we are only adding this temperature to (efficiency)% of the one tile the rock
- //is on. An increase of 4*C @ 25% efficiency here results in an increase of 1*C / (#tilesincore) overall.
- removed.set_temperature(removed.return_temperature() + ((device_energy * dynamic_heat_modifier) / THERMAL_RELEASE_MODIFIER))
+ if(prob(50))
+ //(1 + (tritRad + pluoxDampen * bzDampen * o2Rad * plasmaRad / (10 - bzrads))) * freonbonus
+ radiation_pulse(src, power * max(0, (1 + (power_transmission_bonus/(10-(gas_comp[/datum/gas/bz] * BZ_RADIOACTIVITY_MODIFIER)))) * 1))//freonbonus))// RadModBZ(500%)
+ if(gas_comp[/datum/gas/bz] >= 0.4 && prob(30 * gas_comp[/datum/gas/bz]))
+ src.fire_nuclear_particle() // Start to emit radballs at a maximum of 30% chance per tick
- removed.set_temperature(max(0, min(removed.return_temperature(), 2500 * dynamic_heat_modifier)))
+ //Power * 0.55 * a value between 1 and 0.8
+ var/device_energy = power * REACTION_POWER_MODIFIER
- //Calculate how much gas to release
- removed.adjust_moles(/datum/gas/plasma, max((device_energy * dynamic_heat_modifier) / PLASMA_RELEASE_MODIFIER, 0))
+ //To figure out how much temperature to add each tick, consider that at one atmosphere's worth
+ //of pure oxygen, with all four lasers firing at standard energy and no N2 present, at room temperature
+ //that the device energy is around 2140. At that stage, we don't want too much heat to be put out
+ //Since the core is effectively "cold"
- removed.adjust_moles(/datum/gas/oxygen, max(((device_energy + removed.return_temperature() * dynamic_heat_modifier) - T0C) / OXYGEN_RELEASE_MODIFIER, 0))
+ //Also keep in mind we are only adding this temperature to (efficiency)% of the one tile the rock
+ //is on. An increase of 4*C @ 25% efficiency here results in an increase of 1*C / (#tilesincore) overall.
+ //Power * 0.55 * (some value between 1.5 and 23) / 5
+ removed.set_temperature(removed.return_temperature() + ((device_energy * dynamic_heat_modifier) / THERMAL_RELEASE_MODIFIER))
+ //We can only emit so much heat, that being 57500
+ removed.set_temperature(max(0, min(removed.return_temperature(), 2500 * dynamic_heat_modifier)))
- if(produces_gas)
- env.merge(removed)
- air_update_turf()
+ //Calculate how much gas to release
+ //Varies based on power and gas content
+ removed.adjust_moles(/datum/gas/plasma, max((device_energy * dynamic_heat_modifier) / PLASMA_RELEASE_MODIFIER, 0))
+ //Varies based on power, gas content, and heat
+ removed.adjust_moles(/datum/gas/oxygen, max(((device_energy + removed.return_temperature() * dynamic_heat_modifier) - T0C) / OXYGEN_RELEASE_MODIFIER, 0))
- for(var/mob/living/carbon/human/l in fov_viewers(HALLUCINATION_RANGE(power), src)) // If they can see it without mesons on. Bad on them.
+ if(produces_gas)
+ env.merge(removed)
+ air_update_turf()
+
+ /*********
+ END CITADEL CHANGES
+ *********/
+
+ //Makes em go mad and accumulate rads.
+ for(var/mob/living/carbon/human/l in fov_viewers(src, HALLUCINATION_RANGE(power))) // If they can see it without mesons on. Bad on them.
if(!istype(l.glasses, /obj/item/clothing/glasses/meson))
var/D = sqrt(1 / max(1, get_dist(l, src)))
- l.hallucination += power * config_hallucination_power * D
- l.hallucination = clamp(0, 200, l.hallucination)
-
+ l.hallucination += power * hallucination_power * D
+ l.hallucination = clamp(l.hallucination, 0, 200)
for(var/mob/living/l in range(src, round((power / 100) ** 0.25)))
var/rads = (power / 10) * sqrt( 1 / max(get_dist(l, src),1) )
l.rad_act(rads)
- power -= ((power/500)**3) * powerloss_inhibitor
+ //Transitions between one function and another, one we use for the fast inital startup, the other is used to prevent errors with fusion temperatures.
+ //Use of the second function improves the power gain imparted by using co2
+ if(power_changes)
+ power = max(power - min(((power/500)**3) * powerloss_inhibitor, power * 0.83 * powerloss_inhibitor),0)
+ //After this point power is lowered
+ //This wraps around to the begining of the function
+ //Handle high power zaps/anomaly generation
+ if(power > POWER_PENALTY_THRESHOLD || damage > damage_penalty_point) //If the power is above 5000 or if the damage is above 550
+ var/range = 4
+ zap_cutoff = 1500
+ if(removed && removed.return_pressure() > 0 && removed.return_temperature() > 0)
+ //You may be able to freeze the zapstate of the engine with good planning, we'll see
+ zap_cutoff = clamp(3000 - (power * (removed.total_moles()) / 10) / removed.return_temperature(), 350, 3000)//If the core is cold, it's easier to jump, ditto if there are a lot of mols
+ //We should always be able to zap our way out of the default enclosure
+ //See supermatter_zap() for more details
+ range = clamp(power / removed.return_pressure() * 10, 2, 7)
+ var/flags = ZAP_SUPERMATTER_FLAGS
+ var/zap_count = 0
+ //Deal with power zaps
+ switch(power)
+ if(POWER_PENALTY_THRESHOLD to SEVERE_POWER_PENALTY_THRESHOLD)
+ zap_icon = DEFAULT_ZAP_ICON_STATE
+ zap_count = 2
+ if(SEVERE_POWER_PENALTY_THRESHOLD to CRITICAL_POWER_PENALTY_THRESHOLD)
+ zap_icon = SLIGHTLY_CHARGED_ZAP_ICON_STATE
+ //Uncaps the zap damage, it's maxed by the input power
+ //Objects take damage now
+ flags |= (ZAP_MOB_DAMAGE | ZAP_OBJ_DAMAGE)
+ zap_count = 3
+ if(CRITICAL_POWER_PENALTY_THRESHOLD to INFINITY)
+ zap_icon = OVER_9000_ZAP_ICON_STATE
+ //It'll stun more now, and damage will hit harder, gloves are no garentee.
+ //Machines go boom
+ flags |= (ZAP_MOB_STUN | ZAP_MACHINE_EXPLOSIVE | ZAP_MOB_DAMAGE | ZAP_OBJ_DAMAGE)
+ zap_count = 4
+ //Now we deal with damage shit
+ if (damage > damage_penalty_point && prob(20))
+ zap_count += 1
- if(power > POWER_PENALTY_THRESHOLD || damage > damage_penalty_point)
+ if(zap_count >= 1)
+ playsound(src.loc, 'sound/weapons/emitter2.ogg', 100, TRUE, extrarange = 10)
+ for(var/i in 1 to zap_count)
+ supermatter_zap(src, range, clamp(power*2, 4000, 20000), flags)
- if(power > POWER_PENALTY_THRESHOLD)
- playsound(src.loc, 'sound/weapons/emitter2.ogg', 100, 1, extrarange = 10)
- supermatter_zap(src, 5, min(power*2, 20000))
- supermatter_zap(src, 5, min(power*2, 20000))
- if(power > SEVERE_POWER_PENALTY_THRESHOLD)
- supermatter_zap(src, 5, min(power*2, 20000))
- if(power > CRITICAL_POWER_PENALTY_THRESHOLD)
- supermatter_zap(src, 5, min(power*2, 20000))
- else if (damage > damage_penalty_point && prob(20))
- playsound(src.loc, 'sound/weapons/emitter2.ogg', 100, 1, extrarange = 10)
- supermatter_zap(src, 5, clamp(power*2, 4000, 20000))
-
- if(prob(15) && power > POWER_PENALTY_THRESHOLD)
- supermatter_pull(src, power/750)
if(prob(5))
supermatter_anomaly_gen(src, FLUX_ANOMALY, rand(5, 10))
if(power > SEVERE_POWER_PENALTY_THRESHOLD && prob(5) || prob(1))
supermatter_anomaly_gen(src, GRAVITATIONAL_ANOMALY, rand(5, 10))
- if(power > SEVERE_POWER_PENALTY_THRESHOLD && prob(2) || prob(0.3) && power > POWER_PENALTY_THRESHOLD)
+ if((power > SEVERE_POWER_PENALTY_THRESHOLD && prob(2)) || (prob(0.3) && power > POWER_PENALTY_THRESHOLD))
supermatter_anomaly_gen(src, PYRO_ANOMALY, rand(5, 10))
+
+ if(prob(15))
+ supermatter_pull(loc, min(power/850, 3))//850, 1700, 2550
+
+ //Tells the engi team to get their butt in gear
if(damage > warning_point) // while the core is still damaged and it's still worth noting its status
if((REALTIMEOFDAY - lastwarning) / 10 >= WARNING_DELAY)
alarm()
+ //Oh shit it's bad, time to freak out
if(damage > emergency_point)
radio.talk_into(src, "[emergency_alert] Integrity: [get_integrity()]%", common_channel)
lastwarning = REALTIMEOFDAY
@@ -493,7 +736,7 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal)
if(combined_gas > MOLE_PENALTY_THRESHOLD)
radio.talk_into(src, "Warning: Critical coolant mass reached.", engineering_channel)
-
+ //Boom (Mind blown)
if(damage > explosion_point)
countdown()
@@ -503,16 +746,17 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal)
var/turf/L = loc
if(!istype(L))
return FALSE
- if(!istype(Proj.firer, /obj/machinery/power/emitter))
+ if(!istype(Proj.firer, /obj/machinery/power/emitter) && power_changes)
investigate_log("has been hit by [Proj] fired by [key_name(Proj.firer)]", INVESTIGATE_SUPERMATTER)
if(Proj.flag != "bullet")
- power += Proj.damage * config_bullet_energy
- if(!has_been_powered)
- investigate_log("has been powered for the first time.", INVESTIGATE_SUPERMATTER)
- message_admins("[src] has been powered for the first time [ADMIN_JMP(src)].")
- has_been_powered = TRUE
+ if(power_changes) //This needs to be here I swear
+ power += Proj.damage * bullet_energy
+ if(!has_been_powered)
+ investigate_log("has been powered for the first time.", INVESTIGATE_SUPERMATTER)
+ message_admins("[src] has been powered for the first time [ADMIN_JMP(src)].")
+ has_been_powered = TRUE
else if(takes_damage)
- matter_power += Proj.damage * config_bullet_energy
+ matter_power += Proj.damage * bullet_energy
return BULLET_ACT_HIT
/obj/machinery/power/supermatter_crystal/singularity_act()
@@ -529,15 +773,15 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal)
/obj/machinery/power/supermatter_crystal/blob_act(obj/structure/blob/B)
if(B && !isspaceturf(loc)) //does nothing in space
- playsound(get_turf(src), 'sound/effects/supermatter.ogg', 50, 1)
+ playsound(get_turf(src), 'sound/effects/supermatter.ogg', 50, TRUE)
damage += B.obj_integrity * 0.5 //take damage equal to 50% of remaining blob health before it tried to eat us
if(B.obj_integrity > 100)
B.visible_message("\The [B] strikes at \the [src] and flinches away!",\
- "You hear a loud crack as you are washed with a wave of heat.")
+ "You hear a loud crack as you are washed with a wave of heat.")
B.take_damage(100, BURN)
else
B.visible_message("\The [B] strikes at \the [src] and rapidly flashes to ash.",\
- "You hear a loud crack as you are washed with a wave of heat.")
+ "You hear a loud crack as you are washed with a wave of heat.")
Consume(B)
/obj/machinery/power/supermatter_crystal/attack_tk(mob/user)
@@ -546,11 +790,10 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal)
log_game("[key_name(C)] has been disintegrated by a telekenetic grab on a supermatter crystal.")
to_chat(C, "That was a really dense idea.")
C.visible_message("A bright flare of radiation is seen from [C]'s head, shortly before you hear a sickening sizzling!")
+ C.ghostize()
var/obj/item/organ/brain/rip_u = locate(/obj/item/organ/brain) in C.internal_organs
- rip_u.Remove()
+ rip_u.Remove(C)
qdel(rip_u)
- return
- return ..()
/obj/machinery/power/supermatter_crystal/attack_paw(mob/user)
dust_mob(user, cause = "monkey attack")
@@ -559,9 +802,14 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal)
dust_mob(user, cause = "alien attack")
/obj/machinery/power/supermatter_crystal/attack_animal(mob/living/simple_animal/S)
+ var/murder
+ if(!S.melee_damage_upper && !S.melee_damage_lower)
+ murder = S.friendly_verb_continuous
+ else
+ murder = S.attack_verb_continuous
dust_mob(S, \
- "[S] unwisely [S.attack_verb_continuous] [src], and [S.p_their()] body burns brilliantly before flashing into ash!", \
- "You unwisely [S.attack_verb_simple] [src], and your vision glows brightly as your body crumbles to dust. Oops.", \
+ "[S] unwisely [murder] [src], and [S.p_their()] body burns brilliantly before flashing into ash!", \
+ "You unwisely touch [src], and your vision glows brightly as your body crumbles to dust. Oops.", \
"simple animal attack")
/obj/machinery/power/supermatter_crystal/attack_robot(mob/user)
@@ -571,65 +819,103 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal)
/obj/machinery/power/supermatter_crystal/attack_ai(mob/user)
return
-/obj/machinery/power/supermatter_crystal/on_attack_hand(mob/living/user, act_intent = user.a_intent, unarmed_attack_flags)
+/obj/machinery/power/supermatter_crystal/on_attack_hand(mob/living/user)
. = ..()
- if(.)
- return
dust_mob(user, cause = "hand")
/obj/machinery/power/supermatter_crystal/proc/dust_mob(mob/living/nom, vis_msg, mob_msg, cause)
- if(nom.incorporeal_move || nom.status_flags & GODMODE)
+ if(nom.incorporeal_move || nom.status_flags & GODMODE) //try to keep supermatter sliver's + hemostat's dust conditions in sync with this too
return
if(!vis_msg)
- vis_msg = "[nom] reaches out and touches [src], inducing a resonance... [nom.p_their()] body starts to glow and bursts into flames before flashing into ash"
+ vis_msg = "[nom] reaches out and touches [src], inducing a resonance... [nom.p_their()] body starts to glow and burst into flames before flashing into dust!"
if(!mob_msg)
mob_msg = "You reach out and touch [src]. Everything starts burning and all you can hear is ringing. Your last thought is \"That was not a wise decision.\""
if(!cause)
cause = "contact"
- nom.visible_message(vis_msg, mob_msg, "You hear an unearthly noise as a wave of heat washes over you.")
+ nom.visible_message(vis_msg, mob_msg, "You hear an unearthly noise as a wave of heat washes over you.")
investigate_log("has been attacked ([cause]) by [key_name(nom)]", INVESTIGATE_SUPERMATTER)
- playsound(get_turf(src), 'sound/effects/supermatter.ogg', 50, 1)
+ playsound(get_turf(src), 'sound/effects/supermatter.ogg', 50, TRUE)
Consume(nom)
/obj/machinery/power/supermatter_crystal/attackby(obj/item/W, mob/living/user, params)
if(!istype(W) || (W.item_flags & ABSTRACT) || !istype(user))
return
- if (istype(W, /obj/item/melee/roastingstick))
+ if(istype(W, /obj/item/melee/roastingstick))
return ..()
+ if(istype(W, /obj/item/clothing/mask/cigarette))
+ var/obj/item/clothing/mask/cigarette/cig = W
+ var/clumsy = HAS_TRAIT(user, TRAIT_CLUMSY)
+ if(clumsy)
+ var/which_hand = BODY_ZONE_L_ARM
+ if(!(user.active_hand_index % 2))
+ which_hand = BODY_ZONE_R_ARM
+ var/obj/item/bodypart/dust_arm = user.get_bodypart(which_hand)
+ dust_arm.dismember()
+ user.visible_message("The [W] flashes out of existence on contact with \the [src], resonating with a horrible sound...",\
+ "Oops! The [W] flashes out of existence on contact with \the [src], taking your arm with it! That was clumsy of you!")
+ playsound(src, 'sound/effects/supermatter.ogg', 150, TRUE)
+ Consume(dust_arm)
+ qdel(W)
+ return
+ if(cig.lit || user.a_intent != INTENT_HELP)
+ user.visible_message("A hideous sound echoes as [W] is ashed out on contact with \the [src]. That didn't seem like a good idea...")
+ playsound(src, 'sound/effects/supermatter.ogg', 150, TRUE)
+ Consume(W)
+ radiation_pulse(src, 150, 4)
+ return ..()
+ else
+ cig.light()
+ user.visible_message("As [user] lights \their [W] on \the [src], silence fills the room...",\
+ "Time seems to slow to a crawl as you touch \the [src] with \the [W].\n\The [W] flashes alight with an eerie energy as you nonchalantly lift your hand away from \the [src]. Damn.")
+ playsound(src, 'sound/effects/supermatter.ogg', 50, TRUE)
+ radiation_pulse(src, 50, 3)
+ return
if(istype(W, /obj/item/scalpel/supermatter))
+ var/obj/item/scalpel/supermatter/scalpel = W
to_chat(user, "You carefully begin to scrape \the [src] with \the [W]...")
if(W.use_tool(src, user, 60, volume=100))
- to_chat(user, "You extract a sliver from \the [src]. \The [src] begins to react violently!")
- new /obj/item/nuke_core/supermatter_sliver(drop_location())
- matter_power += 200
+ if (scalpel.usesLeft)
+ to_chat(user, "You extract a sliver from \the [src]. \The [src] begins to react violently!")
+ new /obj/item/nuke_core/supermatter_sliver(drop_location())
+ matter_power += 800
+ scalpel.usesLeft--
+ if (!scalpel.usesLeft)
+ to_chat(user, "A tiny piece of \the [W] falls off, rendering it useless!")
+ else
+ to_chat(user, "You fail to extract a sliver from \The [src]! \the [W] isn't sharp enough anymore.")
else if(user.dropItemToGround(W))
user.visible_message("As [user] touches \the [src] with \a [W], silence fills the room...",\
"You touch \the [src] with \the [W], and everything suddenly goes silent.\n\The [W] flashes into dust as you flinch away from \the [src].",\
- "Everything suddenly goes silent.")
+ "Everything suddenly goes silent.")
investigate_log("has been attacked ([W]) by [key_name(user)]", INVESTIGATE_SUPERMATTER)
Consume(W)
- playsound(get_turf(src), 'sound/effects/supermatter.ogg', 50, 1)
+ playsound(get_turf(src), 'sound/effects/supermatter.ogg', 50, TRUE)
radiation_pulse(src, 150, 4)
+ else if(Adjacent(user)) //if the item is stuck to the person, kill the person too instead of eating just the item.
+ var/vis_msg = "[user] reaches out and touches [src] with [W], inducing a resonance... [W] starts to glow briefly before the light continues up to [user]'s body. [user.p_they(TRUE)] bursts into flames before flashing into dust!"
+ var/mob_msg = "You reach out and touch [src] with [W]. Everything starts burning and all you can hear is ringing. Your last thought is \"That was not a wise decision.\""
+ dust_mob(user, vis_msg, mob_msg)
+
/obj/machinery/power/supermatter_crystal/wrench_act(mob/user, obj/item/tool)
+ ..()
if (moveable)
default_unfasten_wrench(user, tool, time = 20)
return TRUE
/obj/machinery/power/supermatter_crystal/Bumped(atom/movable/AM)
if(isliving(AM))
- AM.visible_message("\The [AM] slams into \the [src] inducing a resonance... [AM.p_their()] body starts to glow and catch flame before flashing into ash.",\
+ AM.visible_message("\The [AM] slams into \the [src] inducing a resonance... [AM.p_their()] body starts to glow and burst into flames before flashing into dust!",\
"You slam into \the [src] as your ears are filled with unearthly ringing. Your last thought is \"Oh, fuck.\"",\
- "You hear an unearthly noise as a wave of heat washes over you.")
+ "You hear an unearthly noise as a wave of heat washes over you.")
else if(isobj(AM) && !iseffect(AM))
AM.visible_message("\The [AM] smacks into \the [src] and rapidly flashes to ash.", null,\
- "You hear a loud crack as you are washed with a wave of heat.")
+ "You hear a loud crack as you are washed with a wave of heat.")
else
return
- playsound(get_turf(src), 'sound/effects/supermatter.ogg', 50, 1)
-
+ playsound(get_turf(src), 'sound/effects/supermatter.ogg', 50, TRUE)
Consume(AM)
/obj/machinery/power/supermatter_crystal/intercept_zImpact(atom/movable/AM, levels)
@@ -645,7 +931,8 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal)
message_admins("[src] has consumed [key_name_admin(user)] [ADMIN_JMP(src)].")
investigate_log("has consumed [key_name(user)].", INVESTIGATE_SUPERMATTER)
user.dust(force = TRUE)
- matter_power += 200
+ if(power_changes)
+ matter_power += 200
else if(istype(AM, /obj/singularity))
return
else if(isobj(AM))
@@ -656,19 +943,29 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal)
message_admins("[src] has consumed [AM], [suspicion] [ADMIN_JMP(src)].")
investigate_log("has consumed [AM] - [suspicion].", INVESTIGATE_SUPERMATTER)
qdel(AM)
- if(!iseffect(AM))
+ if(!iseffect(AM) && power_changes)
matter_power += 200
//Some poor sod got eaten, go ahead and irradiate people nearby.
radiation_pulse(src, 3000, 2, TRUE)
- var/list/viewers = fov_viewers(world.view, src)
for(var/mob/living/L in range(10))
investigate_log("has irradiated [key_name(L)] after consuming [AM].", INVESTIGATE_SUPERMATTER)
+ var/list/viewers = fov_viewers(world.view, src)
if(L in viewers)
L.show_message("As \the [src] slowly stops resonating, you find your skin covered in new radiation burns.", MSG_VISUAL,\
"The unearthly ringing subsides and you notice you have new radiation burns.", MSG_AUDIBLE)
else
- L.show_message("You hear an unearthly ringing and notice your skin is covered in fresh radiation burns.", MSG_AUDIBLE)
+ L.show_message("You hear an unearthly ringing and notice your skin is covered in fresh radiation burns.", MSG_AUDIBLE)
+
+/obj/machinery/power/supermatter_crystal/proc/consume_turf(turf/T)
+ var/oldtype = T.type
+ var/turf/newT = T.ScrapeAway()
+ if(newT.type == oldtype)
+ return
+ playsound(T, 'sound/effects/supermatter.ogg', 50, 1)
+ T.visible_message("[T] smacks into [src] and rapidly flashes to ash.",\
+ "You hear a loud crack as you are washed with a wave of heat.")
+ CALCULATE_ADJACENT_TURFS(T)
//Do not blow up our internal radio
/obj/machinery/power/supermatter_crystal/contents_explosion(severity, target)
@@ -685,6 +982,7 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal)
anchored = FALSE
gasefficency = 0.125
explosion_power = 12
+ layer = ABOVE_MOB_LAYER
moveable = TRUE
/obj/machinery/power/supermatter_crystal/shard/engine
@@ -699,6 +997,8 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal)
name = "anchored supermatter shard"
takes_damage = FALSE
produces_gas = FALSE
+ power_changes = FALSE
+ processes = FALSE //SHUT IT DOWN
moveable = FALSE
anchored = TRUE
@@ -707,101 +1007,183 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal)
base_icon_state = "darkmatter"
icon_state = "darkmatter"
-/obj/machinery/power/supermatter_crystal/proc/supermatter_pull(turf/center, pull_range = 10)
- playsound(src.loc, 'sound/weapons/marauder.ogg', 100, 1, extrarange = 7)
- for(var/atom/P in orange(pull_range,center))
- if(ismovable(P))
- var/atom/movable/pulled_object = P
- if(ishuman(P))
- var/mob/living/carbon/human/H = P
- H.apply_effect(40, EFFECT_KNOCKDOWN, 0)
- if(pulled_object && !pulled_object.anchored && !ishuman(P))
- step_towards(pulled_object,center)
- step_towards(pulled_object,center)
- step_towards(pulled_object,center)
- step_towards(pulled_object,center)
+/obj/machinery/power/supermatter_crystal/proc/supermatter_pull(turf/center, pull_range = 3)
+ playsound(center, 'sound/weapons/marauder.ogg', 100, TRUE, extrarange = pull_range - world.view)
+ for(var/atom/movable/P in orange(pull_range,center))
+ if((P.anchored || P.move_resist >= MOVE_FORCE_EXTREMELY_STRONG)) //move resist memes.
+ if(istype(P, /obj/structure/closet))
+ var/obj/structure/closet/toggle = P
+ toggle.open()
+ continue
+ if(ismob(P))
+ var/mob/M = P
+ if(M.mob_negates_gravity())
+ continue //You can't pull someone nailed to the deck
+ step_towards(P,center)
/obj/machinery/power/supermatter_crystal/proc/supermatter_anomaly_gen(turf/anomalycenter, type = FLUX_ANOMALY, anomalyrange = 5)
var/turf/L = pick(orange(anomalyrange, anomalycenter))
if(L)
switch(type)
if(FLUX_ANOMALY)
- var/obj/effect/anomaly/flux/A = new(L, 300)
+ var/obj/effect/anomaly/flux/A = new(L, 300, FALSE)
A.explosive = FALSE
if(GRAVITATIONAL_ANOMALY)
- new /obj/effect/anomaly/grav(L, 250)
+ new /obj/effect/anomaly/grav(L, 250, FALSE)
if(PYRO_ANOMALY)
- new /obj/effect/anomaly/pyro(L, 200)
+ new /obj/effect/anomaly/pyro(L, 200, FALSE)
-/obj/machinery/power/supermatter_crystal/proc/supermatter_zap(atom/zapstart, range = 3, power)
- . = zapstart.dir
- if(power < 1000)
+/obj/machinery/power/supermatter_crystal/proc/supermatter_zap(atom/zapstart = src, range = 5, zap_str = 4000, zap_flags = ZAP_SUPERMATTER_FLAGS, list/targets_hit = list())
+ if(QDELETED(zapstart))
return
+ . = zapstart.dir
+ //If the strength of the zap decays past the cutoff, we stop
+ if(zap_str < zap_cutoff)
+ return
+ var/atom/target
+ var/target_type = LOWEST
+ var/list/arctargets = list()
+ //Making a new copy so additons further down the recursion do not mess with other arcs
+ //Lets put this ourself into the do not hit list, so we don't curve back to hit the same thing twice with one arc
+ for(var/test in oview(zapstart, range))
+ if(!(zap_flags & ZAP_ALLOW_DUPLICATES) && LAZYACCESS(targets_hit, test))
+ continue
- var/target_atom
- var/mob/living/target_mob
- var/obj/machinery/target_machine
- var/obj/structure/target_structure
- var/list/arctargetsmob = list()
- var/list/arctargetsmachine = list()
- var/list/arctargetsstructure = list()
+ if(istype(test, /obj/vehicle/ridden/bicycle/))
+ var/obj/vehicle/ridden/bicycle/bike = test
+ if(!(bike.obj_flags & BEING_SHOCKED) && bike.can_buckle)//God's not on our side cause he hates idiots.
+ if(target_type != BIKE)
+ arctargets = list()
+ arctargets += test
+ target_type = BIKE
- if(prob(20)) //let's not hit all the engineers with every beam and/or segment of the arc
- for(var/mob/living/Z in oview(zapstart, range+2))
- arctargetsmob += Z
- if(arctargetsmob.len)
- var/mob/living/H = pick(arctargetsmob)
- var/atom/A = H
- target_mob = H
- target_atom = A
+ if(target_type > COIL)
+ continue
- else
- for(var/obj/machinery/X in oview(zapstart, range+2))
- arctargetsmachine += X
- if(arctargetsmachine.len)
- var/obj/machinery/M = pick(arctargetsmachine)
- var/atom/A = M
- target_machine = M
- target_atom = A
+ if(istype(test, /obj/machinery/power/tesla_coil/))
+ var/obj/machinery/power/tesla_coil/coil = test
+ if(coil.anchored && !(coil.obj_flags & BEING_SHOCKED) && !coil.panel_open && prob(70))//Diversity of death
+ if(target_type != COIL)
+ arctargets = list()
+ arctargets += test
+ target_type = COIL
- else
- for(var/obj/structure/Y in oview(zapstart, range+2))
- arctargetsstructure += Y
- if(arctargetsstructure.len)
- var/obj/structure/O = pick(arctargetsstructure)
- var/atom/A = O
- target_structure = O
- target_atom = A
+ if(target_type > ROD)
+ continue
- if(target_atom)
- zapstart.Beam(target_atom, icon_state="nzcrentrs_power", time=5)
- var/zapdir = get_dir(zapstart, target_atom)
+ if(istype(test, /obj/machinery/power/grounding_rod/))
+ var/obj/machinery/power/grounding_rod/rod = test
+ //We're adding machine damaging effects, rods need to be surefire
+ if(rod.anchored && !rod.panel_open)
+ if(target_type != ROD)
+ arctargets = list()
+ arctargets += test
+ target_type = ROD
+
+ if(target_type > LIVING)
+ continue
+
+ if(istype(test, /mob/living/))
+ var/mob/living/alive = test
+ if(!(HAS_TRAIT(alive, TRAIT_TESLA_SHOCKIMMUNE)) && !(alive.flags_1 & SHOCKED_1) && alive.stat != DEAD && prob(20))//let's not hit all the engineers with every beam and/or segment of the arc
+ if(target_type != LIVING)
+ arctargets = list()
+ arctargets += test
+ target_type = LIVING
+
+ if(target_type > MACHINERY)
+ continue
+
+ if(istype(test, /obj/machinery/))
+ var/obj/machinery/machine = test
+ if(!(machine.obj_flags & BEING_SHOCKED) && prob(40))
+ if(target_type != MACHINERY)
+ arctargets = list()
+ arctargets += test
+ target_type = MACHINERY
+
+ if(target_type > OBJECT)
+ continue
+
+ if(istype(test, /obj/))
+ var/obj/object = test
+ if(!(object.obj_flags & BEING_SHOCKED))
+ if(target_type != OBJECT)
+ arctargets = list()
+ arctargets += test
+ target_type = OBJECT
+
+ if(arctargets.len)//Pick from our pool
+ target = pick(arctargets)
+
+ if(!QDELETED(target))//If we found something
+ //Do the animation to zap to it from here
+ if(!(zap_flags & ZAP_ALLOW_DUPLICATES))
+ LAZYSET(targets_hit, target, TRUE)
+ zapstart.Beam(target, icon_state=zap_icon, time=5)
+ var/zapdir = get_dir(zapstart, target)
if(zapdir)
. = zapdir
- if(target_mob)
- target_mob.electrocute_act(rand(5,10), "Supermatter Discharge Bolt", 1, SHOCK_NOSTUN)
- if(prob(15))
- supermatter_zap(target_mob, 5, power / 2)
- supermatter_zap(target_mob, 5, power / 2)
- else
- supermatter_zap(target_mob, 5, power / 1.5)
+ //Going boom should be rareish
+ if(prob(80))
+ zap_flags &= ~ZAP_MACHINE_EXPLOSIVE
+ if(target_type == COIL)
+ //In the best situation we can expect this to grow up to 2120kw before a delam/IT'S GONE TOO FAR FRED SHUT IT DOWN
+ //The formula for power gen is zap_str * zap_mod / 2 * capacitor rating, between 1 and 4
+ var/multi = 10
+ switch(power)//Between 7k and 9k it's 20, above that it's 40
+ if(SEVERE_POWER_PENALTY_THRESHOLD to CRITICAL_POWER_PENALTY_THRESHOLD)
+ multi = 20
+ if(CRITICAL_POWER_PENALTY_THRESHOLD to INFINITY)
+ multi = 40
+ target.zap_act(zap_str * multi, zap_flags, list())
+ zap_str /= 3 //Coils should take a lot out of the power of the zap
- else if(target_machine)
- if(prob(15))
- supermatter_zap(target_machine, 5, power / 2)
- supermatter_zap(target_machine, 5, power / 2)
- else
- supermatter_zap(target_machine, 5, power / 1.5)
+ else if(target_type == ROD)
+ //We can expect this to do very little, maybe shock the poor soul buckled to it, but that's all.
+ //This is one of our endpoints, if the bolt hits a grounding rod, it stops jumping
+ target.zap_act(zap_str, zap_flags, list())
+ return
+
+ else if(isliving(target))//If we got a fleshbag on our hands
+ var/mob/living/creature = target
+ creature.set_shocked()
+ addtimer(CALLBACK(creature, /mob/living/proc/reset_shocked), 10)
+ //3 shots a human with no resistance. 2 to crit, one to death. This is at at least 10000 power.
+ //There's no increase after that because the input power is effectivly capped at 10k
+ //Does 1.5 damage at the least
+ var/shock_damage = ((zap_flags & ZAP_MOB_DAMAGE) ? (power / 200) - 10 : rand(5,10))
+ creature.electrocute_act(shock_damage, "Supermatter Discharge Bolt", 1, ((zap_flags & ZAP_MOB_STUN) ? SHOCK_TESLA : SHOCK_NOSTUN))
+ zap_str /= 1.5 //Meatsacks are conductive, makes working in pairs more destructive
- else if(target_structure)
- if(prob(15))
- supermatter_zap(target_structure, 5, power / 2)
- supermatter_zap(target_structure, 5, power / 2)
else
- supermatter_zap(target_structure, 5, power / 1.5)
+ target.zap_act(zap_str, zap_flags, list())
+ zap_str /= 2 // worse then living things, better then coils
+ //This gotdamn variable is a boomer and keeps giving me problems
+ var/turf/T = get_turf(target)
+ var/pressure = 1
+ if(T && T.return_air())
+ pressure = max(1,T.return_air().return_pressure())
+ //We get our range with the strength of the zap and the pressure, the higher the former and the lower the latter the better
+ var/new_range = clamp(zap_str / pressure * 10, 2, 7)
+ var/zap_count = 1
+ if(prob(5))
+ zap_str -= (zap_str/10)
+ zap_count += 1
+ for(var/j in 1 to zap_count)
+ if(zap_count > 1)
+ targets_hit = targets_hit.Copy() //Pass by ref begone
+ supermatter_zap(target, new_range, zap_str, zap_flags, targets_hit)
#undef HALLUCINATION_RANGE
#undef GRAVITATIONAL_ANOMALY
#undef FLUX_ANOMALY
#undef PYRO_ANOMALY
+#undef BIKE
+#undef COIL
+#undef ROD
+#undef LIVING
+#undef MACHINERY
+#undef OBJECT
+#undef LOWEST
diff --git a/code/modules/power/turbine.dm b/code/modules/power/turbine.dm
index e7a97c554f..a9acea719c 100644
--- a/code/modules/power/turbine.dm
+++ b/code/modules/power/turbine.dm
@@ -56,8 +56,6 @@
resistance_flags = FIRE_PROOF
CanAtmosPass = ATMOS_PASS_DENSITY
circuit = /obj/item/circuitboard/machine/power_turbine
- ui_x = 310
- ui_y = 150
var/opened = 0
var/obj/machinery/power/compressor/compressor
var/turf/outturf
@@ -249,11 +247,10 @@
default_deconstruction_crowbar(I)
-/obj/machinery/power/turbine/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \
- datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state)
- ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+/obj/machinery/power/turbine/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
- ui = new(user, src, ui_key, "TurbineComputer", name, ui_x, ui_y, master_ui, state)
+ ui = new(user, src, "TurbineComputer", name)
ui.open()
/obj/machinery/power/turbine/ui_data(mob/user)
@@ -292,8 +289,6 @@
icon_screen = "turbinecomp"
icon_keyboard = "tech_key"
circuit = /obj/item/circuitboard/computer/turbine_computer
- ui_x = 310
- ui_y = 150
var/obj/machinery/power/compressor/compressor
var/id = 0
@@ -313,11 +308,10 @@
else
compressor = locate(/obj/machinery/power/compressor) in range(7, src)
-/obj/machinery/computer/turbine_computer/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \
- datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state)
- ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+/obj/machinery/computer/turbine_computer/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
- ui = new(user, src, ui_key, "TurbineComputer", name, ui_x, ui_y, master_ui, state)
+ ui = new(user, src, "TurbineComputer", name)
ui.open()
/obj/machinery/computer/turbine_computer/ui_data(mob/user)
diff --git a/code/modules/procedural_mapping/mapGenerator.dm b/code/modules/procedural_mapping/mapGenerator.dm
index f509c409ce..323f74d0ef 100644
--- a/code/modules/procedural_mapping/mapGenerator.dm
+++ b/code/modules/procedural_mapping/mapGenerator.dm
@@ -147,8 +147,16 @@
set category = "Debug"
var/datum/mapGenerator/nature/N = new()
- var/startInput = input(usr,"Start turf of Map, (X;Y;Z)", "Map Gen Settings", "1;1;1") as text
- var/endInput = input(usr,"End turf of Map (X;Y;Z)", "Map Gen Settings", "[world.maxx];[world.maxy];[mob ? mob.z : 1]") as text
+ var/startInput = input(usr,"Start turf of Map, (X;Y;Z)", "Map Gen Settings", "1;1;1") as text|null
+
+ if (isnull(startInput))
+ return
+
+ var/endInput = input(usr,"End turf of Map (X;Y;Z)", "Map Gen Settings", "[world.maxx];[world.maxy];[mob ? mob.z : 1]") as text|null
+
+ if (isnull(endInput))
+ return
+
//maxx maxy and current z so that if you fuck up, you only fuck up one entire z level instead of the entire universe
if(!startInput || !endInput)
to_chat(src, "Missing Input")
diff --git a/code/modules/projectiles/ammunition/_firing.dm b/code/modules/projectiles/ammunition/_firing.dm
index 0ef4c680aa..437b7dcc5a 100644
--- a/code/modules/projectiles/ammunition/_firing.dm
+++ b/code/modules/projectiles/ammunition/_firing.dm
@@ -36,6 +36,14 @@
if(isgun(fired_from))
var/obj/item/gun/G = fired_from
BB.damage *= G.projectile_damage_multiplier
+ if(HAS_TRAIT(user, TRAIT_INSANE_AIM))
+ BB.ricochets_max = max(BB.ricochets_max, 10) //bouncy!
+ BB.ricochet_chance = max(BB.ricochet_chance, 100) //it wont decay so we can leave it at 100 for always bouncing
+ BB.ricochet_auto_aim_range = max(BB.ricochet_auto_aim_range, 3)
+ BB.ricochet_auto_aim_angle = max(BB.ricochet_auto_aim_angle, 360) //it can turn full circle and shoot you in the face because our aim? is insane.
+ BB.ricochet_decay_chance = 0
+ BB.ricochet_decay_damage = max(BB.ricochet_decay_damage, 0.1)
+ BB.ricochet_incidence_leeway = 0
if(reagents && BB.reagents)
reagents.trans_to(BB, reagents.total_volume) //For chemical darts/bullets
diff --git a/code/modules/projectiles/ammunition/ballistic/revolver.dm b/code/modules/projectiles/ammunition/ballistic/revolver.dm
index 693b258e3d..c13a3c953d 100644
--- a/code/modules/projectiles/ammunition/ballistic/revolver.dm
+++ b/code/modules/projectiles/ammunition/ballistic/revolver.dm
@@ -14,9 +14,13 @@
/obj/item/ammo_casing/a357/match
name = ".357 match bullet casing"
desc = "A .357 bullet casing, manufactured to exceedingly high standards."
- caliber = "357"
projectile_type = /obj/item/projectile/bullet/a357/match
+/obj/item/ammo_casing/a357/dumdum
+ name = ".357 DumDum bullet casing"
+ desc = "A .357 bullet casing. Usage of this ammunition will constitute a war crime in your area."
+ projectile_type = /obj/item/projectile/bullet/a357/dumdum
+
// 7.62x38mmR (Nagant Revolver)
/obj/item/ammo_casing/n762
@@ -68,4 +72,4 @@
/obj/item/ammo_casing/c38/dumdum
name = ".38 DumDum bullet casing"
desc = "A .38 DumDum bullet casing."
- projectile_type = /obj/item/projectile/bullet/c38/dumdum
\ No newline at end of file
+ projectile_type = /obj/item/projectile/bullet/c38/dumdum
diff --git a/code/modules/projectiles/boxes_magazines/_box_magazine.dm b/code/modules/projectiles/boxes_magazines/_box_magazine.dm
index 9ea030da99..78ca6e9280 100644
--- a/code/modules/projectiles/boxes_magazines/_box_magazine.dm
+++ b/code/modules/projectiles/boxes_magazines/_box_magazine.dm
@@ -20,6 +20,7 @@
var/caliber
var/multiload = 1
var/start_empty = 0
+ var/load_delay = 0 //how long do we take to load (deciseconds)
var/list/bullet_cost
var/list/base_cost// override this one as well if you override bullet_cost
@@ -75,12 +76,19 @@
return 1
/obj/item/ammo_box/attackby(obj/item/A, mob/user, params, silent = FALSE, replace_spent = 0)
+ if(INTERACTING_WITH(user, src) || INTERACTING_WITH(user, A))
+ to_chat(user, "You're already doing that!")
+ return FALSE
var/num_loaded = 0
if(!can_load(user))
return
if(istype(A, /obj/item/ammo_box))
var/obj/item/ammo_box/AM = A
for(var/obj/item/ammo_casing/AC in AM.stored_ammo)
+ if(load_delay || AM.load_delay)
+ var/loadtime = max(AM.load_delay, load_delay)
+ if(!do_after(user, loadtime, target = src))
+ return FALSE
var/did_load = give_round(AC, replace_spent)
if(did_load)
AM.stored_ammo -= AC
diff --git a/code/modules/projectiles/boxes_magazines/ammo_boxes.dm b/code/modules/projectiles/boxes_magazines/ammo_boxes.dm
index 987efc0404..0b00c89c02 100644
--- a/code/modules/projectiles/boxes_magazines/ammo_boxes.dm
+++ b/code/modules/projectiles/boxes_magazines/ammo_boxes.dm
@@ -16,6 +16,11 @@
name = "speed loader (.357 AP)"
ammo_type = /obj/item/ammo_casing/a357/ap
+/obj/item/ammo_box/a357/dumdum
+ name = "speed loader (.357 DumDum)"
+ desc = "Designed to quickly reload revolvers. Usage of these rounds will constitute a war crime in your area."
+ ammo_type = /obj/item/ammo_casing/a357/dumdum
+
/obj/item/ammo_box/c38
name = "speed loader (.38 rubber)"
desc = "Designed to quickly reload revolvers."
@@ -148,6 +153,8 @@
max_ammo = 4
var/pixeloffsetx = 4
start_empty = TRUE
+ multiload = FALSE
+ load_delay = 6 //6ds
/obj/item/ammo_box/shotgun/update_overlays()
. = ..()
diff --git a/code/modules/projectiles/gun.dm b/code/modules/projectiles/gun.dm
index c3248bab2f..8cddd5d02f 100644
--- a/code/modules/projectiles/gun.dm
+++ b/code/modules/projectiles/gun.dm
@@ -29,7 +29,7 @@
trigger_guard = TRIGGER_GUARD_NORMAL //trigger guard on the weapon, hulks can't fire them with their big meaty fingers
var/sawn_desc = null //description change if weapon is sawn-off
var/sawn_off = FALSE
-
+
/// can we be put into a turret
var/can_turret = TRUE
/// can we be put in a circuit
@@ -62,7 +62,8 @@
var/no_pin_required = FALSE //whether the gun can be fired without a pin
var/obj/item/flashlight/gun_light
- var/can_flashlight = 0
+ var/can_flashlight = FALSE
+ var/gunlight_state = "flight"
var/obj/item/kitchen/knife/bayonet
var/mutable_appearance/knife_overlay
var/can_bayonet = FALSE
@@ -310,8 +311,6 @@
randomized_gun_spread = rand(0, spread)
else if(burst_size > 1 && burst_spread)
randomized_gun_spread = rand(0, burst_spread)
- if(HAS_TRAIT(user, TRAIT_POOR_AIM)) //nice shootin' tex
- bonus_spread += 25
var/randomized_bonus_spread = rand(0, bonus_spread)
if(burst_size > 1)
@@ -419,14 +418,7 @@
return
to_chat(user, "You attach \the [K] to the front of \the [src].")
bayonet = K
- var/state = "bayonet" //Generic state.
- if(bayonet.icon_state in icon_states('icons/obj/guns/bayonets.dmi')) //Snowflake state?
- state = bayonet.icon_state
- var/icon/bayonet_icons = 'icons/obj/guns/bayonets.dmi'
- knife_overlay = mutable_appearance(bayonet_icons, state)
- knife_overlay.pixel_x = knife_x_offset
- knife_overlay.pixel_y = knife_y_offset
- add_overlay(knife_overlay, TRUE)
+ update_icon()
else if(istype(I, /obj/item/screwdriver))
if(gun_light)
var/obj/item/flashlight/seclite/S = gun_light
@@ -441,8 +433,7 @@
var/obj/item/kitchen/knife/K = bayonet
K.forceMove(get_turf(user))
bayonet = null
- cut_overlay(knife_overlay, TRUE)
- knife_overlay = null
+ update_icon()
else
return ..()
@@ -470,22 +461,35 @@
set_light(gun_light.brightness_on, gun_light.flashlight_power, gun_light.light_color)
else
set_light(0)
- cut_overlays(flashlight_overlay, TRUE)
- var/state = "flight[gun_light.on? "_on":""]" //Generic state.
+ else
+ set_light(0)
+ update_icon()
+ for(var/X in actions)
+ var/datum/action/A = X
+ A.UpdateButtonIcon()
+
+/obj/item/gun/update_overlays()
+ . = ..()
+ if(gun_light)
+ var/mutable_appearance/flashlight_overlay
+ var/state = "[gunlight_state][gun_light.on? "_on":""]" //Generic state.
if(gun_light.icon_state in icon_states('icons/obj/guns/flashlights.dmi')) //Snowflake state?
state = gun_light.icon_state
flashlight_overlay = mutable_appearance('icons/obj/guns/flashlights.dmi', state)
flashlight_overlay.pixel_x = flight_x_offset
flashlight_overlay.pixel_y = flight_y_offset
- add_overlay(flashlight_overlay, TRUE)
- else
- set_light(0)
- cut_overlays(flashlight_overlay, TRUE)
- flashlight_overlay = null
- update_icon(TRUE)
- for(var/X in actions)
- var/datum/action/A = X
- A.UpdateButtonIcon()
+ . += flashlight_overlay
+
+ if(bayonet)
+ var/mutable_appearance/knife_overlay
+ var/state = "bayonet" //Generic state.
+ if(bayonet.icon_state in icon_states('icons/obj/guns/bayonets.dmi')) //Snowflake state?
+ state = bayonet.icon_state
+ var/icon/bayonet_icons = 'icons/obj/guns/bayonets.dmi'
+ knife_overlay = mutable_appearance(bayonet_icons, state)
+ knife_overlay.pixel_x = knife_x_offset
+ knife_overlay.pixel_y = knife_y_offset
+ . += knife_overlay
/obj/item/gun/item_action_slot_check(slot, mob/user, datum/action/A)
if(istype(A, /datum/action/item_action/toggle_scope_zoom) && slot != SLOT_HANDS)
@@ -603,10 +607,16 @@
var/penalty = (last_fire + GUN_AIMING_TIME + fire_delay) - world.time
if(penalty > 0) //Yet we only penalize users firing it multiple times in a haste. fire_delay isn't necessarily cumbersomeness.
aiming_delay = penalty
- if(SEND_SIGNAL(user, COMSIG_COMBAT_MODE_CHECK, COMBAT_MODE_ACTIVE)) //To be removed in favor of something less tactless later.
+ if(SEND_SIGNAL(user, COMSIG_COMBAT_MODE_CHECK, COMBAT_MODE_ACTIVE) || HAS_TRAIT(user, TRAIT_INSANE_AIM)) //To be removed in favor of something less tactless later.
base_inaccuracy /= 1.5
if(stamloss > STAMINA_NEAR_SOFTCRIT) //This can null out the above bonus.
base_inaccuracy *= 1 + (stamloss - STAMINA_NEAR_SOFTCRIT)/(STAMINA_NEAR_CRIT - STAMINA_NEAR_SOFTCRIT)*0.5
+ if(HAS_TRAIT(user, TRAIT_POOR_AIM)) //nice shootin' tex
+ if(!HAS_TRAIT(user, TRAIT_INSANE_AIM))
+ bonus_spread += 25
+ else
+ //you have both poor aim and insane aim, why?
+ bonus_spread += rand(0,50)
var/mult = max((GUN_AIMING_TIME + aiming_delay + user.last_click_move - world.time)/GUN_AIMING_TIME, -0.5) //Yes, there is a bonus for taking time aiming.
if(mult < 0) //accurate weapons should provide a proper bonus with negative inaccuracy. the opposite is true too.
mult *= 1/inaccuracy_modifier
diff --git a/code/modules/projectiles/guns/ballistic.dm b/code/modules/projectiles/guns/ballistic.dm
index fa8099a257..1aefa51a51 100644
--- a/code/modules/projectiles/guns/ballistic.dm
+++ b/code/modules/projectiles/guns/ballistic.dm
@@ -53,6 +53,8 @@
..()
if (istype(A, /obj/item/ammo_box/magazine))
var/obj/item/ammo_box/magazine/AM = A
+ if(AM.load_delay && !do_after(user, AM.load_delay, target = src))
+ return FALSE
if (!magazine && istype(AM, mag_type))
if(user.transferItemToLoc(AM, src))
magazine = AM
diff --git a/code/modules/projectiles/guns/ballistic/automatic.dm b/code/modules/projectiles/guns/ballistic/automatic.dm
index b31fd2d1e0..9210e66f22 100644
--- a/code/modules/projectiles/guns/ballistic/automatic.dm
+++ b/code/modules/projectiles/guns/ballistic/automatic.dm
@@ -18,13 +18,15 @@
/obj/item/gun/ballistic/automatic/proto/unrestricted
pin = /obj/item/firing_pin
-/obj/item/gun/ballistic/automatic/update_icon()
- ..()
+/obj/item/gun/ballistic/automatic/update_overlays()
+ . = ..()
if(automatic_burst_overlay)
if(!select)
- add_overlay("[initial(icon_state)]semi")
+ . += ("[initial(icon_state)]semi")
if(select == 1)
- add_overlay("[initial(icon_state)]burst")
+ . += "[initial(icon_state)]burst"
+
+/obj/item/gun/ballistic/automatic/update_icon_state()
icon_state = "[initial(icon_state)][magazine ? "-[magazine.max_ammo]" : ""][chambered ? "" : "-e"][suppressed ? "-suppressed" : ""]"
/obj/item/gun/ballistic/automatic/attackby(obj/item/A, mob/user, params)
@@ -115,8 +117,7 @@
. = ..()
empty_alarm()
-/obj/item/gun/ballistic/automatic/c20r/update_icon()
- ..()
+/obj/item/gun/ballistic/automatic/c20r/update_icon_state()
icon_state = "c20r[magazine ? "-[CEILING(get_ammo(0)/4, 1)*4]" : ""][chambered ? "" : "-e"][suppressed ? "-suppressed" : ""]"
/obj/item/gun/ballistic/automatic/wt550
@@ -141,9 +142,8 @@
. = ..()
spread = 0
-/obj/item/gun/ballistic/automatic/wt550/update_icon()
- ..()
- icon_state = "wt550[magazine ? "-[CEILING(( (get_ammo(FALSE) / magazine.max_ammo) * 20) /4, 1)*4]" : "-0"]" //Sprites only support up to 20.
+/obj/item/gun/ballistic/automatic/wt550/update_icon_state()
+ icon_state = "wt550[magazine ? "-[CEILING(((get_ammo(FALSE) / magazine.max_ammo) * 20) /4, 1)*4]" : "-0"]" //Sprites only support up to 20.
/obj/item/gun/ballistic/automatic/mini_uzi
name = "\improper Type U3 Uzi"
@@ -160,6 +160,7 @@
mag_type = /obj/item/ammo_box/magazine/m556
fire_sound = 'sound/weapons/gunshot_smg.ogg'
can_suppress = FALSE
+ automatic_burst_overlay = FALSE
var/obj/item/gun/ballistic/revolver/grenadelauncher/underbarrel
burst_size = 3
burst_shot_delay = 2
@@ -191,18 +192,19 @@
underbarrel.attackby(A, user, params)
else
..()
-/obj/item/gun/ballistic/automatic/m90/update_icon()
- ..()
- cut_overlays()
+/obj/item/gun/ballistic/automatic/m90/update_overlays()
+ . = ..()
switch(select)
if(0)
- add_overlay("[initial(icon_state)]semi")
+ . += "[initial(icon_state)]semi"
if(1)
- add_overlay("[initial(icon_state)]burst")
+ . += "[initial(icon_state)]burst"
if(2)
- add_overlay("[initial(icon_state)]gren")
+ . += "[initial(icon_state)]gren"
+
+/obj/item/gun/ballistic/automatic/m90/update_icon_state()
icon_state = "[initial(icon_state)][magazine ? "" : "-e"]"
- return
+
/obj/item/gun/ballistic/automatic/m90/burst_select()
var/mob/living/carbon/human/user = usr
switch(select)
@@ -257,6 +259,7 @@
weapon_weight = WEAPON_MEDIUM
mag_type = /obj/item/ammo_box/magazine/m12g
fire_sound = 'sound/weapons/gunshot.ogg'
+ automatic_burst_overlay = FALSE
can_suppress = FALSE
burst_size = 1
pin = /obj/item/firing_pin/implant/pindicate
@@ -269,10 +272,13 @@
. = ..()
update_icon()
-/obj/item/gun/ballistic/automatic/shotgun/bulldog/update_icon()
- cut_overlays()
+/obj/item/gun/ballistic/automatic/shotgun/bulldog/update_icon_state()
+ return
+
+/obj/item/gun/ballistic/automatic/shotgun/bulldog/update_overlays()
+ . = ..()
if(magazine)
- add_overlay("[magazine.icon_state]")
+ . += "[magazine.icon_state]"
icon_state = "bulldog[chambered ? "" : "-e"]"
/obj/item/gun/ballistic/automatic/shotgun/bulldog/afterattack()
@@ -298,6 +304,7 @@
burst_shot_delay = 1
spread = 7
pin = /obj/item/firing_pin/implant/pindicate
+ automatic_burst_overlay = FALSE
/obj/item/gun/ballistic/automatic/l6_saw/unrestricted
pin = /obj/item/firing_pin
@@ -316,7 +323,7 @@
playsound(user, 'sound/weapons/sawclose.ogg', 60, 1)
update_icon()
-/obj/item/gun/ballistic/automatic/l6_saw/update_icon()
+/obj/item/gun/ballistic/automatic/l6_saw/update_icon_state()
icon_state = "l6[cover_open ? "open" : "closed"][magazine ? CEILING(get_ammo(0)/12.5, 1)*25 : "-empty"][suppressed ? "-suppressed" : ""]"
item_state = "l6[cover_open ? "openmag" : "closedmag"]"
@@ -369,9 +376,10 @@
zoom_amt = 10 //Long range, enough to see in front of you, but no tiles behind you.
zoom_out_amt = 13
slot_flags = ITEM_SLOT_BACK
+ automatic_burst_overlay = FALSE
actions_types = list()
-/obj/item/gun/ballistic/automatic/sniper_rifle/update_icon()
+/obj/item/gun/ballistic/automatic/sniper_rifle/update_icon_state()
if(magazine)
icon_state = "sniper-mag"
else
@@ -397,9 +405,10 @@
can_suppress = TRUE
w_class = WEIGHT_CLASS_HUGE
slot_flags = ITEM_SLOT_BACK
+ automatic_burst_overlay = FALSE
actions_types = list()
-/obj/item/gun/ballistic/automatic/surplus/update_icon()
+/obj/item/gun/ballistic/automatic/surplus/update_icon_state()
if(magazine)
icon_state = "surplus"
else
@@ -413,6 +422,7 @@
icon_state = "oldrifle"
item_state = "arg"
mag_type = /obj/item/ammo_box/magazine/recharge
+ automatic_burst_overlay = FALSE
fire_delay = 2
can_suppress = FALSE
burst_size = 1
@@ -420,7 +430,5 @@
fire_sound = 'sound/weapons/laser.ogg'
casing_ejector = FALSE
-/obj/item/gun/ballistic/automatic/laser/update_icon()
- ..()
+/obj/item/gun/ballistic/automatic/laser/update_icon_state()
icon_state = "oldrifle[magazine ? "-[CEILING(get_ammo(0)/4, 1)*4]" : ""]"
- return
diff --git a/code/modules/projectiles/guns/ballistic/launchers.dm b/code/modules/projectiles/guns/ballistic/launchers.dm
index 004f78235a..9e03207888 100644
--- a/code/modules/projectiles/guns/ballistic/launchers.dm
+++ b/code/modules/projectiles/guns/ballistic/launchers.dm
@@ -42,8 +42,7 @@
actions_types = list()
casing_ejector = FALSE
-/obj/item/gun/ballistic/automatic/gyropistol/update_icon()
- ..()
+/obj/item/gun/ballistic/automatic/gyropistol/update_icon_state()
icon_state = "[initial(icon_state)][magazine ? "loaded" : ""]"
/obj/item/gun/ballistic/automatic/speargun
@@ -54,6 +53,7 @@
w_class = WEIGHT_CLASS_BULKY
force = 10
can_suppress = FALSE
+ automatic_burst_overlay = FALSE
mag_type = /obj/item/ammo_box/magazine/internal/speargun
fire_sound = 'sound/weapons/grenadelaunch.ogg'
burst_size = 1
@@ -62,8 +62,9 @@
actions_types = list()
casing_ejector = FALSE
-/obj/item/gun/ballistic/automatic/speargun/update_icon()
- return
+/obj/item/gun/ballistic/automatic/speargun/ComponentInitialize()
+ . = ..()
+ AddElement(/datum/element/update_icon_blocker)
/obj/item/gun/ballistic/automatic/speargun/attack_self()
return
@@ -137,7 +138,7 @@
chamber_round()
update_icon()
-/obj/item/gun/ballistic/rocketlauncher/update_icon()
+/obj/item/gun/ballistic/rocketlauncher/update_icon_state()
icon_state = "[initial(icon_state)]-[chambered ? "1" : "0"]"
/obj/item/gun/ballistic/rocketlauncher/suicide_act(mob/living/user)
diff --git a/code/modules/projectiles/guns/ballistic/magweapon.dm b/code/modules/projectiles/guns/ballistic/magweapon.dm
index 74b8b210a7..4e27a73300 100644
--- a/code/modules/projectiles/guns/ballistic/magweapon.dm
+++ b/code/modules/projectiles/guns/ballistic/magweapon.dm
@@ -75,8 +75,7 @@
recoil = 2
weapon_weight = WEAPON_HEAVY
-/obj/item/gun/ballistic/automatic/magrifle/hyperburst/update_icon()
- ..()
+/obj/item/gun/ballistic/automatic/magrifle/hyperburst/update_icon_state()
icon_state = "hyperburst[magazine ? "-[get_ammo()]" : ""][chambered ? "" : "-e"]"
///magpistol///
@@ -92,12 +91,14 @@
fire_delay = 2
inaccuracy_modifier = 0.25
cell_type = /obj/item/stock_parts/cell/magnetic/pistol
+ automatic_burst_overlay = FALSE
-/obj/item/gun/ballistic/automatic/magrifle/pistol/update_icon()
- ..()
- cut_overlays()
+/obj/item/gun/ballistic/automatic/magrifle/pistol/update_overlays()
+ . = ..()
if(magazine)
- add_overlay("magpistol-magazine")
+ . += "magpistol-magazine"
+
+/obj/item/gun/ballistic/automatic/magrifle/pistol/update_icon_state()
icon_state = "[initial(icon_state)][chambered ? "" : "-e"]"
/obj/item/gun/ballistic/automatic/magrifle/pistol/nopin
diff --git a/code/modules/projectiles/guns/ballistic/pistol.dm b/code/modules/projectiles/guns/ballistic/pistol.dm
index cdaadb5c3b..e775fdc05a 100644
--- a/code/modules/projectiles/guns/ballistic/pistol.dm
+++ b/code/modules/projectiles/guns/ballistic/pistol.dm
@@ -8,12 +8,12 @@
burst_size = 1
fire_delay = 0
actions_types = list()
+ automatic_burst_overlay = FALSE
/obj/item/gun/ballistic/automatic/pistol/no_mag
spawnwithmagazine = FALSE
-/obj/item/gun/ballistic/automatic/pistol/update_icon()
- ..()
+/obj/item/gun/ballistic/automatic/pistol/update_icon_state()
icon_state = "[initial(icon_state)][chambered ? "" : "-e"][suppressed ? "-suppressed" : ""]"
/obj/item/gun/ballistic/automatic/pistol/suppressed/Initialize(mapload)
@@ -28,6 +28,7 @@
icon = 'modular_citadel/icons/obj/guns/cit_guns.dmi'
icon_state = "cde"
can_unsuppress = TRUE
+ automatic_burst_overlay = FALSE
obj_flags = UNIQUE_RENAME
unique_reskin = list("Default" = "cde",
"N-99" = "n99",
@@ -38,20 +39,18 @@
"PX4 Storm" = "px4"
)
-/obj/item/gun/ballistic/automatic/pistol/modular/update_icon()
- ..()
+/obj/item/gun/ballistic/automatic/pistol/modular/update_icon_state()
if(current_skin)
icon_state = "[unique_reskin[current_skin]][chambered ? "" : "-e"][suppressed ? "-suppressed" : ""]"
else
icon_state = "[initial(icon_state)][chambered ? "" : "-e"][suppressed ? "-suppressed" : ""]"
+
+/obj/item/gun/ballistic/automatic/pistol/modular/update_overlays()
+ . = ..()
if(magazine && suppressed)
- cut_overlays()
- add_overlay("[unique_reskin[current_skin]]-magazine-sup") //Yes, this means the default iconstate can't have a magazine overlay
+ . += "[unique_reskin[current_skin]]-magazine-sup" //Yes, this means the default iconstate can't have a magazine overlay
else if (magazine)
- cut_overlays()
- add_overlay("[unique_reskin[current_skin]]-magazine")
- else
- cut_overlays()
+ . += "[unique_reskin[current_skin]]-magazine"
/obj/item/gun/ballistic/automatic/pistol/m1911
name = "\improper M1911"
@@ -77,14 +76,14 @@
force = 14
mag_type = /obj/item/ammo_box/magazine/m50
can_suppress = FALSE
+ automatic_burst_overlay = FALSE
-/obj/item/gun/ballistic/automatic/pistol/deagle/update_icon()
- ..()
+/obj/item/gun/ballistic/automatic/pistol/deagle/update_overlays()
+ . = ..()
if(magazine)
- cut_overlays()
- add_overlay("deagle_magazine")
- else
- cut_overlays()
+ . += "deagle_magazine"
+
+/obj/item/gun/ballistic/automatic/pistol/deagle/update_icon_state()
icon_state = "[initial(icon_state)][chambered ? "" : "-e"]"
/obj/item/gun/ballistic/automatic/pistol/deagle/gold
@@ -142,14 +141,14 @@
actions_types = list()
fire_sound = 'sound/weapons/blastcannon.ogg'
spread = 20 //damn thing has no rifling.
+ automatic_burst_overlay = FALSE
-/obj/item/gun/ballistic/automatic/pistol/antitank/update_icon()
- ..()
+/obj/item/gun/ballistic/automatic/pistol/antitank/update_overlays()
+ . = ..()
if(magazine)
- cut_overlays()
- add_overlay("atp-mag")
- else
- cut_overlays()
+ . += "atp-mag"
+
+/obj/item/gun/ballistic/automatic/pistol/antitank/update_icon_state()
icon_state = "[initial(icon_state)][chambered ? "" : "-e"]"
/obj/item/gun/ballistic/automatic/pistol/antitank/syndicate
diff --git a/code/modules/projectiles/guns/ballistic/revolver.dm b/code/modules/projectiles/guns/ballistic/revolver.dm
index a5ed45dd48..6f1fb96af5 100644
--- a/code/modules/projectiles/guns/ballistic/revolver.dm
+++ b/code/modules/projectiles/guns/ballistic/revolver.dm
@@ -348,10 +348,10 @@
else
to_chat(user, "You need at least ten lengths of cable if you want to make a sling!")
-/obj/item/gun/ballistic/revolver/doublebarrel/improvised/update_icon()
- ..()
+/obj/item/gun/ballistic/revolver/doublebarrel/improvised/update_overlays()
+ . = ..()
if(slung)
- icon_state += "sling"
+ . += "[icon_state]sling"
/obj/item/gun/ballistic/revolver/doublebarrel/improvised/sawoff(mob/user)
. = ..()
diff --git a/code/modules/projectiles/guns/ballistic/shotgun.dm b/code/modules/projectiles/guns/ballistic/shotgun.dm
index 873b129c8f..ecf6e538b8 100644
--- a/code/modules/projectiles/guns/ballistic/shotgun.dm
+++ b/code/modules/projectiles/guns/ballistic/shotgun.dm
@@ -164,10 +164,10 @@
else
to_chat(user, "You need at least ten lengths of cable if you want to make a sling!")
-/obj/item/gun/ballistic/shotgun/boltaction/improvised/update_icon()
- ..()
+/obj/item/gun/ballistic/shotgun/boltaction/improvised/update_overlays()
+ . = ..()
if(slung)
- icon_state += "sling"
+ . += "[icon_state]sling"
/obj/item/gun/ballistic/shotgun/boltaction/enchanted
name = "enchanted bolt action rifle"
@@ -272,7 +272,7 @@
spread = 2
update_icon()
-/obj/item/gun/ballistic/shotgun/automatic/combat/compact/update_icon()
+/obj/item/gun/ballistic/shotgun/automatic/combat/compact/update_icon_state()
icon_state = "[current_skin ? unique_reskin[current_skin] : "cshotgun"][stock ? "" : "c"]"
//Dual Feed Shotgun
diff --git a/code/modules/projectiles/guns/ballistic/toy.dm b/code/modules/projectiles/guns/ballistic/toy.dm
index 1f66cfdf8a..e7f26670d4 100644
--- a/code/modules/projectiles/guns/ballistic/toy.dm
+++ b/code/modules/projectiles/guns/ballistic/toy.dm
@@ -27,9 +27,9 @@
burst_size = 1
fire_delay = 0
actions_types = list()
+ automatic_burst_overlay = FALSE
-/obj/item/gun/ballistic/automatic/toy/pistol/update_icon()
- ..()
+/obj/item/gun/ballistic/automatic/toy/pistol/update_icon_state()
icon_state = "[initial(icon_state)][chambered ? "" : "-e"][suppressed ? "-suppressed" : ""]"
/obj/item/gun/ballistic/automatic/toy/pistol/riot
diff --git a/code/modules/projectiles/guns/energy.dm b/code/modules/projectiles/guns/energy.dm
index c2b821dfcf..17dcfa96e6 100644
--- a/code/modules/projectiles/guns/energy.dm
+++ b/code/modules/projectiles/guns/energy.dm
@@ -29,7 +29,6 @@
var/charge_sections = 4
ammo_x_offset = 2
var/shaded_charge = FALSE //if this gun uses a stateful charge bar for more detail
- var/old_ratio = 0 // stores the gun's previous ammo "ratio" to see if it needs an updated icon
var/selfcharge = EGUN_NO_SELFCHARGE // EGUN_SELFCHARGE if true, EGUN_SELFCHARGE_BORG drains the cyborg's cell to recharge its own
var/charge_tick = 0
var/charge_delay = 4
@@ -64,10 +63,20 @@
START_PROCESSING(SSobj, src)
update_icon()
+/obj/item/gun/energy/ComponentInitialize()
+ . = ..()
+ AddElement(/datum/element/update_icon_updates_onmob)
+
/obj/item/gun/energy/Destroy()
STOP_PROCESSING(SSobj, src)
return ..()
+/obj/item/gun/energy/handle_atom_del(atom/A)
+ if(A == cell)
+ cell = null
+ update_icon()
+ return ..()
+
/obj/item/gun/energy/examine(mob/user)
. = ..()
if(!right_click_overridden)
@@ -226,46 +235,47 @@
#undef DECREMENT_OR_WRAP
#undef IS_VALID_INDEX
-/obj/item/gun/energy/update_icon(force_update)
- if(QDELETED(src))
+/obj/item/gun/energy/update_icon_state()
+ if(initial(item_state))
return
..()
+ var/ratio = get_charge_ratio()
+ var/new_item_state = ""
+ new_item_state = initial(icon_state)
+ if(modifystate)
+ var/obj/item/ammo_casing/energy/shot = ammo_type[current_firemode_index]
+ new_item_state += "[shot.select_name]"
+ new_item_state += "[ratio]"
+ item_state = new_item_state
+
+/obj/item/gun/energy/update_overlays()
+ . = ..()
+ if(QDELETED(src))
+ return
if(!automatic_charge_overlays)
return
- var/ratio = can_shoot() ? CEILING(clamp(cell.charge / cell.maxcharge, 0, 1) * charge_sections, 1) : 0
- // Sets the ratio to 0 if the gun doesn't have enough charge to fire, or if it's power cell is removed.
- // TG issues #5361 & #47908
- if(ratio == old_ratio && !force_update)
- return
- old_ratio = ratio
- cut_overlays()
- var/iconState = "[icon_state]_charge"
- var/itemState = null
- if(!initial(item_state))
- itemState = icon_state
+ var/overlay_icon_state = "[icon_state]_charge"
+ var/ratio = get_charge_ratio()
if (modifystate)
var/obj/item/ammo_casing/energy/shot = ammo_type[current_firemode_index]
- add_overlay("[icon_state]_[shot.select_name]")
- iconState += "_[shot.select_name]"
- if(itemState)
- itemState += "[shot.select_name]"
+ . += "[icon_state]_[shot.select_name]"
+ overlay_icon_state += "_[shot.select_name]"
if(ratio == 0)
- add_overlay("[icon_state]_empty")
+ . += "[icon_state]_empty"
else
if(!shaded_charge)
- var/mutable_appearance/charge_overlay = mutable_appearance(icon, iconState)
+ var/mutable_appearance/charge_overlay = mutable_appearance(icon, overlay_icon_state)
for(var/i = ratio, i >= 1, i--)
charge_overlay.pixel_x = ammo_x_offset * (i - 1)
charge_overlay.pixel_y = ammo_y_offset * (i - 1)
- add_overlay(charge_overlay)
+ . += charge_overlay
else
- add_overlay("[icon_state]_charge[ratio]")
- if(itemState)
- itemState += "[ratio]"
- item_state = itemState
- if(ismob(loc)) //forces inhands to update
- var/mob/M = loc
- M.update_inv_hands()
+ . += "[icon_state]_charge[ratio]"
+
+///Used by update_icon_state() and update_overlays()
+/obj/item/gun/energy/proc/get_charge_ratio()
+ return can_shoot() ? CEILING(clamp(cell.charge / cell.maxcharge, 0, 1) * charge_sections, 1) : 0
+ // Sets the ratio to 0 if the gun doesn't have enough charge to fire, or if its power cell is removed.
/obj/item/gun/energy/suicide_act(mob/living/user)
if (istype(user) && can_shoot() && can_trigger_gun(user) && user.get_bodypart(BODY_ZONE_HEAD))
diff --git a/code/modules/projectiles/guns/energy/dueling.dm b/code/modules/projectiles/guns/energy/dueling.dm
index 80bb269b21..04eff5afa9 100644
--- a/code/modules/projectiles/guns/energy/dueling.dm
+++ b/code/modules/projectiles/guns/energy/dueling.dm
@@ -207,12 +207,11 @@
to_chat(user,"You switch [src] setting to [setting] mode.")
update_icon()
-/obj/item/gun/energy/dueling/update_icon(force_update)
+/obj/item/gun/energy/dueling/update_overlays(force_update)
. = ..()
if(setting_overlay)
- cut_overlay(setting_overlay)
setting_overlay.icon_state = setting_iconstate()
- add_overlay(setting_overlay)
+ . += setting_overlay
/obj/item/gun/energy/dueling/Destroy()
if(duel)
@@ -363,8 +362,7 @@
STR.max_items = 2
STR.can_hold = typecacheof(/obj/item/gun/energy/dueling)
-/obj/item/storage/lockbox/dueling/update_icon()
- cut_overlays()
+/obj/item/storage/lockbox/dueling/update_icon_state()
var/locked = SEND_SIGNAL(src, COMSIG_IS_STORAGE_LOCKED)
if(locked)
icon_state = "medalbox+l"
diff --git a/code/modules/projectiles/guns/energy/energy_gun.dm b/code/modules/projectiles/guns/energy/energy_gun.dm
index 1b835d35a4..2c9794f391 100644
--- a/code/modules/projectiles/guns/energy/energy_gun.dm
+++ b/code/modules/projectiles/guns/energy/energy_gun.dm
@@ -19,17 +19,13 @@
cell_type = /obj/item/stock_parts/cell{charge = 600; maxcharge = 600}
ammo_x_offset = 2
charge_sections = 3
+ gunlight_state = "mini-light"
can_flashlight = 0 // Can't attach or detach the flashlight, and override it's icon update
/obj/item/gun/energy/e_gun/mini/Initialize()
gun_light = new /obj/item/flashlight/seclite(src)
return ..()
-/obj/item/gun/energy/e_gun/mini/update_icon()
- ..()
- if(gun_light && gun_light.on)
- add_overlay("mini-light")
-
/obj/item/gun/energy/e_gun/stun
name = "tactical energy gun"
desc = "Military issue energy gun, is able to fire stun rounds."
@@ -138,15 +134,15 @@
return
fail_chance = min(fail_chance + round(15/severity), 100)
-/obj/item/gun/energy/e_gun/nuclear/update_icon()
- ..()
+/obj/item/gun/energy/e_gun/nuclear/update_overlays()
+ . = ..()
if(crit_fail)
- add_overlay("[icon_state]_fail_3")
+ . += "[icon_state]_fail_3"
else
switch(fail_tick)
if(0)
- add_overlay("[icon_state]_fail_0")
+ . += "[icon_state]_fail_0"
if(1 to 150)
- add_overlay("[icon_state]_fail_1")
+ . += "[icon_state]_fail_1"
if(151 to INFINITY)
- add_overlay("[icon_state]_fail_2")
+ . += "[icon_state]_fail_2"
diff --git a/code/modules/projectiles/guns/energy/kinetic_accelerator.dm b/code/modules/projectiles/guns/energy/kinetic_accelerator.dm
index 0c723199a1..c1f47ccd1a 100644
--- a/code/modules/projectiles/guns/energy/kinetic_accelerator.dm
+++ b/code/modules/projectiles/guns/energy/kinetic_accelerator.dm
@@ -46,13 +46,6 @@
range = 4
log_override = TRUE
-/obj/item/gun/energy/kinetic_accelerator/premiumka/update_icon()
- ..()
- if(!can_shoot())
- add_overlay("[icon_state]_empty")
- else
- cut_overlays()
-
/obj/item/gun/energy/kinetic_accelerator/getinaccuracy(mob/living/user, bonus_spread, stamloss)
var/old_fire_delay = fire_delay //It's pretty irrelevant tbh but whatever.
fire_delay = overheat_time
@@ -186,12 +179,10 @@
update_icon()
overheat = FALSE
-/obj/item/gun/energy/kinetic_accelerator/update_icon()
- ..()
+/obj/item/gun/energy/kinetic_accelerator/update_overlays()
+ . = ..()
if(!can_shoot())
- add_overlay("[icon_state]_empty")
- else
- cut_overlays()
+ . += "[icon_state]_empty"
//Casing
/obj/item/ammo_casing/energy/kinetic
diff --git a/code/modules/projectiles/guns/energy/special.dm b/code/modules/projectiles/guns/energy/special.dm
index 20e847326e..19ca42022d 100644
--- a/code/modules/projectiles/guns/energy/special.dm
+++ b/code/modules/projectiles/guns/energy/special.dm
@@ -34,11 +34,11 @@
pin = null
ammo_x_offset = 1
-/obj/item/gun/energy/decloner/update_icon()
+/obj/item/gun/energy/decloner/update_overlays()
..()
var/obj/item/ammo_casing/energy/shot = ammo_type[current_firemode_index]
if(!QDELETED(cell) && (cell.charge > shot.e_cost))
- add_overlay("decloner_spin")
+ . += "decloner_spin"
/obj/item/gun/energy/floragun
name = "floral somatoray"
@@ -134,9 +134,10 @@
tool_behaviour = TOOL_WELDER
toolspeed = 0.7 //plasmacutters can be used as welders, and are faster than standard welders
-/obj/item/gun/energy/plasmacutter/Initialize()
+/obj/item/gun/energy/plasmacutter/ComponentInitialize()
. = ..()
AddComponent(/datum/component/butchering, 25, 105, 0, 'sound/weapons/plasma_cutter.ogg')
+ AddElement(/datum/element/update_icon_blocker)
/obj/item/gun/energy/plasmacutter/examine(mob/user)
. = ..()
@@ -166,9 +167,6 @@
/obj/item/gun/energy/plasmacutter/use(amount)
return cell.use(amount * 100)
-/obj/item/gun/energy/plasmacutter/update_icon()
- return
-
/obj/item/gun/energy/plasmacutter/adv
name = "advanced plasma cutter"
icon_state = "adv_plasmacutter"
@@ -183,11 +181,12 @@
icon_state = "wormhole_projector"
pin = null
inaccuracy_modifier = 0.25
+ automatic_charge_overlays = FALSE
var/obj/effect/portal/p_blue
var/obj/effect/portal/p_orange
var/atmos_link = FALSE
-/obj/item/gun/energy/wormhole_projector/update_icon()
+/obj/item/gun/energy/wormhole_projector/update_icon_state()
icon_state = "[initial(icon_state)][current_firemode_index]"
item_state = icon_state
@@ -256,8 +255,9 @@
can_charge = 0
use_cyborg_cell = 1
-/obj/item/gun/energy/printer/update_icon()
- return
+/obj/item/gun/energy/printer/ComponentInitialize()
+ . = ..()
+ AddElement(/datum/element/update_icon_blocker)
/obj/item/gun/energy/printer/emp_act()
return
@@ -321,14 +321,14 @@
inaccuracy_modifier = 0.25
cell_type = /obj/item/stock_parts/cell/super
ammo_type = list(/obj/item/ammo_casing/energy/emitter)
+ automatic_charge_overlays = FALSE
-/obj/item/gun/energy/emitter/update_icon()
- ..()
+/obj/item/gun/energy/emitter/update_icon_state()
var/obj/item/ammo_casing/energy/shot = ammo_type[current_firemode_index]
if(!QDELETED(cell) && (cell.charge > shot.e_cost))
- add_overlay("emitter_carbine_empty")
+ icon_state = "emitter_carbine_empty"
else
- add_overlay("emitter_carbine")
+ icon_state = "emitter_carbine"
//the pickle ray
/obj/item/gun/energy/pickle_gun
diff --git a/code/modules/projectiles/guns/misc/beam_rifle.dm b/code/modules/projectiles/guns/misc/beam_rifle.dm
index fd09aa7f9d..5e250d44e2 100644
--- a/code/modules/projectiles/guns/misc/beam_rifle.dm
+++ b/code/modules/projectiles/guns/misc/beam_rifle.dm
@@ -35,6 +35,7 @@
slowdown = 1
item_flags = NO_MAT_REDEMPTION | SLOWS_WHILE_IN_HAND | NEEDS_PERMIT
pin = null
+ automatic_charge_overlays = FALSE
var/aiming = FALSE
var/aiming_time = 14
var/aiming_time_fire_threshold = 5
@@ -152,13 +153,13 @@
current_zoom_x = 0
current_zoom_y = 0
-/obj/item/gun/energy/beam_rifle/update_icon()
- cut_overlays()
+/obj/item/gun/energy/beam_rifle/update_overlays()
+ . = ..()
var/obj/item/ammo_casing/energy/primary_ammo = ammo_type[1]
if(!QDELETED(cell) && (cell.charge > primary_ammo.e_cost))
- add_overlay(charged_overlay)
+ . += charged_overlay
else
- add_overlay(drained_overlay)
+ . += drained_overlay
/obj/item/gun/energy/beam_rifle/attack_self(mob/user)
if(!structure_piercing)
diff --git a/code/modules/projectiles/guns/misc/blastcannon.dm b/code/modules/projectiles/guns/misc/blastcannon.dm
index 1c8d519ba8..60b7565333 100644
--- a/code/modules/projectiles/guns/misc/blastcannon.dm
+++ b/code/modules/projectiles/guns/misc/blastcannon.dm
@@ -41,18 +41,16 @@
user.put_in_hands(bomb)
user.visible_message("[user] detaches [bomb] from [src].")
bomb = null
+ name = initial(name)
+ desc = initial(desc)
update_icon()
return ..()
-/obj/item/gun/blastcannon/update_icon()
+/obj/item/gun/blastcannon/update_icon_state()
if(bomb)
icon_state = icon_state_loaded
- name = "blast cannon"
- desc = "A makeshift device used to concentrate a bomb's blast energy to a narrow wave."
else
icon_state = initial(icon_state)
- name = initial(name)
- desc = initial(desc)
/obj/item/gun/blastcannon/attackby(obj/O, mob/user)
if(istype(O, /obj/item/transfer_valve))
@@ -65,6 +63,8 @@
return FALSE
user.visible_message("[user] attaches [T] to [src]!")
bomb = T
+ name = "blast cannon"
+ desc = "A makeshift device used to concentrate a bomb's blast energy to a narrow wave."
update_icon()
return TRUE
return ..()
diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm
index 43384280d4..99a0bedc4d 100644
--- a/code/modules/projectiles/projectile.dm
+++ b/code/modules/projectiles/projectile.dm
@@ -327,16 +327,18 @@
if(!trajectory)
return
var/turf/T = get_turf(A)
- if(check_ricochet(A) && A.handle_ricochet(src)) //if you can ricochet, attempt to ricochet off the object
- on_ricochet(A) //if allowed, use autoaim to ricochet into someone, otherwise default to ricocheting off the object from above
- var/datum/point/pcache = trajectory.copy_to()
- if(hitscan)
- store_hitscan_collision(pcache)
- decayedRange = max(0, decayedRange - reflect_range_decrease)
- ricochet_chance *= ricochet_decay_chance
- damage *= ricochet_decay_damage
- range = decayedRange
- return TRUE
+ if(check_ricochet_flag(A) && check_ricochet(A)) //if you can ricochet, attempt to ricochet off the object
+ ricochets++
+ if(A.handle_ricochet(src))
+ on_ricochet(A) //if allowed, use autoaim to ricochet into someone, otherwise default to ricocheting off the object from above
+ var/datum/point/pcache = trajectory.copy_to()
+ if(hitscan)
+ store_hitscan_collision(pcache)
+ decayedRange = max(0, decayedRange - reflect_range_decrease)
+ ricochet_chance *= ricochet_decay_chance
+ damage *= ricochet_decay_damage
+ range = decayedRange
+ return TRUE
var/distance = get_dist(T, starting) // Get the distance between the turf shot from and the mob we hit and use that for the calculations.
if(def_zone && check_zone(def_zone) != BODY_ZONE_CHEST)
@@ -680,7 +682,8 @@
if(!ignore_source_check && firer)
var/mob/M = firer
if((target == firer) || ((target == firer.loc) && ismecha(firer.loc)) || (target in firer.buckled_mobs) || (istype(M) && (M.buckled == target)))
- return FALSE
+ if(!ricochets) //if it has ricocheted, it can hit the firer.
+ return FALSE
if(!ignore_loc && (loc != target.loc))
return FALSE
if(target in passthrough)
diff --git a/code/modules/projectiles/projectile/bullets/revolver.dm b/code/modules/projectiles/projectile/bullets/revolver.dm
index 95d43ba1ce..ec3cadc31a 100644
--- a/code/modules/projectiles/projectile/bullets/revolver.dm
+++ b/code/modules/projectiles/projectile/bullets/revolver.dm
@@ -126,4 +126,15 @@
ricochet_auto_aim_angle = 50
ricochet_auto_aim_range = 6
ricochet_incidence_leeway = 80
- ricochet_decay_chance = 1
\ No newline at end of file
+ ricochet_decay_chance = 1
+
+/obj/item/projectile/bullet/a357/dumdum
+ name = ".357 DumDum bullet" // the warcrime bullet
+ damage = 40
+ armour_penetration = -20
+ wound_bonus = 45
+ bare_wound_bonus = 45
+ sharpness = SHARP_EDGED
+ embedding = list(embed_chance=90, fall_chance=2, jostle_chance=5, ignore_throwspeed_threshold=TRUE, pain_stam_pct=0.4, pain_mult=5, jostle_pain_mult=6, rip_time=10)
+ wound_falloff_tile = -1
+ embed_falloff_tile = -5
diff --git a/code/modules/projectiles/projectile/special/plasma.dm b/code/modules/projectiles/projectile/special/plasma.dm
index 33559fa92c..77509cb574 100644
--- a/code/modules/projectiles/projectile/special/plasma.dm
+++ b/code/modules/projectiles/projectile/special/plasma.dm
@@ -2,7 +2,7 @@
name = "plasma blast"
icon_state = "plasmacutter"
damage_type = BRUTE
- damage = 20
+ damage = 10
range = 4
dismemberment = 20
impact_effect_type = /obj/effect/temp_visual/impact_effect/purple_laser
@@ -32,12 +32,12 @@
return BULLET_ACT_FORCE_PIERCE
/obj/item/projectile/plasma/adv
- damage = 28
+ damage = 14
range = 5
mine_range = 5
/obj/item/projectile/plasma/adv/mech
- damage = 40
+ damage = 20
range = 9
mine_range = 3
@@ -52,4 +52,4 @@
dismemberment = 0
damage = 10
range = 4
- mine_range = 0
\ No newline at end of file
+ mine_range = 0
diff --git a/code/modules/reagents/chemistry/holder.dm b/code/modules/reagents/chemistry/holder.dm
index c2662a8342..c489edf88e 100644
--- a/code/modules/reagents/chemistry/holder.dm
+++ b/code/modules/reagents/chemistry/holder.dm
@@ -1165,3 +1165,9 @@
random_reagents += R
var/picked_reagent = pick(random_reagents)
return picked_reagent
+
+/proc/get_chem_id(chem_name)
+ for(var/X in GLOB.chemical_reagents_list)
+ var/datum/reagent/R = GLOB.chemical_reagents_list[X]
+ if(ckey(chem_name) == ckey(lowertext(R.name)))
+ return X
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..32ac7cecba 100644
--- a/code/modules/reagents/chemistry/machinery/chem_master.dm
+++ b/code/modules/reagents/chemistry/machinery/chem_master.dm
@@ -1,5 +1,3 @@
-#define PILL_STYLE_COUNT 22 //Update this if you add more pill icons or you die
-#define RANDOM_PILL_STYLE 22 //Dont change this one though
/obj/machinery/chem_master
name = "ChemMaster 3000"
@@ -12,6 +10,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 +153,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 +178,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 +201,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..d22523c4b8 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,9 +32,18 @@
/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
+/obj/machinery/smoke_machine/ComponentInitialize()
+ . = ..()
+ AddComponent(/datum/component/simple_rotation, ROTATION_ALTCLICK | ROTATION_CLOCKWISE | ROTATION_COUNTERCLOCKWISE | ROTATION_VERBS, null, CALLBACK(src, .proc/can_be_rotated))
+ AddComponent(/datum/component/plumbing/simple_demand) //this SURELY CANT' LEAD TO BAD THINGS HAPPENING.
+
+/obj/machinery/smoke_machine/proc/can_be_rotated(mob/user, rotation_type)
+ return !anchored
+
/obj/machinery/smoke_machine/update_icon_state()
if((!is_operational()) || (!on) || (reagents.total_volume == 0))
if (panel_open)
@@ -81,10 +91,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 +109,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.dm b/code/modules/reagents/chemistry/reagents.dm
index a85ac8b085..672127cb11 100644
--- a/code/modules/reagents/chemistry/reagents.dm
+++ b/code/modules/reagents/chemistry/reagents.dm
@@ -8,6 +8,7 @@ GLOBAL_LIST_INIT(name2reagent, build_name2reagent())
if (length(initial(R.name)))
.[ckey(initial(R.name))] = t
+
//Various reagents
//Toxin & acid reagents
//Hydroponics stuff
@@ -52,6 +53,14 @@ GLOBAL_LIST_INIT(name2reagent, build_name2reagent())
var/metabolizing = FALSE
var/chemical_flags // See fermi/readme.dm REAGENT_DEAD_PROCESS, REAGENT_DONOTSPLIT, REAGENT_ONLYINVERSE, REAGENT_ONMOBMERGE, REAGENT_INVISIBLE, REAGENT_FORCEONNEW, REAGENT_SNEAKYNAME
var/value = REAGENT_VALUE_NONE //How much does it sell for in cargo?
+ var/datum/material/material //are we made of material?
+
+/datum/reagent/New()
+ . = ..()
+
+ if(material)
+ material = SSmaterials.GetMaterialRef(material)
+
/datum/reagent/Destroy() // This should only be called by the holder, so it's already handled clearing its references
. = ..()
@@ -220,4 +229,3 @@ GLOBAL_LIST_INIT(name2reagent, build_name2reagent())
bloodsuckerdatum.handle_eat_human_food(disgust, blood_puke, force)
if(blood_change)
bloodsuckerdatum.AddBloodVolume(blood_change)
-
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..18203f1a4c 100644
--- a/code/modules/reagents/chemistry/reagents/food_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/food_reagents.dm
@@ -132,8 +132,8 @@
"You're covered in boiling oil!")
M.emote("scream")
playsound(M, 'sound/machines/fryer/deep_fryer_emerge.ogg', 25, TRUE)
- var/oil_damage = max((holder.chem_temp / fry_temperature) * 0.33,1) //Damage taken per unit
- M.adjustFireLoss(oil_damage * max(reac_volume,20)) //Damage caps at 20
+ var/oil_damage = min((holder.chem_temp / fry_temperature) * 0.33,1) //Damage taken per unit
+ M.adjustFireLoss(oil_damage * min(reac_volume,20)) //Damage caps at 20
else
..()
return TRUE
@@ -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/medicine_reagents.dm b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm
index 8bb34c0a82..37010cbbb5 100644
--- a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm
@@ -515,12 +515,13 @@
overdose_threshold = 30
pH = 2
value = REAGENT_VALUE_UNCOMMON
+ var/healing = 0.5
/datum/reagent/medicine/omnizine/on_mob_life(mob/living/carbon/M)
- M.adjustToxLoss(-0.5*REM, 0)
- M.adjustOxyLoss(-0.5*REM, 0)
- M.adjustBruteLoss(-0.5*REM, 0)
- M.adjustFireLoss(-0.5*REM, 0)
+ M.adjustToxLoss(-healing*REM, 0)
+ M.adjustOxyLoss(-healing*REM, 0)
+ M.adjustBruteLoss(-healing*REM, 0)
+ M.adjustFireLoss(-healing*REM, 0)
..()
. = 1
@@ -532,6 +533,12 @@
..()
. = 1
+/datum/reagent/medicine/omnizine/protozine
+ name = "Protozine"
+ description = "A less environmentally friendly and somewhat weaker variant of omnizine."
+ color = "#d8c7b7"
+ healing = 0.2
+
/datum/reagent/medicine/calomel
name = "Calomel"
description = "Quickly purges the body of all chemicals. Toxin damage is dealt if the patient is in good condition."
diff --git a/code/modules/reagents/chemistry/reagents/other_reagents.dm b/code/modules/reagents/chemistry/reagents/other_reagents.dm
index 81ad676613..5c01fd8cf6 100644
--- a/code/modules/reagents/chemistry/reagents/other_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/other_reagents.dm
@@ -313,6 +313,13 @@
metabolization_rate = 45 * REAGENTS_METABOLISM
. = 1
+/datum/reagent/water/hollowwater
+ name = "Hollow Water"
+ description = "An ubiquitous chemical substance that is composed of hydrogen and oxygen, but it looks kinda hollow."
+ color = "#88878777"
+ taste_description = "emptyiness"
+
+
/datum/reagent/water/holywater
name = "Holy Water"
description = "Water blessed by some deity."
@@ -883,7 +890,7 @@
if(istype(O, /obj/item/stack/sheet/metal))
var/obj/item/stack/sheet/metal/M = O
reac_volume = min(reac_volume, M.amount)
- new/obj/item/stack/tile/bronze(get_turf(M), reac_volume)
+ new/obj/item/stack/sheet/bronze(get_turf(M), reac_volume)
M.use(reac_volume)
/datum/reagent/nitrogen
@@ -950,6 +957,7 @@
color = "#1C1300" // rgb: 30, 20, 0
taste_description = "sour chalk"
pH = 5
+ material = /datum/material/diamond
/datum/reagent/carbon/reaction_turf(turf/T, reac_volume)
if(!isspaceturf(T))
@@ -1072,6 +1080,7 @@
pH = 6
overdose_threshold = 30
color = "#c2391d"
+ material = /datum/material/iron
/datum/reagent/iron/on_mob_life(mob/living/carbon/C)
if((HAS_TRAIT(C, TRAIT_NOMARROW)))
@@ -1103,6 +1112,7 @@
reagent_state = SOLID
color = "#F7C430" // rgb: 247, 196, 48
taste_description = "expensive metal"
+ material = /datum/material/gold
/datum/reagent/silver
name = "Silver"
@@ -1110,6 +1120,7 @@
reagent_state = SOLID
color = "#D0D0D0" // rgb: 208, 208, 208
taste_description = "expensive yet reasonable metal"
+ material = /datum/material/silver
/datum/reagent/silver/reaction_mob(mob/living/M, method=TOUCH, reac_volume)
if(M.has_bane(BANE_SILVER))
@@ -1123,6 +1134,7 @@
color = "#B8B8C0" // rgb: 184, 184, 192
taste_description = "the inside of a reactor"
pH = 4
+ material = /datum/material/uranium
/datum/reagent/uranium/on_mob_life(mob/living/carbon/M)
M.apply_effect(1/M.metabolism_efficiency,EFFECT_IRRADIATE,0)
@@ -1144,6 +1156,7 @@
taste_description = "fizzling blue"
pH = 12
value = REAGENT_VALUE_RARE
+ material = /datum/material/bluespace
/datum/reagent/bluespace/reaction_mob(mob/living/M, method=TOUCH, reac_volume)
if(method == TOUCH || method == VAPOR)
@@ -1182,6 +1195,7 @@
color = "#A8A8A8" // rgb: 168, 168, 168
taste_mult = 0
pH = 10
+ material = /datum/material/glass
/datum/reagent/fuel
name = "Welding fuel"
@@ -2197,13 +2211,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
@@ -2213,6 +2220,66 @@
color = "#f7685e"
metabolization_rate = REAGENTS_METABOLISM * 0.25
+/datum/reagent/wittel
+ name = "Wittel"
+ description = "An extremely rare metallic-white substance only found on demon-class planets."
+ color = "#FFFFFF" // rgb: 255, 255, 255
+ taste_mult = 0 // oderless and tasteless
+
+/datum/reagent/metalgen
+ name = "Metalgen"
+ data = list("material"=null)
+ description = "A purple metal morphic liquid, said to impose it's metallic properties on whatever it touches."
+ color = "#b000aa"
+ taste_mult = 0 // oderless and tasteless
+ var/applied_material_flags = MATERIAL_ADD_PREFIX | MATERIAL_COLOR
+ var/minumum_material_amount = 100
+
+/datum/reagent/metalgen/reaction_obj(obj/O, volume)
+ metal_morph(O)
+ return
+
+/datum/reagent/metalgen/reaction_turf(turf/T, volume)
+ metal_morph(T)
+ return
+
+///turn an object into a special material
+/datum/reagent/metalgen/proc/metal_morph(atom/A)
+ var/metal_ref = data["material"]
+ if(!metal_ref)
+ return
+ var/metal_amount = 0
+
+ for(var/B in A.custom_materials) //list with what they're made of
+ metal_amount += A.custom_materials[B]
+
+ if(!metal_amount)
+ metal_amount = minumum_material_amount //some stuff doesn't have materials at all. To still give them properties, we give them a material. Basically doesnt exist
+
+ var/list/metal_dat = list()
+ metal_dat[metal_ref] = metal_amount //if we pass the list directly, byond turns metal_ref into "metal_ref" kjewrg8fwcyvf
+
+ A.material_flags = applied_material_flags
+ A.set_custom_materials(metal_dat)
+
+/datum/reagent/gravitum
+ name = "Gravitum"
+ description = "A rare kind of null fluid, capable of temporalily removing all weight of whatever it touches." //i dont even
+ color = "#050096" // rgb: 5, 0, 150
+ taste_mult = 0 // oderless and tasteless
+ metabolization_rate = 0.1 * REAGENTS_METABOLISM //20 times as long, so it's actually viable to use
+ var/time_multiplier = 1 MINUTES //1 minute per unit of gravitum on objects. Seems overpowered, but the whole thing is very niche
+
+/datum/reagent/gravitum/reaction_obj(obj/O, volume)
+ O.AddElement(/datum/element/forced_gravity, 0)
+
+ addtimer(CALLBACK(O, .proc/_RemoveElement, /datum/element/forced_gravity, 0), volume * time_multiplier)
+
+/datum/reagent/gravitum/on_mob_add(mob/living/L)
+ L.AddElement(/datum/element/forced_gravity, 0) //0 is the gravity, and in this case weightless
+
+/datum/reagent/gravitum/on_mob_end_metabolize(mob/living/L)
+ L.RemoveElement(/datum/element/forced_gravity, 0)
//body bluids
/datum/reagent/consumable/semen
@@ -2225,6 +2292,7 @@
color = "#FFFFFF" // rgb: 255, 255, 255
can_synth = FALSE
nutriment_factor = 0.5 * REAGENTS_METABOLISM
+ var/decal_path = /obj/effect/decal/cleanable/semen
/datum/reagent/consumable/semen/reaction_turf(turf/T, reac_volume)
if(!istype(T))
@@ -2234,7 +2302,7 @@
var/obj/effect/decal/cleanable/semen/S = locate() in T
if(!S)
- S = new(T)
+ S = new decal_path(T)
if(data["blood_DNA"])
S.add_blood_DNA(list(data["blood_DNA"] = data["blood_type"]))
@@ -2258,51 +2326,20 @@
blood_DNA |= S.blood_DNA
return ..()
-/datum/reagent/consumable/femcum
+/datum/reagent/consumable/semen/femcum
name = "Female Ejaculate"
description = "Vaginal lubricant found in most mammals and other animals of similar nature. Where you found this is your own business."
taste_description = "something with a tang" // wew coders who haven't eaten out a girl.
- taste_mult = 2
- data = list("donor"=null,"viruses"=null,"donor_DNA"=null,"blood_type"=null,"resistances"=null,"trace_chem"=null,"mind"=null,"ckey"=null,"gender"=null,"real_name"=null)
- reagent_state = LIQUID
color = "#AAAAAA77"
- can_synth = FALSE
- nutriment_factor = 0.5 * REAGENTS_METABOLISM
+ decal_path = /obj/effect/decal/cleanable/semen/femcum
-/obj/effect/decal/cleanable/femcum
+/obj/effect/decal/cleanable/semen/femcum
name = "female ejaculate"
- desc = null
- gender = PLURAL
- density = 0
- layer = ABOVE_NORMAL_TURF_LAYER
- icon = 'icons/obj/genitals/effects.dmi'
icon_state = "fem1"
random_icon_states = list("fem1", "fem2", "fem3", "fem4")
blood_state = null
bloodiness = null
-/obj/effect/decal/cleanable/femcum/Initialize(mapload)
- . = ..()
- dir = GLOB.cardinals
- add_blood_DNA(list("Non-human DNA" = "A+"))
-
-/obj/effect/decal/cleanable/femcum/replace_decal(obj/effect/decal/cleanable/femcum/F)
- if(F.blood_DNA)
- blood_DNA |= F.blood_DNA
- return ..()
-
-/datum/reagent/consumable/femcum/reaction_turf(turf/T, reac_volume)
- if(!istype(T))
- return
- if(reac_volume < 10)
- return
-
- var/obj/effect/decal/cleanable/femcum/S = locate() in T
- if(!S)
- S = new(T)
- if(data["blood_DNA"])
- S.add_blood_DNA(list(data["blood_DNA"] = data["blood_type"]))
-
/datum/reagent/determination
name = "Determination"
description = "For when you need to push on a little more. Do NOT allow near plants."
@@ -2338,6 +2375,32 @@
M.adjustStaminaLoss(-0.25*REM) // the more wounds, the more stamina regen
..()
+datum/reagent/eldritch
+ name = "Eldritch Essence"
+ description = "Strange liquid that defies the laws of physics"
+ taste_description = "Ag'hsj'saje'sh"
+ color = "#1f8016"
+
+/datum/reagent/eldritch/on_mob_life(mob/living/carbon/M)
+ if(IS_HERETIC(M))
+ M.drowsyness = max(M.drowsyness-5, 0)
+ M.AdjustAllImmobility(-40, FALSE)
+ M.adjustStaminaLoss(-15, FALSE)
+ M.adjustToxLoss(-3, FALSE)
+ M.adjustOxyLoss(-3, FALSE)
+ M.adjustBruteLoss(-3, FALSE)
+ M.adjustFireLoss(-3, FALSE)
+ if(ishuman(M) && M.blood_volume < BLOOD_VOLUME_NORMAL)
+ M.blood_volume += 3
+ else
+ M.adjustOrganLoss(ORGAN_SLOT_BRAIN, 3, 150)
+ M.adjustToxLoss(2, FALSE)
+ M.adjustFireLoss(2, FALSE)
+ M.adjustOxyLoss(2, FALSE)
+ M.adjustBruteLoss(2, FALSE)
+ holder.remove_reagent(type, 1)
+ return TRUE
+
/datum/reagent/cellulose
name = "Cellulose Fibers"
description = "A crystaline polydextrose polymer, plants swear by this stuff."
@@ -2345,6 +2408,7 @@
color = "#E6E6DA"
taste_mult = 0
+
/datum/reagent/hairball
name = "Hairball"
description = "A bundle of keratinous bits and fibers, not easily digestible."
diff --git a/code/modules/reagents/chemistry/reagents/toxin_reagents.dm b/code/modules/reagents/chemistry/reagents/toxin_reagents.dm
index 8dca028b4a..07934d9880 100644
--- a/code/modules/reagents/chemistry/reagents/toxin_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/toxin_reagents.dm
@@ -64,6 +64,7 @@
toxpwr = 3
pH = 4
value = REAGENT_VALUE_RARE //sheets are worth more
+ material = /datum/material/plasma
/datum/reagent/toxin/plasma/on_mob_life(mob/living/carbon/C)
if(holder.has_reagent(/datum/reagent/medicine/epinephrine))
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/medicine.dm b/code/modules/reagents/chemistry/recipes/medicine.dm
index bb9a951cac..9e0c78d2e6 100644
--- a/code/modules/reagents/chemistry/recipes/medicine.dm
+++ b/code/modules/reagents/chemistry/recipes/medicine.dm
@@ -219,6 +219,12 @@
results = list(/datum/reagent/medicine/strange_reagent = 3)
required_reagents = list(/datum/reagent/medicine/omnizine = 1, /datum/reagent/water/holywater = 1, /datum/reagent/toxin/mutagen = 1)
+/datum/chemical_reaction/strange_reagent/alt
+ name = "Strange Reagent"
+ id = /datum/reagent/medicine/strange_reagent
+ results = list(/datum/reagent/medicine/strange_reagent = 2)
+ required_reagents = list(/datum/reagent/medicine/omnizine/protozine = 1, /datum/reagent/water/holywater = 1, /datum/reagent/toxin/mutagen = 1)
+
/datum/chemical_reaction/mannitol
name = "Mannitol"
id = /datum/reagent/medicine/mannitol
@@ -345,4 +351,20 @@
/datum/chemical_reaction/medmesh/on_reaction(datum/reagents/holder, created_volume)
var/location = get_turf(holder.my_atom)
for(var/i = 1, i <= created_volume, i++)
- new /obj/item/stack/medical/mesh/advanced(location)
\ No newline at end of file
+ new /obj/item/stack/medical/mesh/advanced(location)
+
+/datum/chemical_reaction/suture
+ required_reagents = list(/datum/reagent/cellulose = 2, /datum/reagent/medicine/styptic_powder = 2)
+
+/datum/chemical_reaction/suture/on_reaction(datum/reagents/holder, created_volume)
+ var/location = get_turf(holder.my_atom)
+ for(var/i = 1, i <= created_volume, i++)
+ new /obj/item/stack/medical/suture/(location)
+
+/datum/chemical_reaction/mesh
+ required_reagents = list(/datum/reagent/cellulose = 2, /datum/reagent/medicine/silver_sulfadiazine = 2)
+
+/datum/chemical_reaction/mesh/on_reaction(datum/reagents/holder, created_volume)
+ var/location = get_turf(holder.my_atom)
+ for(var/i = 1, i <= created_volume, i++)
+ new /obj/item/stack/medical/mesh/(location)
diff --git a/code/modules/reagents/chemistry/recipes/others.dm b/code/modules/reagents/chemistry/recipes/others.dm
index f32bc708d0..92861a94ed 100644
--- a/code/modules/reagents/chemistry/recipes/others.dm
+++ b/code/modules/reagents/chemistry/recipes/others.dm
@@ -1,3 +1,34 @@
+/datum/chemical_reaction/metalgen
+ name = "metalgen"
+ id = /datum/reagent/metalgen
+ required_reagents = list(/datum/reagent/wittel = 1, /datum/reagent/bluespace = 1, /datum/reagent/toxin/mutagen = 1)
+ results = list(/datum/reagent/metalgen = 1)
+
+/datum/chemical_reaction/metalgen_imprint
+ name = "metalgen imprint"
+ id = /datum/reagent/metalgen
+ required_reagents = list(/datum/reagent/metalgen = 1, /datum/reagent/liquid_dark_matter = 1)
+ results = list(/datum/reagent/metalgen = 1)
+
+/datum/chemical_reaction/holywater
+ name = "Holy Water"
+ id = /datum/reagent/water/holywater
+ results = list(/datum/reagent/water/holywater = 1)
+ required_reagents = list(/datum/reagent/water/hollowwater = 1)
+ required_catalysts = list(/datum/reagent/water/holywater = 1)
+
+/datum/chemical_reaction/metalgen_imprint/on_reaction(datum/reagents/holder, created_volume)
+ var/datum/reagent/metalgen/MM = holder.get_reagent(/datum/reagent/metalgen)
+ for(var/datum/reagent/R in holder.reagent_list)
+ if(R.material && R.volume >= 40)
+ MM.data["material"] = R.material
+ holder.remove_reagent(R.type, 40)
+
+/datum/chemical_reaction/gravitum
+ name = "gravitum"
+ id = /datum/reagent/gravitum
+ required_reagents = list(/datum/reagent/wittel = 1, /datum/reagent/sorium = 10)
+ results = list(/datum/reagent/gravitum = 10)
/datum/chemical_reaction/sterilizine
name = "Sterilizine"
@@ -684,10 +715,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
@@ -711,7 +742,7 @@
/datum/chemical_reaction/slime_extractification/on_reaction(datum/reagents/holder, created_volume)
var/location = get_turf(holder.my_atom)
new /obj/item/slime_extract/grey(location)
-
+
// Liquid Carpets
/datum/chemical_reaction/carpet
@@ -837,4 +868,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..6541deda1c 100644
--- a/code/modules/reagents/reagent_dispenser.dm
+++ b/code/modules/reagents/reagent_dispenser.dm
@@ -24,7 +24,8 @@
/obj/structure/reagent_dispensers/Initialize()
create_reagents(tank_volume, DRAINABLE | AMOUNT_VISIBLE)
- reagents.add_reagent(reagent_id, tank_volume)
+ if(reagent_id)
+ reagents.add_reagent(reagent_id, tank_volume)
. = ..()
/obj/structure/reagent_dispensers/proc/boom()
@@ -91,6 +92,38 @@
user.put_in_hands(S)
paper_cups--
+/obj/structure/reagent_dispensers/plumbed
+ name = "stationairy water tank"
+ anchored = TRUE
+ icon_state = "water_stationairy"
+ desc = "A stationairy, plumbed, water tank."
+
+/obj/structure/reagent_dispensers/plumbed/wrench_act(mob/living/user, obj/item/I)
+ default_unfasten_wrench(user, I)
+ return TRUE
+
+/obj/structure/reagent_dispensers/plumbed/default_unfasten_wrench(mob/user, obj/item/I, time = 20)
+ . = ..()
+ if(. == SUCCESSFUL_UNFASTEN)
+ user.visible_message("[user.name] [anchored ? "fasten" : "unfasten"] [src]", \
+ "You [anchored ? "fasten" : "unfasten"] [src]")
+ var/datum/component/plumbing/CP = GetComponent(/datum/component/plumbing)
+ if(anchored)
+ CP.enable()
+ else
+ CP.disable()
+
+/obj/structure/reagent_dispensers/plumbed/ComponentInitialize()
+ AddComponent(/datum/component/plumbing/simple_supply)
+
+/obj/structure/reagent_dispensers/plumbed/storage
+ name = "stationairy storage tank"
+ icon_state = "tank_stationairy"
+ reagent_id = null //start empty
+
+/obj/structure/reagent_dispensers/plumbed/storage/ComponentInitialize()
+ AddComponent(/datum/component/plumbing/tank)
+
//////////////
//Fuel Tanks//
//////////////
@@ -222,19 +255,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."
@@ -284,5 +304,3 @@
icon_state = "bluekeg"
reagent_id = /datum/reagent/consumable/ethanol/neurotoxin
tank_volume = 100 //2.5x less than the other kegs because it's harder to get
-
-
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/designs/machine_desings/machine_designs_medical.dm b/code/modules/research/designs/machine_desings/machine_designs_medical.dm
index 329fb7bf6e..84a3ed10d5 100644
--- a/code/modules/research/designs/machine_desings/machine_designs_medical.dm
+++ b/code/modules/research/designs/machine_desings/machine_designs_medical.dm
@@ -105,3 +105,11 @@
build_path = /obj/item/circuitboard/machine/bloodbankgen
departmental_flags = DEPARTMENTAL_FLAG_MEDICAL
category = list ("Medical Machinery")
+
+/datum/design/board/medipen_refiller
+ name = "Machine Design (Medipen Refiller)"
+ desc = "The circuit board for a Medipen Refiller."
+ id = "medipen_refiller"
+ build_path = /obj/item/circuitboard/machine/medipen_refiller
+ category = list ("Medical Machinery")
+ departmental_flags = DEPARTMENTAL_FLAG_MEDICAL
diff --git a/code/modules/research/designs/machine_desings/machine_designs_service.dm b/code/modules/research/designs/machine_desings/machine_designs_service.dm
index 5cbff1c66a..af4f650793 100644
--- a/code/modules/research/designs/machine_desings/machine_designs_service.dm
+++ b/code/modules/research/designs/machine_desings/machine_designs_service.dm
@@ -81,6 +81,14 @@
category = list ("Hydroponics Machinery")
departmental_flags = DEPARTMENTAL_FLAG_SERVICE
+/datum/design/board/hydroponics/auto
+ name = "Machine Design (Automatic Hydroponics Tray Board)"
+ desc = "The circuit board for an automatic hydroponics tray. GIVE ME THE PLANT, CAPTAIN."
+ id = "autohydrotray"
+ build_path = /obj/machinery/hydroponics/constructable/automagic
+ category = list ("Hydroponics Machinery")
+ departmental_flags = DEPARTMENTAL_FLAG_SERVICE | DEPARTMENTAL_FLAG_MEDICAL
+
/datum/design/board/monkey_recycler
name = "Machine Design (Monkey Recycler Board)"
desc = "The circuit board for a monkey recycler."
diff --git a/code/modules/research/designs/medical_designs.dm b/code/modules/research/designs/medical_designs.dm
index e1e55f3476..0a9fce2e67 100644
--- a/code/modules/research/designs/medical_designs.dm
+++ b/code/modules/research/designs/medical_designs.dm
@@ -961,3 +961,158 @@
build_path = /obj/item/bodypart/r_arm/robot/surplus_upgraded
category = list("Medical Designs")
departmental_flags = DEPARTMENTAL_FLAG_MEDICAL
+
+
+/datum/design/acclimator
+ name = "Plumbing Acclimator"
+ desc = "A heating and cooling device for pipes!"
+ id = "acclimator"
+ build_type = PROTOLATHE
+ materials = list(/datum/material/iron = 1000, /datum/material/glass = 500)
+ construction_time = 15
+ build_path = /obj/machinery/plumbing/acclimator
+ category = list("Misc","Medical Designs")
+ departmental_flags = DEPARTMENTAL_FLAG_MEDICAL | DEPARTMENTAL_FLAG_SCIENCE
+
+/datum/design/disposer
+ name = "Plumbing Disposer"
+ desc = "Using the power of Science, dissolves reagents into nothing (almost)."
+ id = "disposer"
+ build_type = PROTOLATHE
+ materials = list(/datum/material/iron = 500, /datum/material/glass = 100)
+ construction_time = 15
+ build_path = /obj/machinery/plumbing/disposer
+ category = list("Misc","Medical Designs")
+ departmental_flags = DEPARTMENTAL_FLAG_MEDICAL | DEPARTMENTAL_FLAG_SCIENCE
+
+/datum/design/plumb_filter
+ name = "Plumbing Filter"
+ desc = "Filters out chemicals by their NTDB ID."
+ id = "plumb_filter"
+ build_type = PROTOLATHE
+ materials = list(/datum/material/iron = 1000, /datum/material/glass = 500)
+ construction_time = 15
+ build_path = /obj/machinery/plumbing/filter
+ category = list("Misc","Medical Designs")
+ departmental_flags = DEPARTMENTAL_FLAG_MEDICAL | DEPARTMENTAL_FLAG_SCIENCE
+
+/datum/design/plumb_synth
+ name = "Plumbing Synthesizer"
+ desc = "Using standard mass-energy dynamic autoconverters, generates reagents from power and puts them in a pipe."
+ id = "plumb_synth"
+ build_type = PROTOLATHE
+ materials = list(/datum/material/iron = 5000, /datum/material/glass = 1000, /datum/material/plastic = 1000)
+ construction_time = 15
+ build_path = /obj/machinery/plumbing/synthesizer
+ category = list("Misc","Medical Designs")
+ departmental_flags = DEPARTMENTAL_FLAG_MEDICAL | DEPARTMENTAL_FLAG_SCIENCE
+
+/datum/design/plumb_grinder
+ name = "Plumbing-Linked Autogrinder"
+ desc = "Automatically extracts reagents from an item by grinding it. Think of the possibilities! Note: does not grind people."
+ id = "plumb_grinder"
+ build_type = PROTOLATHE
+ materials = list(/datum/material/iron = 2000, /datum/material/glass = 1500)
+ construction_time = 15
+ build_path = /obj/machinery/plumbing/grinder_chemical
+ category = list("Misc","Medical Designs")
+ departmental_flags = DEPARTMENTAL_FLAG_MEDICAL | DEPARTMENTAL_FLAG_SCIENCE
+
+/datum/design/reaction_chamber
+ name = "Plumbing Reaction Chamber"
+ desc = "You can set a list of allowed reagents and amounts. Once the chamber has these reagents, will let the products through."
+ id = "reaction_chamber"
+ build_type = PROTOLATHE
+ materials = list(/datum/material/iron = 1000, /datum/material/glass = 500)
+ construction_time = 15
+ build_path = /obj/machinery/plumbing/reaction_chamber
+ category = list("Misc","Medical Designs")
+ departmental_flags = DEPARTMENTAL_FLAG_MEDICAL | DEPARTMENTAL_FLAG_SCIENCE
+
+/datum/design/duct_print
+ name = "Plumbing Ducts"
+ desc = "Ducts for plumbing! Now lathed for efficiency."
+ id = "duct_print"
+ build_type = PROTOLATHE
+ materials = list(/datum/material/plastic = 400)
+ construction_time = 1
+ build_path = /obj/item/stack/ducts
+ category = list("Misc","Medical Designs")
+ departmental_flags = DEPARTMENTAL_FLAG_MEDICAL | DEPARTMENTAL_FLAG_SCIENCE
+
+/datum/design/plumb_splitter
+ name = "Plumbing Chemical Splitter"
+ desc = "A splitter. Has 2 outputs. Can be configured to allow a certain amount through each side."
+ id = "plumb_splitter"
+ build_type = PROTOLATHE
+ materials = list(/datum/material/iron = 750, /datum/material/glass = 250)
+ construction_time = 15
+ build_path = /obj/machinery/plumbing/splitter
+ category = list("Misc","Medical Designs")
+ departmental_flags = DEPARTMENTAL_FLAG_MEDICAL | DEPARTMENTAL_FLAG_SCIENCE
+
+/datum/design/pill_press
+ name = "Plumbing Automatic Pill Former"
+ desc = "Automatically forms pills to the required parameters with piped reagents! A good replacement for those lazy, useless chemists."
+ id = "pill_press"
+ build_type = PROTOLATHE
+ materials = list(/datum/material/iron = 1000, /datum/material/glass = 500)
+ construction_time = 15
+ build_path = /obj/machinery/plumbing/pill_press
+ category = list("Misc","Medical Designs")
+ departmental_flags = DEPARTMENTAL_FLAG_MEDICAL | DEPARTMENTAL_FLAG_SCIENCE
+
+/datum/design/plumb_pump
+ name = "Liquid Extraction Pump"
+ desc = "Use it for extracting liquids from lavaland's geysers!"
+ id = "plumb_pump"
+ build_type = PROTOLATHE
+ materials = list(/datum/material/iron = 1000, /datum/material/glass = 500)
+ construction_time = 15
+ build_path = /obj/machinery/plumbing/liquid_pump
+ category = list("Misc","Medical Designs")
+ departmental_flags = DEPARTMENTAL_FLAG_MEDICAL | DEPARTMENTAL_FLAG_SCIENCE
+
+/datum/design/plumb_in
+ name = "Plumbing Input Device"
+ desc = "A big piped funnel for putting stuff in the pipe network."
+ id = "plumb_in"
+ build_type = PROTOLATHE
+ materials = list(/datum/material/iron = 400, /datum/material/glass = 400)
+ construction_time = 15
+ build_path = /obj/machinery/plumbing/input
+ category = list("Misc","Medical Designs")
+ departmental_flags = DEPARTMENTAL_FLAG_MEDICAL | DEPARTMENTAL_FLAG_SCIENCE
+
+/datum/design/plumb_out
+ name = "Plumbing Output Device"
+ desc = "A big piped funnel for taking stuff out of the pipe network."
+ id = "plumb_out"
+ build_type = PROTOLATHE
+ materials = list(/datum/material/iron = 400, /datum/material/glass = 400)
+ construction_time = 15
+ build_path = /obj/machinery/plumbing/output
+ category = list("Misc","Medical Designs")
+ departmental_flags = DEPARTMENTAL_FLAG_MEDICAL | DEPARTMENTAL_FLAG_SCIENCE
+
+/datum/design/plumb_tank
+ name = "Plumbed Storage Tank"
+ desc = "A tank for storing plumbed reagents."
+ id = "plumb_tank"
+ build_type = PROTOLATHE
+ materials = list(/datum/material/iron = 10000, /datum/material/glass = 10000, /datum/material/plastic = 4000)
+ construction_time = 15
+ build_path = /obj/machinery/plumbing/tank
+ category = list("Misc","Medical Designs")
+ departmental_flags = DEPARTMENTAL_FLAG_MEDICAL | DEPARTMENTAL_FLAG_SCIENCE
+
+/datum/design/plumb_rcd
+ name = "Plumbed Autoconstruction Device"
+ desc = "A RCD for plumbing machines! Cannot make ducts."
+ id = "plumb_rcd"
+ build_type = PROTOLATHE
+ materials = list(/datum/material/iron = 20000, /datum/material/glass = 10000, /datum/material/plastic = 20000, /datum/material/titanium = 2000, /datum/material/diamond = 800, /datum/material/gold = 2000, /datum/material/silver = 2000)
+ construction_time = 150
+ build_path = /obj/item/construction/plumbing
+ category = list("Misc","Medical Designs")
+ departmental_flags = DEPARTMENTAL_FLAG_MEDICAL | DEPARTMENTAL_FLAG_SCIENCE
diff --git a/code/modules/research/nanites/extra_settings/text.dm b/code/modules/research/nanites/extra_settings/text.dm
index 56aa3dd07f..d3cad27bcf 100644
--- a/code/modules/research/nanites/extra_settings/text.dm
+++ b/code/modules/research/nanites/extra_settings/text.dm
@@ -10,6 +10,9 @@
/datum/nanite_extra_setting/text/get_copy()
return new /datum/nanite_extra_setting/text(value)
+/datum/nanite_extra_setting/text/get_value()
+ return html_encode(value)
+
/datum/nanite_extra_setting/text/get_frontend_list(name)
return list(list(
"name" = name,
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..44ebe11c29 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,20 +18,27 @@
/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))
- disk.forceMove(drop_location())
+ disk.forceMove(drop_location())
+ if(istype(user) && user.Adjacent(src))
+ user.put_in_active_hand(disk)
disk = null
/obj/machinery/computer/nanite_cloud_controller/proc/get_backup(cloud_id)
@@ -53,10 +58,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..495c788845 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,45 @@
/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))
- disk.forceMove(drop_location())
+ disk.forceMove(drop_location())
+ if(istype(user) && Adjacent(user))
+ user.put_in_active_hand(disk)
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 +140,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..b6a2c8b28b 100644
--- a/code/modules/research/nanites/nanite_programmer.dm
+++ b/code/modules/research/nanites/nanite_programmer.dm
@@ -3,41 +3,58 @@
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))
- disk.forceMove(drop_location())
+ disk.forceMove(drop_location())
+ if(istype(user) && user.Adjacent(src))
+ user.put_in_active_hand(disk)
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 +148,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_programs/suppression.dm b/code/modules/research/nanites/nanite_programs/suppression.dm
index 3b0d6d0d06..d2aa243fee 100644
--- a/code/modules/research/nanites/nanite_programs/suppression.dm
+++ b/code/modules/research/nanites/nanite_programs/suppression.dm
@@ -176,7 +176,7 @@
sent_message = message_setting.get_value()
if(host_mob.stat == DEAD)
return
- to_chat(host_mob, "You hear a strange, robotic voice in your head... \"[sent_message]\"")
+ to_chat(host_mob, "You hear a strange, robotic voice in your head... \"[html_encode(sent_message)]\"")
/datum/nanite_program/comm/hallucination
name = "Hallucination"
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/research/techweb/nodes/medical_nodes.dm b/code/modules/research/techweb/nodes/medical_nodes.dm
index 3a9e654b81..150e420c09 100644
--- a/code/modules/research/techweb/nodes/medical_nodes.dm
+++ b/code/modules/research/techweb/nodes/medical_nodes.dm
@@ -24,6 +24,23 @@
design_ids = list("defib_decay", "defib_shock", "defib_heal", "defib_speed")
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
+/datum/techweb_node/plumbing
+ id = "plumbing"
+ display_name = "Reagent Plumbing Technology"
+ description = "Plastic tubes, and machinery used for manipulating things in them."
+ prereq_ids = list("base")
+ design_ids = list("acclimator", "disposer", "plumb_filter", "plumb_synth", "plumb_grinder", "reaction_chamber", "duct_print", "plumb_splitter", "pill_press", "plumb_pump", "plumb_in", "plumb_out", "plumb_tank", "medipen_refiller")
+ research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 1000)
+
+
+/datum/techweb_node/advplumbing
+ id = "advplumbing"
+ display_name = "Advanced Plumbing Technology"
+ description = "Plumbing RCD."
+ prereq_ids = list("plumbing", "adv_engi")
+ design_ids = list("plumb_rcd", "autohydrotray")
+ research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
+
//////////////////////Cybernetics/////////////////////
/datum/techweb_node/surplus_limbs
diff --git a/code/modules/ruins/lavaland_ruin_code.dm b/code/modules/ruins/lavaland_ruin_code.dm
index 45b6939f42..e572f1ee02 100644
--- a/code/modules/ruins/lavaland_ruin_code.dm
+++ b/code/modules/ruins/lavaland_ruin_code.dm
@@ -93,7 +93,7 @@
/obj/item/stack/sheet/mineral/adamantine = /datum/species/golem/adamantine,
/obj/item/stack/sheet/plastic = /datum/species/golem/plastic,
/obj/item/stack/tile/brass = /datum/species/golem/clockwork,
- /obj/item/stack/tile/bronze = /datum/species/golem/bronze,
+ /obj/item/stack/sheet/bronze = /datum/species/golem/bronze,
/obj/item/stack/sheet/cardboard = /datum/species/golem/cardboard,
/obj/item/stack/sheet/leather = /datum/species/golem/leather,
/obj/item/stack/sheet/bone = /datum/species/golem/bone,
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/ruins/spaceruin_code/clericsden.dm b/code/modules/ruins/spaceruin_code/clericsden.dm
index 7d1fda6740..3fe4cad794 100644
--- a/code/modules/ruins/spaceruin_code/clericsden.dm
+++ b/code/modules/ruins/spaceruin_code/clericsden.dm
@@ -21,7 +21,6 @@
desc = "A weaker construct meant to scour ruins for objects of Nar'Sie's affection. Those barbed claws are no joke."
icon_state = "proteon"
icon_living = "proteon"
- threat = 0.4
maxHealth = 35
health = 35
melee_damage_lower = 8
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/shuttle/shuttle_creation/shuttle_creator.dm b/code/modules/shuttle/shuttle_creation/shuttle_creator.dm
index f5a11db60f..b3d99f22ab 100644
--- a/code/modules/shuttle/shuttle_creation/shuttle_creator.dm
+++ b/code/modules/shuttle/shuttle_creation/shuttle_creator.dm
@@ -43,6 +43,7 @@ GLOBAL_LIST_EMPTY(custom_shuttle_machines) //Machines that require updating (He
. = ..()
internal_shuttle_creator = new()
internal_shuttle_creator.owner_rsd = src
+ desc += " Attention, the max size of the shuttle is [SHUTTLE_CREATOR_MAX_SIZE]."
overlay_holder = new()
/obj/item/shuttle_creator/Destroy()
@@ -237,13 +238,13 @@ GLOBAL_LIST_EMPTY(custom_shuttle_machines) //Machines that require updating (He
port.register()
- icon_state = "rsd_used"
+ icon_state = "rsd_empty"
//Clear highlights
overlay_holder.clear_highlights()
GLOB.custom_shuttle_count ++
- message_admins("[ADMIN_LOOKUPFLW(user)] created a new shuttle with a [src] at [ADMIN_VERBOSEJMP(user)] ([GLOB.custom_shuttle_count] custom shuttles, limit is [CUSTOM_SHUTTLE_LIMIT])")
- log_game("[key_name(user)] created a new shuttle with a [src] at [AREACOORD(user)] ([GLOB.custom_shuttle_count] custom shuttles, limit is [CUSTOM_SHUTTLE_LIMIT])")
+ message_admins("[ADMIN_LOOKUPFLW(user)] created a new shuttle with a [src] at [ADMIN_VERBOSEJMP(user)] ([GLOB.custom_shuttle_count] custom shuttles)")
+ log_game("[key_name(user)] created a new shuttle with a [src] at [AREACOORD(user)] ([GLOB.custom_shuttle_count] custom shuttles)")
return TRUE
/obj/item/shuttle_creator/proc/create_shuttle_area(mob/user)
@@ -350,7 +351,7 @@ GLOBAL_LIST_EMPTY(custom_shuttle_machines) //Machines that require updating (He
loggedOldArea = get_area(get_turf(user))
loggedTurfs |= turfs
overlay_holder.highlight_area(turfs)
- //TODO READD THIS SHIT: icon_state = "rsd_used"
+ //TODO READD THIS SHIT: icon_state = "rsd_empty"
to_chat(user, "You add the area into the buffer of the [src], you made add more areas or select an airlock to act as a docking port to complete the shuttle.")
return turfs
diff --git a/code/modules/spells/spell.dm b/code/modules/spells/spell.dm
index 57502e2cd5..2272a14612 100644
--- a/code/modules/spells/spell.dm
+++ b/code/modules/spells/spell.dm
@@ -224,7 +224,15 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell)) //needed for th
/obj/effect/proc_holder/spell/proc/choose_targets(mob/user = usr) //depends on subtype - /targeted or /aoe_turf
return
-/obj/effect/proc_holder/spell/proc/can_target(mob/living/target)
+/**
+ * can_target: Checks if we are allowed to cast the spell on a target.
+ *
+ * Arguments:
+ * * target The atom that is being targeted by the spell.
+ * * user The mob using the spell.
+ * * silent If the checks should not give any feedback messages.
+ */
+/obj/effect/proc_holder/spell/proc/can_target(atom/target, mob/user, silent = FALSE)
return TRUE
/obj/effect/proc_holder/spell/proc/start_recharge()
@@ -296,6 +304,13 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell)) //needed for th
/obj/effect/proc_holder/spell/proc/cast(list/targets,mob/user = usr)
return
+/obj/effect/proc_holder/spell/proc/view_or_range(distance = world.view, center=usr, type="view")
+ switch(type)
+ if("view")
+ . = view(distance,center)
+ if("range")
+ . = range(distance,center)
+
/obj/effect/proc_holder/spell/proc/revert_cast(mob/user = usr) //resets recharge or readds a charge
switch(charge_type)
if("recharge")
@@ -345,7 +360,7 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell)) //needed for th
switch(max_targets)
if(0) //unlimited
for(var/mob/living/target in view_or_range(range, user, selection_type))
- if(!can_target(target))
+ if(!can_target(target, user, TRUE))
continue
targets += target
if(1) //single target can be picked
@@ -357,7 +372,7 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell)) //needed for th
for(var/mob/living/M in view_or_range(range, user, selection_type))
if(!include_user && user == M)
continue
- if(!can_target(M))
+ if(!can_target(M, user, TRUE))
continue
possible_targets += M
@@ -365,7 +380,7 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell)) //needed for th
//Adds a safety check post-input to make sure those targets are actually in range.
var/mob/M
if(!random_target)
- M = input("Choose the target for the spell.", "Targeting") as null|mob in possible_targets
+ M = input("Choose the target for the spell.", "Targeting") as null|mob in sortNames(possible_targets)
else
switch(random_target_priority)
if(TARGET_RANDOM)
@@ -385,7 +400,7 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell)) //needed for th
else
var/list/possible_targets = list()
for(var/mob/living/target in view_or_range(range, user, selection_type))
- if(!can_target(target))
+ if(!can_target(target, user, TRUE))
continue
possible_targets += target
for(var/i=1,i<=max_targets,i++)
@@ -411,7 +426,7 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell)) //needed for th
var/list/targets = list()
for(var/turf/target in view_or_range(range,user,selection_type))
- if(!can_target(target))
+ if(!can_target(target, user, TRUE))
continue
if(!(target in view_or_range(inner_radius,user,selection_type)))
targets += target
diff --git a/code/modules/spells/spell_types/cone_spells.dm b/code/modules/spells/spell_types/cone_spells.dm
new file mode 100644
index 0000000000..63bae4b7cf
--- /dev/null
+++ b/code/modules/spells/spell_types/cone_spells.dm
@@ -0,0 +1,117 @@
+/obj/effect/proc_holder/spell/cone
+ name = "Cone of Nothing"
+ desc = "Does nothing in a cone! Wow!"
+ school = "evocation"
+ charge_max = 100
+ clothes_req = FALSE
+ invocation = "FUKAN NOTHAN"
+ invocation_type = "shout"
+ sound = 'sound/magic/forcewall.ogg'
+ action_icon_state = "shield"
+ range = -1
+ cooldown_min = 0.5 SECONDS
+ ///This controls how many levels the cone has, increase this value to make a bigger cone.
+ var/cone_levels = 3
+ ///This value determines if the cone penetrates walls.
+ var/respect_density = FALSE
+
+/obj/effect/proc_holder/spell/cone/choose_targets(mob/user = usr)
+ perform(null, user=user)
+
+///This proc creates a list of turfs that are hit by the cone
+/obj/effect/proc_holder/spell/cone/proc/cone_helper(var/turf/starter_turf, var/dir_to_use, var/cone_levels = 3)
+ var/list/turfs_to_return = list()
+ var/turf/turf_to_use = starter_turf
+ var/turf/left_turf
+ var/turf/right_turf
+ var/right_dir
+ var/left_dir
+ switch(dir_to_use)
+ if(NORTH)
+ left_dir = WEST
+ right_dir = EAST
+ if(SOUTH)
+ left_dir = EAST
+ right_dir = WEST
+ if(EAST)
+ left_dir = NORTH
+ right_dir = SOUTH
+ if(WEST)
+ left_dir = SOUTH
+ right_dir = NORTH
+
+
+ for(var/i in 1 to cone_levels)
+ var/list/level_turfs = list()
+ turf_to_use = get_step(turf_to_use, dir_to_use)
+ level_turfs += turf_to_use
+ if(i != 1)
+ left_turf = get_step(turf_to_use, left_dir)
+ level_turfs += left_turf
+ right_turf = get_step(turf_to_use, right_dir)
+ level_turfs += right_turf
+ for(var/left_i in 1 to i -calculate_cone_shape(i))
+ if(left_turf.density && respect_density)
+ break
+ left_turf = get_step(left_turf, left_dir)
+ level_turfs += left_turf
+ for(var/right_i in 1 to i -calculate_cone_shape(i))
+ if(right_turf.density && respect_density)
+ break
+ right_turf = get_step(right_turf, right_dir)
+ level_turfs += right_turf
+ turfs_to_return += list(level_turfs)
+ if(i == cone_levels)
+ continue
+ if(turf_to_use.density && respect_density)
+ break
+ return turfs_to_return
+
+/obj/effect/proc_holder/spell/cone/cast(list/targets,mob/user = usr)
+ var/list/cone_turfs = cone_helper(get_turf(user), user.dir, cone_levels)
+ for(var/list/turf_list in cone_turfs)
+ do_cone_effects(turf_list)
+
+///This proc does obj, mob and turf cone effects on all targets in a list
+/obj/effect/proc_holder/spell/cone/proc/do_cone_effects(list/target_turf_list, level)
+ for(var/target_turf in target_turf_list)
+ if(!target_turf) //if turf is no longer there
+ continue
+ do_turf_cone_effect(target_turf, level)
+ if(isopenturf(target_turf))
+ var/turf/open/open_turf = target_turf
+ for(var/movable_content in open_turf)
+ if(isobj(movable_content))
+ do_obj_cone_effect(movable_content, level)
+ else if(isliving(movable_content))
+ do_mob_cone_effect(movable_content, level)
+
+///This proc deterimines how the spell will affect turfs.
+/obj/effect/proc_holder/spell/cone/proc/do_turf_cone_effect(turf/target_turf, level)
+ return
+
+///This proc deterimines how the spell will affect objects.
+/obj/effect/proc_holder/spell/cone/proc/do_obj_cone_effect(obj/target_obj, level)
+ return
+
+///This proc deterimines how the spell will affect mobs.
+/obj/effect/proc_holder/spell/cone/proc/do_mob_cone_effect(mob/living/target_mob, level)
+ return
+
+///This proc adjusts the cones width depending on the level.
+/obj/effect/proc_holder/spell/cone/proc/calculate_cone_shape(current_level)
+ var/end_taper_start = round(cone_levels * 0.8)
+ if(current_level > end_taper_start)
+ return (current_level % end_taper_start) * 2 //someone more talented and probably come up with a better formula.
+ else
+ return 2
+
+///This type of cone gradually affects each level of the cone instead of affecting the entire area at once.
+/obj/effect/proc_holder/spell/cone/staggered
+
+/obj/effect/proc_holder/spell/cone/staggered/cast(list/targets,mob/user = usr)
+ var/level_counter = 0
+ var/list/cone_turfs = cone_helper(get_turf(user), user.dir, cone_levels)
+ for(var/list/turf_list in cone_turfs)
+ level_counter++
+ addtimer(CALLBACK(src, .proc/do_cone_effects, turf_list, level_counter), 2 * level_counter)
diff --git a/code/modules/spells/spell_types/dumbfire.dm b/code/modules/spells/spell_types/dumbfire.dm
index 6931b4ac31..424c702a31 100644
--- a/code/modules/spells/spell_types/dumbfire.dm
+++ b/code/modules/spells/spell_types/dumbfire.dm
@@ -49,8 +49,8 @@
var/projectile_type = text2path(proj_type)
projectile = new projectile_type(user)
else if(istype(proj_type, /obj/effect/proc_holder/spell))
- projectile = new /obj/effect/proc_holder/spell/targeted/trigger(user)
- var/obj/effect/proc_holder/spell/targeted/trigger/T = projectile
+ projectile = new /obj/effect/proc_holder/spell/pointed/trigger(user)
+ var/obj/effect/proc_holder/spell/pointed/trigger/T = projectile
T.linked_spells += proj_type
else
projectile = new proj_type(user)
diff --git a/code/modules/spells/spell_types/ethereal_jaunt.dm b/code/modules/spells/spell_types/ethereal_jaunt.dm
index 8cf51d45c6..9d91b6534d 100644
--- a/code/modules/spells/spell_types/ethereal_jaunt.dm
+++ b/code/modules/spells/spell_types/ethereal_jaunt.dm
@@ -17,7 +17,7 @@
action_icon_state = "jaunt"
/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/cast(list/targets,mob/user = usr) //magnets, so mostly hardcoded
- playsound(get_turf(user), 'sound/magic/ethereal_enter.ogg', 50, 1, -1)
+ play_sound("enter",user)
for(var/mob/living/target in targets)
INVOKE_ASYNC(src, .proc/do_jaunt, target)
@@ -42,7 +42,7 @@
ADD_TRAIT(target, TRAIT_MOBILITY_NOMOVE, src)
target.update_mobility()
holder.reappearing = 1
- playsound(get_turf(target), 'sound/magic/ethereal_exit.ogg', 50, 1, -1)
+ play_sound("exit",target)
sleep(25 - jaunt_in_time)
new jaunt_in_type(mobloc, holder.dir)
target.setDir(holder.dir)
@@ -63,6 +63,13 @@
steam.set_up(10, 0, mobloc)
steam.start()
+/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/proc/play_sound(type,mob/living/target)
+ switch(type)
+ if("enter")
+ playsound(get_turf(target), 'sound/magic/ethereal_enter.ogg', 50, TRUE, -1)
+ if("exit")
+ playsound(get_turf(target), 'sound/magic/ethereal_exit.ogg', 50, TRUE, -1)
+
/obj/effect/dummy/phased_mob/spell_jaunt
name = "water"
icon = 'icons/effects/effects.dmi'
diff --git a/code/modules/spells/spell_types/mind_transfer.dm b/code/modules/spells/spell_types/mind_transfer.dm
deleted file mode 100644
index d2ef015d1d..0000000000
--- a/code/modules/spells/spell_types/mind_transfer.dm
+++ /dev/null
@@ -1,88 +0,0 @@
-/obj/effect/proc_holder/spell/targeted/mind_transfer
- name = "Mind Transfer"
- desc = "This spell allows the user to switch bodies with a target."
-
- school = "transmutation"
- charge_max = 600
- clothes_req = NONE
- invocation = "GIN'YU CAPAN"
- invocation_type = "whisper"
- range = 1
- cooldown_min = 200 //100 deciseconds reduction per rank
- var/unconscious_amount_caster = 400 //how much the caster is stunned for after the spell
- var/unconscious_amount_victim = 400 //how much the victim is stunned for after the spell
-
- action_icon_state = "mindswap"
-
-/*
-Urist: I don't feel like figuring out how you store object spells so I'm leaving this for you to do.
-Make sure spells that are removed from spell_list are actually removed and deleted when mind transferring.
-Also, you never added distance checking after target is selected. I've went ahead and did that.
-*/
-/obj/effect/proc_holder/spell/targeted/mind_transfer/cast(list/targets, mob/living/user = usr, distanceoverride, silent = FALSE)
- if(!targets.len)
- if(!silent)
- to_chat(user, "No mind found!")
- return
-
- if(targets.len > 1)
- if(!silent)
- to_chat(user, "Too many minds! You're not a hive damnit!")
- return
-
- var/mob/living/target = targets[1]
-
- var/t_He = target.p_they(TRUE)
- var/t_is = target.p_are()
-
- if(!(target in oview(range)) && !distanceoverride)//If they are not in overview after selection. Do note that !() is necessary for in to work because ! takes precedence over it.
- if(!silent)
- to_chat(user, "[t_He] [t_is] too far away!")
- return
-
- if(ismegafauna(target))
- if(!silent)
- to_chat(user, "This creature is too powerful to control!")
- return
-
- if(target.stat == DEAD)
- if(!silent)
- to_chat(user, "You don't particularly want to be dead!")
- return
-
- if(!target.key || !target.mind)
- if(!silent)
- to_chat(user, "[t_He] appear[target.p_s()] to be catatonic! Not even magic can affect [target.p_their()] vacant mind.")
- return
-
- if(user.suiciding)
- if(!silent)
- to_chat(user, "You're killing yourself! You can't concentrate enough to do this!")
- return
-
- var/datum/mind/TM = target.mind
- if(target.anti_magic_check(TRUE, FALSE) || TM.has_antag_datum(/datum/antagonist/wizard) || TM.has_antag_datum(/datum/antagonist/cult) || TM.has_antag_datum(/datum/antagonist/clockcult) || TM.has_antag_datum(/datum/antagonist/changeling) || TM.has_antag_datum(/datum/antagonist/rev) || target.key[1] == "@")
- if(!silent)
- to_chat(user, "[target.p_their(TRUE)] mind is resisting your spell!")
- return
-
- var/mob/living/victim = target//The target of the spell whos body will be transferred to.
- var/mob/living/caster = user//The wizard/whomever doing the body transferring.
-
- //MIND TRANSFER BEGIN
- var/mob/dead/observer/ghost = victim.ghostize(FALSE, TRUE)
- caster.mind.transfer_to(victim)
-
- ghost.mind.transfer_to(caster)
- if(ghost.key)
- ghost.transfer_ckey(caster) //have to transfer the key since the mind was not active
- qdel(ghost)
-
- //MIND TRANSFER END
-
- //Here we knock both mobs out for a time.
- caster.Unconscious(unconscious_amount_caster)
- victim.Unconscious(unconscious_amount_victim)
- SEND_SOUND(caster, sound('sound/magic/mandswap.ogg'))
- SEND_SOUND(victim, sound('sound/magic/mandswap.ogg'))// only the caster and victim hear the sounds, that way no one knows for sure if the swap happened
- return TRUE
diff --git a/code/modules/spells/spell_types/barnyard.dm b/code/modules/spells/spell_types/pointed/barnyard.dm
similarity index 52%
rename from code/modules/spells/spell_types/barnyard.dm
rename to code/modules/spells/spell_types/pointed/barnyard.dm
index 4b972e8030..61e3cf7127 100644
--- a/code/modules/spells/spell_types/barnyard.dm
+++ b/code/modules/spells/spell_types/pointed/barnyard.dm
@@ -1,51 +1,54 @@
-/obj/effect/proc_holder/spell/targeted/barnyardcurse
+/obj/effect/proc_holder/spell/pointed/barnyardcurse
name = "Curse of the Barnyard"
desc = "This spell dooms an unlucky soul to possess the speech and facial attributes of a barnyard animal."
school = "transmutation"
charge_type = "recharge"
charge_max = 150
charge_counter = 0
- clothes_req = NONE
- stat_allowed = 0
+ clothes_req = FALSE
+ stat_allowed = FALSE
invocation = "KN'A FTAGHU, PUCK 'BTHNK!"
invocation_type = "shout"
range = 7
cooldown_min = 30
- selection_type = "range"
- var/list/compatible_mobs = list(/mob/living/carbon/human, /mob/living/carbon/monkey)
-
+ ranged_mousepointer = 'icons/effects/mouse_pointers/barn_target.dmi'
action_icon_state = "barn"
+ active_msg = "You prepare to curse a target..."
+ deactive_msg = "You dispel the curse..."
+ /// List of mobs which are allowed to be a target of the spell
+ var/static/list/compatible_mobs_typecache = typecacheof(list(/mob/living/carbon/human, /mob/living/carbon/monkey))
-/obj/effect/proc_holder/spell/targeted/barnyardcurse/cast(list/targets, mob/user = usr)
+/obj/effect/proc_holder/spell/pointed/barnyardcurse/cast(list/targets, mob/user)
if(!targets.len)
- to_chat(user, "No target found in range.")
- return
+ to_chat(user, "No target found in range!")
+ return FALSE
+ if(!can_target(targets[1], user))
+ return FALSE
var/mob/living/carbon/target = targets[1]
-
- if(!(target.type in compatible_mobs))
- to_chat(user, "You are unable to curse [target]'s head!")
- return
-
- if(!(target in oview(range)))
- to_chat(user, "[target.p_theyre(TRUE)] too far away!")
- return
-
if(target.anti_magic_check())
to_chat(user, "The spell had no effect!")
target.visible_message("[target]'s face bursts into flames, which instantly burst outward, leaving [target] unharmed!", \
- "Your face starts burning up, but the flames are repulsed by your anti-magic protection!")
- return
+ "Your face starts burning up, but the flames are repulsed by your anti-magic protection!")
+ return FALSE
var/list/masks = list(/obj/item/clothing/mask/pig/cursed, /obj/item/clothing/mask/cowmask/cursed, /obj/item/clothing/mask/horsehead/cursed)
-
var/choice = pick(masks)
var/obj/item/clothing/mask/magichead = new choice(get_turf(target))
- magichead.flags_inv = null
+
target.visible_message("[target]'s face bursts into flames, and a barnyard animal's head takes its place!", \
"Your face burns up, and shortly after the fire you realise you have the face of a barnyard animal!")
if(!target.dropItemToGround(target.wear_mask))
qdel(target.wear_mask)
- target.equip_to_slot_if_possible(magichead, SLOT_WEAR_MASK, 1, 1)
-
+ target.equip_to_slot_if_possible(magichead, ITEM_SLOT_MASK, 1, 1)
target.flash_act()
+
+/obj/effect/proc_holder/spell/pointed/barnyardcurse/can_target(atom/target, mob/user, silent)
+ . = ..()
+ if(!.)
+ return FALSE
+ if(!is_type_in_typecache(target, compatible_mobs_typecache))
+ if(!silent)
+ to_chat(user, "You are unable to curse [target]!")
+ return FALSE
+ return TRUE
diff --git a/code/modules/spells/spell_types/pointed/blind.dm b/code/modules/spells/spell_types/pointed/blind.dm
new file mode 100644
index 0000000000..a773c5ad8d
--- /dev/null
+++ b/code/modules/spells/spell_types/pointed/blind.dm
@@ -0,0 +1,35 @@
+/obj/effect/proc_holder/spell/pointed/trigger/blind
+ name = "Blind"
+ desc = "This spell temporarily blinds a single target."
+ school = "transmutation"
+ charge_max = 300
+ clothes_req = FALSE
+ invocation = "STI KALY"
+ invocation_type = "whisper"
+ message = "Your eyes cry out in pain!"
+ cooldown_min = 50 //12 deciseconds reduction per rank
+ starting_spells = list("/obj/effect/proc_holder/spell/targeted/inflict_handler/blind", "/obj/effect/proc_holder/spell/targeted/genetic/blind")
+ ranged_mousepointer = 'icons/effects/mouse_pointers/blind_target.dmi'
+ action_icon_state = "blind"
+ active_msg = "You prepare to blind a target..."
+
+/obj/effect/proc_holder/spell/targeted/inflict_handler/blind
+ amt_eye_blind = 10
+ amt_eye_blurry = 20
+ sound = 'sound/magic/blind.ogg'
+
+/obj/effect/proc_holder/spell/targeted/genetic/blind
+ mutations = list(BLINDMUT)
+ duration = 300
+ charge_max = 400 // needs to be higher than the duration or it'll be permanent
+ sound = 'sound/magic/blind.ogg'
+
+/obj/effect/proc_holder/spell/pointed/trigger/blind/can_target(atom/target, mob/user, silent)
+ . = ..()
+ if(!.)
+ return FALSE
+ if(!isliving(target))
+ if(!silent)
+ to_chat(user, "You can only blind living beings!")
+ return FALSE
+ return TRUE
diff --git a/code/modules/spells/spell_types/pointed/mind_transfer.dm b/code/modules/spells/spell_types/pointed/mind_transfer.dm
new file mode 100644
index 0000000000..28d646f6b6
--- /dev/null
+++ b/code/modules/spells/spell_types/pointed/mind_transfer.dm
@@ -0,0 +1,103 @@
+/obj/effect/proc_holder/spell/pointed/mind_transfer
+ name = "Mind Transfer"
+ desc = "This spell allows the user to switch bodies with a target next to him."
+ school = "transmutation"
+ charge_max = 600
+ clothes_req = FALSE
+ invocation = "GIN'YU CAPAN"
+ invocation_type = "whisper"
+ range = 1
+ cooldown_min = 200 //100 deciseconds reduction per rank
+ ranged_mousepointer = 'icons/effects/mouse_pointers/mindswap_target.dmi'
+ action_icon_state = "mindswap"
+ active_msg = "You prepare to swap minds with a target..."
+ /// For how long is the caster stunned for after the spell
+ var/unconscious_amount_caster = 40 SECONDS
+ /// For how long is the victim stunned for after the spell
+ var/unconscious_amount_victim = 40 SECONDS
+
+/obj/effect/proc_holder/spell/pointed/mind_transfer/cast(list/targets, mob/living/user, silent = FALSE)
+ if(!targets.len)
+ if(!silent)
+ to_chat(user, "No mind found!")
+ return FALSE
+ if(targets.len > 1)
+ if(!silent)
+ to_chat(user, "Too many minds! You're not a hive damnit!")
+ return FALSE
+ if(!can_target(targets[1], user, silent))
+ return FALSE
+
+ var/mob/living/victim = targets[1] //The target of the spell whos body will be transferred to.
+ var/datum/mind/VM = victim.mind
+ if(victim.anti_magic_check(TRUE, FALSE) || VM.has_antag_datum(/datum/antagonist/wizard) || VM.has_antag_datum(/datum/antagonist/cult) || VM.has_antag_datum(/datum/antagonist/changeling) || VM.has_antag_datum(/datum/antagonist/rev) || victim.key[1] == "@")
+ if(!silent)
+ to_chat(user, "[victim.p_their(TRUE)] mind is resisting your spell!")
+ return FALSE
+ if(istype(victim, /mob/living/simple_animal/hostile/guardian))
+ var/mob/living/simple_animal/hostile/guardian/stand = victim
+ if(stand.summoner)
+ victim = stand.summoner
+
+ //You should not be able to enter one of the most powerful side-antags as a fucking wizard.
+ if(istype(victim,/mob/living/simple_animal/slaughter))
+ to_chat(user, "The devilish contract doesn't include the 'mind swappable' package, please try again another lifetime.")
+ return
+
+ //MIND TRANSFER BEGIN
+ var/mob/dead/observer/ghost = victim.ghostize()
+ user.mind.transfer_to(victim)
+
+ ghost.mind.transfer_to(user)
+ if(ghost.key)
+ user.key = ghost.key //have to transfer the key since the mind was not active
+ qdel(ghost)
+ //MIND TRANSFER END
+
+ //Here we knock both mobs out for a time.
+ user.Unconscious(unconscious_amount_caster)
+ victim.Unconscious(unconscious_amount_victim)
+ SEND_SOUND(user, sound('sound/magic/mandswap.ogg'))
+ SEND_SOUND(victim, sound('sound/magic/mandswap.ogg')) // only the caster and victim hear the sounds, that way no one knows for sure if the swap happened
+ return TRUE
+
+/obj/effect/proc_holder/spell/pointed/mind_transfer/can_target(atom/target, mob/user, silent)
+ . = ..()
+ if(!.)
+ return FALSE
+ if(!isliving(target))
+ if(!silent)
+ to_chat(user, "You can only swap minds with living beings!")
+ return FALSE
+ if(user == target)
+ if(!silent)
+ to_chat(user, "You can't swap minds with yourself!")
+ return FALSE
+
+ var/mob/living/victim = target
+ var/t_He = victim.p_they(TRUE)
+
+ if(ismegafauna(victim))
+ if(!silent)
+ to_chat(user, "This creature is too powerful to control!")
+ return FALSE
+ if(victim.stat == DEAD)
+ if(!silent)
+ to_chat(user, "You don't particularly want to be dead!")
+ return FALSE
+ if(!victim.key || !victim.mind)
+ if(!silent)
+ to_chat(user, "[t_He] appear[victim.p_s()] to be catatonic! Not even magic can affect [victim.p_their()] vacant mind.")
+ return FALSE
+ if(user.suiciding)
+ if(!silent)
+ to_chat(user, "You're killing yourself! You can't concentrate enough to do this!")
+ return FALSE
+ if(istype(victim, /mob/living/simple_animal/hostile/guardian))
+ var/mob/living/simple_animal/hostile/guardian/stand = victim
+ if(stand.summoner)
+ if(stand.summoner == user)
+ if(!silent)
+ to_chat(user, "Swapping minds with your own guardian would just put you back into your own head!")
+ return FALSE
+ return TRUE
diff --git a/code/modules/spells/spell_types/pointed/pointed.dm b/code/modules/spells/spell_types/pointed/pointed.dm
new file mode 100644
index 0000000000..7b942dee27
--- /dev/null
+++ b/code/modules/spells/spell_types/pointed/pointed.dm
@@ -0,0 +1,105 @@
+/obj/effect/proc_holder/spell/pointed
+ name = "pointed spell"
+ ranged_mousepointer = 'icons/effects/mouse_pointers/throw_target.dmi'
+ action_icon_state = "projectile"
+ /// Message showing to the spell owner upon deactivating pointed spell.
+ var/deactive_msg = "You dispel the magic..."
+ /// Message showing to the spell owner upon activating pointed spell.
+ var/active_msg = "You prepare to use the spell on a target..."
+ /// Variable dictating if the user is allowed to cast a spell on himself.
+ var/self_castable = FALSE
+ /// Variable dictating if the spell will use turf based aim assist
+ var/aim_assist = TRUE
+
+/obj/effect/proc_holder/spell/pointed/Trigger(mob/user, skip_can_cast = TRUE)
+ if(!istype(user))
+ return
+ var/msg
+ if(!can_cast(user))
+ msg = "You can no longer cast [name]!"
+ remove_ranged_ability(msg)
+ return
+ if(active)
+ msg = "[deactive_msg]"
+ remove_ranged_ability(msg)
+ else
+ msg = "[active_msg] Left-click to activate spell on a target!"
+ add_ranged_ability(user, msg, TRUE)
+ on_activation(user)
+
+/obj/effect/proc_holder/spell/pointed/on_lose(mob/living/user)
+ remove_ranged_ability()
+
+/obj/effect/proc_holder/spell/pointed/remove_ranged_ability(msg)
+ . = ..()
+ on_deactivation(ranged_ability_user)
+
+/obj/effect/proc_holder/spell/pointed/add_ranged_ability(mob/living/user, msg, forced)
+ . = ..()
+ on_activation(user)
+
+/**
+ * on_activation: What happens upon pointed spell activation.
+ *
+ * Arguments:
+ * * user The mob interacting owning the spell.
+ */
+/obj/effect/proc_holder/spell/pointed/proc/on_activation(mob/user)
+ return
+
+/**
+ * on_activation: What happens upon pointed spell deactivation.
+ *
+ * Arguments:
+ * * user The mob interacting owning the spell.
+ */
+/obj/effect/proc_holder/spell/pointed/proc/on_deactivation(mob/user)
+ return
+
+/obj/effect/proc_holder/spell/pointed/update_icon()
+ if(!action)
+ return
+ if(active)
+ action.button_icon_state = "[action_icon_state]1"
+ else
+ action.button_icon_state = "[action_icon_state]"
+ action.UpdateButtonIcon()
+
+/obj/effect/proc_holder/spell/pointed/InterceptClickOn(mob/living/caller, params, atom/target)
+ if(..())
+ return TRUE
+ if(aim_assist && isturf(target))
+ var/list/possible_targets = list()
+ for(var/A in target)
+ if(intercept_check(caller, A, TRUE))
+ possible_targets += A
+ if(possible_targets.len == 1)
+ target = possible_targets[1]
+ if(!intercept_check(caller, target))
+ return TRUE
+ if(!cast_check(FALSE, caller))
+ return TRUE
+ perform(list(target), user = caller)
+ remove_ranged_ability()
+ return TRUE // Do not do any underlying actions after the spell cast
+
+/**
+ * intercept_check: Specific spell checks for InterceptClickOn() targets.
+ *
+ * Arguments:
+ * * user The mob using the ranged spell via intercept.
+ * * target The atom that is being targeted by the spell via intercept.
+ * * silent If the checks should produce not any feedback messages for the user.
+ */
+/obj/effect/proc_holder/spell/pointed/proc/intercept_check(mob/user, atom/target, silent = FALSE)
+ if(!self_castable && target == user)
+ if(!silent)
+ to_chat(user, "You cannot cast the spell on yourself!")
+ return FALSE
+ if(!(target in view_or_range(range, user, selection_type)))
+ if(!silent)
+ to_chat(user, "[target.p_theyre(TRUE)] too far away!")
+ return FALSE
+ if(!can_target(target, user, silent))
+ return FALSE
+ return TRUE
diff --git a/code/modules/spells/spell_types/projectile.dm b/code/modules/spells/spell_types/projectile.dm
index be305520a2..3a8f48cf7c 100644
--- a/code/modules/spells/spell_types/projectile.dm
+++ b/code/modules/spells/spell_types/projectile.dm
@@ -37,8 +37,8 @@
var/projectile_type = text2path(proj_type)
projectile = new projectile_type(user)
if(istype(proj_type, /obj/effect/proc_holder/spell))
- projectile = new /obj/effect/proc_holder/spell/targeted/trigger(user)
- var/obj/effect/proc_holder/spell/targeted/trigger/T = projectile
+ projectile = new /obj/effect/proc_holder/spell/pointed/trigger(user)
+ var/obj/effect/proc_holder/spell/pointed/trigger/T = projectile
T.linked_spells += proj_type
projectile.icon = proj_icon
projectile.icon_state = proj_icon_state
diff --git a/code/modules/spells/spell_types/trigger.dm b/code/modules/spells/spell_types/trigger.dm
index 39cff63d98..df579d9243 100644
--- a/code/modules/spells/spell_types/trigger.dm
+++ b/code/modules/spells/spell_types/trigger.dm
@@ -1,30 +1,26 @@
-/obj/effect/proc_holder/spell/targeted/trigger
+/obj/effect/proc_holder/spell/pointed/trigger
name = "Trigger"
desc = "This spell triggers another spell or a few."
-
var/list/linked_spells = list() //those are just referenced by the trigger spell and are unaffected by it directly
var/list/starting_spells = list() //those are added on New() to contents from default spells and are deleted when the trigger spell is deleted to prevent memory leaks
-/obj/effect/proc_holder/spell/targeted/trigger/Initialize()
+/obj/effect/proc_holder/spell/pointed/trigger/Initialize()
. = ..()
-
for(var/spell in starting_spells)
var/spell_to_add = text2path(spell)
new spell_to_add(src) //should result in adding to contents, needs testing
-/obj/effect/proc_holder/spell/targeted/trigger/Destroy()
+/obj/effect/proc_holder/spell/pointed/trigger/Destroy()
for(var/spell in contents)
qdel(spell)
linked_spells = null
starting_spells = null
return ..()
-/obj/effect/proc_holder/spell/targeted/trigger/cast(list/targets,mob/user = usr)
+/obj/effect/proc_holder/spell/pointed/trigger/cast(list/targets,mob/user = usr)
playMagSound()
for(var/mob/living/target in targets)
for(var/obj/effect/proc_holder/spell/spell in contents)
spell.perform(list(target),0)
for(var/obj/effect/proc_holder/spell/spell in linked_spells)
spell.perform(list(target),0)
-
- return
\ No newline at end of file
diff --git a/code/modules/spells/spell_types/wizard.dm b/code/modules/spells/spell_types/wizard.dm
index 13c47ab9ac..14f359ef81 100644
--- a/code/modules/spells/spell_types/wizard.dm
+++ b/code/modules/spells/spell_types/wizard.dm
@@ -207,40 +207,12 @@
summon_type = list(/mob/living/simple_animal/hostile/netherworld)
cast_sound = 'sound/magic/summonitems_generic.ogg'
-/obj/effect/proc_holder/spell/targeted/trigger/blind
- name = "Blind"
- desc = "This spell temporarily blinds a single person and does not require wizard garb."
-
- school = "transmutation"
- charge_max = 300
- clothes_req = NONE
- invocation = "STI KALY"
- invocation_type = "whisper"
- message = "Your eyes cry out in pain!"
- cooldown_min = 50 //12 deciseconds reduction per rank
-
- starting_spells = list("/obj/effect/proc_holder/spell/targeted/inflict_handler/blind","/obj/effect/proc_holder/spell/targeted/genetic/blind")
-
- action_icon_state = "blind"
-
/obj/effect/proc_holder/spell/aoe_turf/conjure/creature/cult
name = "Summon Creatures (DANGEROUS)"
clothes_req = SPELL_CULT_GARB
charge_max = 5000
summon_amt = 2
-
-
-/obj/effect/proc_holder/spell/targeted/inflict_handler/blind
- amt_eye_blind = 10
- amt_eye_blurry = 20
- sound = 'sound/magic/blind.ogg'
-
-/obj/effect/proc_holder/spell/targeted/genetic/blind
- mutations = list(BLINDMUT)
- duration = 300
- sound = 'sound/magic/blind.ogg'
-
/obj/effect/proc_holder/spell/aoe_turf/repulse
name = "Repulse"
desc = "This spell throws everything around the user away."
diff --git a/code/modules/station_goals/bsa.dm b/code/modules/station_goals/bsa.dm
index 2ca0e65477..472734b74b 100644
--- a/code/modules/station_goals/bsa.dm
+++ b/code/modules/station_goals/bsa.dm
@@ -180,16 +180,37 @@
reload()
/obj/machinery/bsa/full/proc/fire(mob/user, turf/bullseye)
- var/turf/point = get_front_turf()
- for(var/turf/T in getline(get_step(point,dir),get_target_turf()))
- T.ex_act(EXPLODE_DEVASTATE)
- point.Beam(get_target_turf(),icon_state="bsa_beam",time=50,maxdistance = world.maxx) //ZZZAP
-
- message_admins("[ADMIN_LOOKUPFLW(user)] has launched an artillery strike.")
- explosion(bullseye,ex_power,ex_power*2,ex_power*4)
-
reload()
+ var/turf/point = get_front_turf()
+ var/turf/target = get_target_turf()
+ var/atom/movable/blocker
+ for(var/T in getline(get_step(point, dir), target))
+ var/turf/tile = T
+ if(SEND_SIGNAL(tile, COMSIG_ATOM_BSA_BEAM) & COMSIG_ATOM_BLOCKS_BSA_BEAM)
+ blocker = tile
+ else
+ for(var/AM in tile)
+ var/atom/movable/stuff = AM
+ if(SEND_SIGNAL(stuff, COMSIG_ATOM_BSA_BEAM) & COMSIG_ATOM_BLOCKS_BSA_BEAM)
+ blocker = stuff
+ break
+ if(blocker)
+ target = tile
+ break
+ else
+ tile.ex_act(EXPLODE_DEVASTATE)
+ point.Beam(target, icon_state = "bsa_beam", time = 50, maxdistance = world.maxx) //ZZZAP
+ new /obj/effect/temp_visual/bsa_splash(point, dir)
+
+ if(!blocker)
+ message_admins("[ADMIN_LOOKUPFLW(user)] has launched an artillery strike targeting [ADMIN_VERBOSEJMP(bullseye)].")
+ log_game("[key_name(user)] has launched an artillery strike targeting [AREACOORD(bullseye)].")
+ explosion(bullseye, ex_power, ex_power*2, ex_power*4)
+ else
+ message_admins("[ADMIN_LOOKUPFLW(user)] has launched an artillery strike targeting [ADMIN_VERBOSEJMP(bullseye)] but it was blocked by [blocker] at [ADMIN_VERBOSEJMP(target)].")
+ log_game("[key_name(user)] has launched an artillery strike targeting [AREACOORD(bullseye)] but it was blocked by [blocker] at [AREACOORD(target)].")
+
/obj/machinery/bsa/full/proc/reload()
ready = FALSE
use_power(power_used_per_shot)
@@ -210,20 +231,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 +279,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/uplink/uplink_items/uplink_clothing.dm b/code/modules/uplink/uplink_items/uplink_clothing.dm
index 014e0452b5..c26a9ae1f0 100644
--- a/code/modules/uplink/uplink_items/uplink_clothing.dm
+++ b/code/modules/uplink/uplink_items/uplink_clothing.dm
@@ -97,3 +97,9 @@
item = /obj/item/clothing/gloves/tackler/combat/insulated
include_modes = list(/datum/game_mode/nuclear, /datum/game_mode/nuclear/clown_ops)
cost = 2
+
+/datum/uplink_item/device_tools/syndicate_eyepatch
+ name = "Mechanical Eyepatch"
+ desc = "An eyepatch that connects itself to your eye socket, enhancing your shooting to an impossible degree, allowing your bullets to ricochet far more often than usual."
+ item = /obj/item/clothing/glasses/eyepatch/syndicate
+ cost = 8
diff --git a/code/modules/uplink/uplink_items/uplink_stealthdevices.dm b/code/modules/uplink/uplink_items/uplink_stealthdevices.dm
index f1c27c640b..28d02cf79b 100644
--- a/code/modules/uplink/uplink_items/uplink_stealthdevices.dm
+++ b/code/modules/uplink/uplink_items/uplink_stealthdevices.dm
@@ -112,13 +112,13 @@
name = "Radio Jammer"
desc = "This device will disrupt any nearby outgoing radio communication when activated. Does not affect binary chat."
item = /obj/item/jammer
- cost = 5
+ cost = 2
/datum/uplink_item/stealthy_tools/smugglersatchel
name = "Smuggler's Satchel"
desc = "This satchel is thin enough to be hidden in the gap between plating and tiling; great for stashing \
your stolen goods. Comes with a crowbar and a floor tile inside. Properly hidden satchels have been \
- known to survive intact even beyond the current shift. "
+ known to survive intact even beyond the current shift, but this is just a myth. "
item = /obj/item/storage/backpack/satchel/flat
- cost = 2
+ cost = 1
surplus = 30
diff --git a/code/modules/vehicles/_vehicle.dm b/code/modules/vehicles/_vehicle.dm
index ac7fa879f4..12e9f365d0 100644
--- a/code/modules/vehicles/_vehicle.dm
+++ b/code/modules/vehicles/_vehicle.dm
@@ -18,6 +18,7 @@
var/canmove = TRUE
var/emulate_door_bumps = TRUE //when bumping a door try to make occupants bump them to open them.
var/default_driver_move = TRUE //handle driver movement instead of letting something else do it like riding datums.
+ var/enclosed = FALSE // is the rider protected from bullets? assume no
var/list/autogrant_actions_passenger //plain list of typepaths
var/list/autogrant_actions_controller //assoc list "[bitflag]" = list(typepaths)
var/list/mob/occupant_actions //assoc list mob = list(type = action datum assigned to mob)
@@ -166,3 +167,9 @@
if(trailer && .)
var/dir_to_move = get_dir(trailer.loc, newloc)
step(trailer, dir_to_move)
+
+/obj/vehicle/bullet_act(obj/item/projectile/Proj) //wrapper
+ if (!enclosed && length(occupants) && !Proj.force_hit && (Proj.def_zone == BODY_ZONE_HEAD || Proj.def_zone == BODY_ZONE_CHEST)) //allows bullets to hit drivers
+ occupants[1].bullet_act(Proj) // driver dinkage
+ return BULLET_ACT_HIT
+ . = ..()
diff --git a/code/modules/vehicles/sealed.dm b/code/modules/vehicles/sealed.dm
index edaab8b982..28f6b1cca8 100644
--- a/code/modules/vehicles/sealed.dm
+++ b/code/modules/vehicles/sealed.dm
@@ -1,4 +1,5 @@
/obj/vehicle/sealed
+ enclosed = TRUE // you're in a sealed vehicle dont get dinked idiot
var/enter_delay = 20
flags_1 = BLOCK_FACE_ATOM_1
diff --git a/code/modules/vending/_vending.dm b/code/modules/vending/_vending.dm
index 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/medical.dm b/code/modules/vending/medical.dm
index 34de3b490c..795d35adc4 100644
--- a/code/modules/vending/medical.dm
+++ b/code/modules/vending/medical.dm
@@ -46,7 +46,8 @@
/obj/item/wrench/medical = 1,
/obj/item/storage/belt/medolier/full = 2,
/obj/item/gun/syringe/dart = 2,
- /obj/item/storage/briefcase/medical = 2)
+ /obj/item/storage/briefcase/medical = 2,
+ /obj/item/plunger/reinforced = 2)
armor = list("melee" = 100, "bullet" = 100, "laser" = 100, "energy" = 100, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 50)
resistance_flags = FIRE_PROOF
diff --git a/code/modules/vending/snack.dm b/code/modules/vending/snack.dm
index 7aef2b627c..ff8fd46676 100644
--- a/code/modules/vending/snack.dm
+++ b/code/modules/vending/snack.dm
@@ -12,7 +12,8 @@
/obj/item/reagent_containers/food/snacks/no_raisin = 5,
/obj/item/reagent_containers/food/snacks/spacetwinkie = 5,
/obj/item/reagent_containers/food/snacks/cheesiehonkers = 5,
- /obj/item/reagent_containers/food/snacks/cornchips = 5)
+ /obj/item/reagent_containers/food/snacks/cornchips = 5,
+ /obj/item/reagent_containers/food/snacks/energybar = 6)
contraband = list(
/obj/item/reagent_containers/food/snacks/cracker = 10,
/obj/item/reagent_containers/food/snacks/honeybar = 5,
diff --git a/code/modules/zombie/items.dm b/code/modules/zombie/items.dm
index 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..a5b0d0b8c4 100644
--- a/config/game_options.txt
+++ b/config/game_options.txt
@@ -116,6 +116,7 @@ CONTINUOUS CHANGELING
CONTINUOUS WIZARD
#CONTINUOUS MONKEY
CONTINUOUS BLOODSUCKER
+CONTINUOUS HERESY
##Note: do not toggle continuous off for these modes, as they have no antagonists and would thus end immediately!
@@ -445,6 +446,7 @@ ROUNDSTART_RACES plasmaman
#ROUNDSTART_RACES shadow
ROUNDSTART_RACES felinid
ROUNDSTART_RACES dwarf
+ROUNDSTART_RACES ethereal
## Races that are better than humans in some ways, but worse in others
#ROUNDSTART_RACES jelly
diff --git a/config/spaceRuinBlacklist.txt b/config/spaceRuinBlacklist.txt
index 90682f5bad..969e4135f6 100644
--- a/config/spaceRuinBlacklist.txt
+++ b/config/spaceRuinBlacklist.txt
@@ -52,3 +52,4 @@
#_maps/RandomRuins/SpaceRuins/arcade.dmm
#_maps/RandomRuins/SpaceRuins/spacehermit.dmm
#_maps/RandomRuins/SpaceRuins/advancedlab.dmm
+#_maps/RandomRuins/SpaceRuins/spacediner.dmm
diff --git a/html/changelog.html b/html/changelog.html
index 2e0891ed7f..fd590f4673 100644
--- a/html/changelog.html
+++ b/html/changelog.html
@@ -50,6 +50,407 @@
-->
+
23 August 2020
+
DeltaFire15 updated:
+
+
silicons and clockies can now access APCs properly
+
+
EmeraldSundisk updated:
+
+
Medbay now has a smartfridge for organ storage
+
Slight enhancements to the station's electrical wiring layout
+
Very small library renovation
+
Exterior airlocks have been given proper air systems for safety's sake
+
+
Ghommie updated:
+
+
Stops shielded hardsuits from slowly turning the wearer into a big glowing ball of stacked energy shield overlays.
+
the shielding overlay is merely visual as result. Aim your clicks.
+
+
Ludox235 updated:
+
+
no more 10 pop xenos (25pop now)
+
+
MrJWhit updated:
+
+
Increases the majority of airlocks by 1 tile.
+
Minor adjustments to the TEG engine.
+
+
Putnam3145 updated:
+
+
Simplemobs no longer count in dynamic.
+
"Story" storyteller no longer starts at a ludicrously low threat, always.
+
Blob threat now scales with coverage.
+
One person with their pref on no longer overpowers 40 people who might not even know there is one.
+
Negative-weight rulesets are no longer put into the list.
+
+
kiwedespars updated:
+
+
removed durathread from armwraps recipe.
+
+
lolman360 updated:
+
+
breath mask balaclava
+
+
timothyteakettle updated:
+
+
lizards are now a recommended species for mam snouts
+
+
zeroisthebiggay updated:
+
+
new sprites for the temporal katana
+
suiciding with the temporal katana omae wa mou shinderius you into the shadow realm
+
twilight isnt earrape
+
+
+
22 August 2020
+
Time-Green (copypasta'd by lolman360) updated:
+
+
plumbing
+
automatic hydro trays
+
+
+
21 August 2020
+
LetterN updated:
+
+
Updates and adds some of the tips
+
+
Putnam3145 updated:
+
+
added reftracking as a compile flag
+
+
SmArtKar updated:
+
+
RSD limitation is now 500 tiles
+
Fixed broken RSD sprites
+
Removed that shuttle limit
+
+
timothyteakettle updated:
+
+
two snouts can once again be chosen in customization
+
lizard snouts work again
+
+
+
20 August 2020
+
DeltaFire15 updated:
+
+
The cooking oil damage formula is no longer scuffed.
+
Changed the clockie help-link to lead to our own wiki.
+
+
Fikou updated:
+
+
admins can now do html in ahelps properly
+
+
Hatterhat updated:
+
+
Pirate threats are now announced as "business propositions", and their arrivals are now also announced properly.
+
+
tiramisuapimancer updated:
+
+
Ethereal hair is now their body color instead of accidentally white
+
+
+
18 August 2020
+
DeltaFire15 updated:
+
+
kindle cast time: 15ds -> 25ds
+
Moved the Belligerent Scripture to where it should be in the code
+
+
Detective-Google updated:
+
+
glass floors
+
uncrowbarrable plasma floors tweak:disco inferno's plasma floors can no longer be crowbarred.
+
ghost cafe has funky fresh art
+
you can actually remove glass floors now
+
get_equipped_items is hopefully less gross
+
plasma cutters are no longer gay
+
+
Hatterhat updated:
+
+
Slaughter demons (and laughter demons, being a subtype) are MOB_SIZE_LARGE, with one of the more immediate effects being able to mark them with a crusher and backstab them.
+
The funny blyat men have stumbled upon another surplus of Mosin-Nagants and are starting to pack them into crates again.
+
Vehicle riders can now, by default, get shot in the face and/or chest.
+
Adminspawn only .357 DumDum rounds! Because sometimes the other guy just really needs to hurt.
+
Bluespace beakers now have a chemical window through the side that shows chemical overlays.
+
Plant DNA manipulators now let you chuck things over them. Or they WOULD, if LETPASSTHROW worked half a damn.
+
+
LetterN updated:
+
+
uplink implant states
+
tweaks how role assigning works
+
+
MrJWhit updated:
+
+
Gives ashwalkers nightvision
+
Makes tesla blast people, not the environment, to save the server.
+
+
TheObserver-sys updated:
+
+
moves Garlic sprites from growing.dmi to growing_vegetable.dmi
+
Removes the unused Electric Lime mutation, it just takes up space with no actual function nor sprites.
+
Gives Catnip growing sprites
+
Removes redundant images in growing.dmi
+
+
kiwedespars updated:
+
+
10 force to a fucking rubber cock.
+
+
lolman360 updated:
+
+
shotgun stripper clip nerf. ammoboxes can now accept a load_delay that happens when they attack a magazine, internal or external.
+
+
ported from tg updated:
+
+
bronze airlocks and windows can now be built
+
i also tweaked bronze flooring to be cheaper.
+
+
silicons updated:
+
+
stamina draining projectiles without stamina for their primary damage type now has their stamina damage taken into account for shield blocking, rather than the block being done for free for that.
+
+
timothyteakettle updated:
+
+
snowflake code tidyup
+
snowflake code for mutant bodypart selection has been rewritten to be ~14x shorter
+
meat type and horns can now be selected by any species
+
+
+
17 August 2020
+
DeltaFire15 updated:
+
+
Cogscarabs are no longer always Pogscarabs
+
+
Strazyplus updated:
+
+
Added drakeborgs
+
Added drakeplushies
+
added drakeborg sprites
+
added drakeplushie sprites
+
changed some code - added drakeplushies to backpack loadout Removed duplicate voresleeper belly sprites from engdrake & jantidrake. [CC BY-NC-SA 3.0](https://creativecommons.org/licenses/by-nc-sa/3.0/)
+
Added CC BY-NC-SA 3.0 license details to icon/mob/cyborg moved drakeborg.dmi to icon/mob/cyborg
+
+
+
16 August 2020
+
kiwedespars updated:
+
+
nerfed hypereut chaplain weapon.
+
50% rng blockchance -> 0%
+
parry made much worse because it's an actual weapon and a roundstart one at that.
+
+
zeroisthebiggay updated:
+
+
tips
+
+
+
15 August 2020
+
LetterN updated:
+
+
missing anomaly core icons
+
wrong state. blame the tg vertion i copied
+
+
silicons updated:
+
+
the 8 rotation limit from clockwork chairs has been removed. please don't abuse this.
The temporal katana is now slightly more worthy of the 2 spell point cost, with a smaller, antimagic respecting timestop, less force, and no random blockchance. Society has progressed past the need for blockchance.
+
+
LetterN updated:
+
+
Mafia Component
+
Fixed missing icons and handtele
+
+
Putnam3145 updated:
+
+
a whole lot of jank regarding funny part sprite display.
+
+
Toriate updated:
+
+
Opossums have migrated into the maintenance tunnels! Seek them out at your own peril!
+
+
ancientpower updated:
+
+
Doors added to the west side of box medbay to make things a bit more manageable.
+
+
kappa-sama updated:
+
+
smuggler satchel cost 2->1
+
radio jammer cost 5->2
+
smuggler satchel uplink description now implies that persistence is disabled
+
+
lolman360 updated:
+
+
renameable necklace (accessory, attaches to suit) and ring (glove slot.)
+
custom rename is now 2048 characters? i think it's characters.
+
+
silicons updated:
+
+
You can now use anything as an emoji by doing :/obj/item/path/to/item:. This works for any /atom or subtype.
+
+
timothyteakettle updated:
+
+
syndicate agents now have access to mechanical aim enhancers which allow them to aim bullets to bounce off walls
+
ricochets work properly now for the bullets that support them
+
+
zeroisthebiggay updated:
+
+
hair and some sechuds
+
ce hardsuit radproofing
+
+
+
11 August 2020
+
Hatterhat updated:
+
+
PDA uplinks can now steal from pens. Properly. Just make sure to have a pen in your PDA, first.
+
+
kappa-sama updated:
+
+
tracer no longer gives you full stamheals per use
+
+
zeroisthebiggay updated:
+
+
volaju two
+
+
+
10 August 2020
+
Hatterhat updated:
+
+
Parry counterattack text now shows up.
+
Sterilized gauze is now better at stopping bleeding, and applies slightly faster. Very slightly faster.
+
Ointment and sutures now hold more in a stack (12 and 15, respectively).
+
Sterilized gauze can now be made by just pouring 10u sterilizine onto standard medical gauze, instead of having to craft it. Why you had to craft it, I will honestly never know.
+
Proto-kinetic glaives are more expensive, stagger/cooldown on failed parries increased slightly, perfect parries required for counterattack.
+
New item: Temporal Katana. 2 points for wizards, timestops upon successful parry, bokken quickparry stats (100 force on melee counter!).
+
Also you can *smirk. This has no mechanical effect, other than being smug.
+
+
KeRSedChaplain updated:
+
+
Added a guide for romerol usage
+
made infectious zombies not enter softcrit and take no stamina damage
+
+
LetterN updated:
+
+
clocktheme color
+
Ports TGUI-4
+
+
Lynxless updated:
+
+
Ports TG #51879
+
+
Owai-Seek updated:
+
+
Meatballs now spawn raw from food processors.
+
+
Putnam3145 updated:
+
+
Ethereals
+
(Hexa)crocin
+
(Hexa)camphor
+
Tweaked wording for marking tickets IC issue.
+
Rerolling your traitor goals will ONLY give you "proper" objectives.
+
+
Seris02 updated:
+
+
borgs being able to select and use a module when it's too damaged
+
+
Sishen1542 updated:
+
+
gave chairs active block/parry in exchange for removal of block_chance
+
replaces box whiteship tbaton with truncheon
+
+
kappa-sama updated:
+
+
made the Dirty Magazines crate cost 4000 instead of 12000 credits
+
MODS I SPILLED MU JUICE HEJPPHRLP HELPJ JLEP HELP
+
+
silicons updated:
+
+
player made areas are no longer valid for malf hacking
+
default space levels is 4 again.
+
rats now swarm instead of stacking on one spot.
+
getting hit by an explosion will now barely hard knockdown, but will leave you somewhat winded.
+
+
timothyteakettle updated:
+
+
speech verbs copy through dna copying now
+
+
+
09 August 2020
+
Hatterhat updated:
+
+
Proto-kinetic glaives (not crushers) can parry now.
+
+
MrJWhit updated:
+
+
Adds a second shutter on the top of the hop line
+
+
silicons updated:
+
+
immovable rods no longer drop down chasms
+
fun removal: squeaking objects now have an 1 second cooldown between squeaks, and will have a 33% chance of interrupting any other squeaking object when Cross()ing, meaning no more ear-fuck conveyor belts.
+
+
+
08 August 2020
+
DeltaFire15 updated:
+
+
Roundstart cultists now start with a replica fabricator - no brass though, make your own.
+
Kindle cast time: 10 > 15, mute after stun end: 2 > 5, slur after mute end: 3 > 5
+
The ratvarian spear no longer adds negative vitality under very specific circumstances.
+
The Ratvarian Spear can parry now! Short parries with low leeway, but low cooldown.
+
The brass claw, a implant-based weapon which gains combo on consecutive hits against the same target.
+
The sigil of rites, a sigil used to perform various rites with a cost of power and materials
+
The Rite of Advancement: Used to add a organ or cyberimplant to a clockie without need for surgery.
+
The Rite of Woundmending: Used to heal all wounds on another cultist, causing toxins damage in return.
+
The Rite of the Claw: Used to summon a brass claw implant. Maximum of 4 uses per round.
+
+
Hatterhat updated:
+
+
You can now buy a toolbox's worth of Mosin-Nagant ammo for a fairly discounted price.
+
Revolvers from the dedicated kit now have reskinning capabilities.
+
You can now actually buy the riflery primer, which lets you pump shotguns and work the Mosin's bolt faster.
+
Bulldog slug magazines now have a unique sprite.
+
+
Ludox235 updated:
+
+
Removed an abductee objective that told you to remove all oxygen.
+
Added a new abductee objective to replace the removed one.
+
+
Sishen1542 updated:
+
+
🅱ï¸oneless
+
squishy slime emotes
+
+
timothyteakettle updated:
+
+
heparin makes you bleed half as much now
+
cuts make you bleed 25% less now
+
more items in the loadout and loadout has subcategories now for easier searching
+
+
07 August 2020
dapnee updated:
@@ -1067,271 +1468,6 @@
Adds IV bags.
-
-
21 June 2020
-
kevinz000 updated:
-
-
calculations for punch hit chance has been drastically buffed in favor of the attacker.
-
-
-
20 June 2020
-
LetterN updated:
-
-
Asset cache from tg
-
Made the map viewer not look bad
-
Admin matrix right-bracket
-
-
bunny232 updated:
-
-
Removed unsavory things from the vent clog event
-
-
-
19 June 2020
-
Bhijn updated:
-
-
Atmos can no longer become completely bricked
-
-
Funce updated:
-
-
Square root circuit should now actually work.
-
-
SmArtKar updated:
-
-
Fixed my runtimes
-
-
TheSpaghetti updated:
-
-
more insectoid insects
-
-
kevinz000 updated:
-
-
bay/polaris style say_emphasis has been added. You can now |italicize| _underline_ and +bold+ your messages.
-
-
timothyteakettle updated:
-
-
Adds the brain trauma event, where one player gets a random brain trauma!
-
Adds the wisdom cow event, where the wisdom cow appears on the station!
-
Adds the fake virus event, where people get fake virus symptoms.
-
Adds the stray cargo pod event, where a cargo pod crashes into the station.
-
Adds the fugitives event, where fugitives are loose on the station, and it's the hunters jobs to capture them.
-
-
-
18 June 2020
-
Detective-Google updated:
-
-
cog is now less the suck
-
couple little derpy bits
-
malf disk and illegal tech disk moved from ashwalker base (guaranteed) to tendrils (chance based)
-
-
SmArtKar updated:
-
-
Ported shuttles from beestation
-
-
timothyteakettle updated:
-
-
embeds got reworked, sticky tape was added, more bullets that ricochet also added
-
medbots can now be tipped over
-
added more medbot sounds
-
-
-
17 June 2020
-
SmArtKar updated:
-
-
New ID icons
-
Sutures and Meshes
-
-
Trilbyspaceclone updated:
-
-
Gin export takes gin now
-
-
-
16 June 2020
-
Ghommie updated:
-
-
You can't blink nor use LOOC/AOOC as a petrified statue anymore.
-
-
Trilbyspaceclone updated:
-
-
BEPIS decal painter has been moved to venders, replacing it being the flashdark
-
-
kevinz000 updated:
-
-
projectile ricochets now use a less hilariously terrible way of being handled and should be easier to w
-
projectile runtime/ricocheting
-
Lobotomy no longer has a 50% chance of giving you a nigh-unremovable trauma, but does 50 brain damage even on success. On failure, it will give you a lobotomy-class trauma.
-
spinesnapping from tackling now only gives a lobotomy class trauma instead of magic.
-
-
timothyteakettle updated:
-
-
Food carts now function as intended, allowing the pouring and mixing of drinks.
-
slimes can now change their color using alter form
-
-
-
15 June 2020
-
Anturk, kevinz000 updated:
-
-
VV now properly allows access to datums in associative lists
-
SDQL2 printout has been upgraded for the 5th time.
Improvised Energy Gun. Fires 5 shots of 10 burn damage each. Can be upgraded with a lens made from glassworking and T4 parts for a minor buff.
-
Ammo + gun part loot spawners for mappers.
-
New sprites for Improvised Rifle, the ability to sling the rifle and a sprite for that.
-
New sprites for the Improvised Shotgun and its sling sprite.
-
Improvised shotguns are now two-handed only.
-
Improvised shotguns now only have a much less harsh 0.9* modifier, keeping them two hits to crit with slugs and buckshot. It can no longer be dual-wielded but can still be sawn off for w_class medium (can fit in backpacks).
-
Missing handsaw icons added in.
-
Crafting table cleaned up into sections.
-
-
Ghommie updated:
-
-
changed the weak attack message prefix from "inefficiently" to "limply", "feebly" and "saplessly" and lowered the threshold.
-
Fixing old beserker hardsuits having the wrong helmet type.
-
-
The0bserver and Stewydeadmike updated:
-
-
Hey, there's a bit of dust in this recipe book! Recipes using peas? How many recipes does this book even have?
-
Adds 6 new food items, and 1 new consumable reagent using all forms of the recently discovered peas. Ask your local botanist and chef for them today!
-
-
YakumoChen updated:
-
-
Adds polychrome options to loadout
-
Adds risque polychrome options to Kinkmate vendors
-
-
kevinz000 updated:
-
-
emissive blockers can no longer be radioactive.
-
Ghosts can now scan air inside most objects that contain air by clicking on them.
-
Directional blocking has been added, keybound to G. This will reduce a portion of incoming damage if done with eligible items at the cost of stamina damage incurred to the user based on damage blocked as well as while active, as well as in most cases preventing the user from doing any attacks while this is active.
-
Parrying has been added, keybound to F. Timing-based counterattacks, effect heavily dependent on the item, WIP.
-
Disks are now smaller.
-
-
-
12 June 2020
-
EmeraldSundisk updated:
-
-
The Detective's Office has been commandeered in order to serve a Head of Security. Detectives will find their new office within starboard maintenance.
-
Adds the Head of Security's standard equipment to their new office.
-
Slight adjustment/expansion to the main security department
-
Additional maintenance work
-
-
timothyteakettle updated:
-
-
Crabs, cockroaches, slimes and crabs are now small enough to fit in pet carriers
-
-
-
11 June 2020
-
Ghommie updated:
-
-
Balanced vending machine prices to be generally more affordable, a minority has been priced up though.
-
-
-
10 June 2020
-
DeltaFire15 updated:
-
-
Golems / simillar now inherit the neutered antag datum if the creator is neutered.
-
-
Ghommie updated:
-
-
Fixing missing pill type buttons from the chem master UI.
-
-
Naksu updated:
-
-
Lighting corner updates are ever so slightly faster.
-
-
Putnam for helping me code the contamination clearing on people updated:
-
-
Lab made Zeolites have been remade anew and more affective now that they refined the best possable way to mix and make a supper Zeolite capable of clearing contamination form not only people but items!
-
-
Trilbyspaceclone updated:
-
-
Tank Dispender has been moved into toxin storage from toxins
-
-
kevinz000 updated:
-
-
traitor classes can now be poplocked. hijack/glorious death are now locked to 25/20 respectively.
-
-
timothyteakettle updated:
-
-
adds a new fermichem, used for creating sentient plushies!
-
Mice can now breed using cheese wedges
-
Royal cheese can be crafted to convert a mouse into king rat
-
-
-
09 June 2020
-
Anonymous updated:
-
-
Added Orville-inspired clothing as a worthy alternative to Trek stuff.
-
Adds chaplain role allowance to the TMP Service Uniform loadout.
-
Adds paramedic in every medsci mentioned uniform loadout.
-
-
DeltaFire15 updated:
-
-
Offstation AIs can once again only interact with their z-level
-
-
Ghommie updated:
-
-
Fixing IC material containers interaction with stacks, for real.
-
-
Trilbyspaceclone updated:
-
-
Gasses like BZ and Masiam seem to just sell for less in cargo, markets seem to change it seems
-
-
kevinz000 updated:
-
-
plantpeople should stop dying in the halls now
-
Modifier-independent hotkey bindings have been added.
-
-
-
08 June 2020
-
DeltaFire15 updated:
-
-
Delinging now properly removes their special role
-
Keycard auth devices now require two seperate IDs with sufficient access to auth.
-
-
Linzolle updated:
-
-
shotguns no longer delete chambered shells while firing
-
-
kevinz000 updated:
-
-
test
-
-
shellspeed1 updated:
-
-
Internal tanks are now printable at the engineering lathe.
-
-
timothyteakettle updated:
-
-
newly created areas using blueprints now maintain the previous areas noteleport value
-
kudzu seeds now actually spawn vines
-
GoonStation 13 Development Team
diff --git a/html/changelogs/.all_changelog.yml b/html/changelogs/.all_changelog.yml
index 02aa9cab74..d4243a01ec 100644
--- a/html/changelogs/.all_changelog.yml
+++ b/html/changelogs/.all_changelog.yml
@@ -26770,3 +26770,293 @@ DO NOT EDIT THIS FILE BY HAND! AUTOMATICALLY GENERATED BY ss13_genchangelog.py.
instead of cutting it off and discarding the overflow.
- rscadd: holoparasites can now play music
- balance: lethal blood now causes damaging bleeding instead of outright gibbing
+2020-08-08:
+ DeltaFire15:
+ - balance: Roundstart cultists now start with a replica fabricator - no brass though,
+ make your own.
+ - balance: 'Kindle cast time: 10 > 15, mute after stun end: 2 > 5, slur after mute
+ end: 3 > 5'
+ - bugfix: The ratvarian spear no longer adds negative vitality under very specific
+ circumstances.
+ - balance: The Ratvarian Spear can parry now! Short parries with low leeway, but
+ low cooldown.
+ - rscadd: The brass claw, a implant-based weapon which gains combo on consecutive
+ hits against the same target.
+ - rscadd: The sigil of rites, a sigil used to perform various rites with a cost
+ of power and materials
+ - rscadd: 'The Rite of Advancement: Used to add a organ or cyberimplant to a clockie
+ without need for surgery.'
+ - rscadd: 'The Rite of Woundmending: Used to heal all wounds on another cultist,
+ causing toxins damage in return.'
+ - rscadd: 'The Rite of the Claw: Used to summon a brass claw implant. Maximum of
+ 4 uses per round.'
+ Hatterhat:
+ - rscadd: You can now buy a toolbox's worth of Mosin-Nagant ammo for a fairly discounted
+ price.
+ - rscadd: Revolvers from the dedicated kit now have reskinning capabilities.
+ - bugfix: You can now actually buy the riflery primer, which lets you pump shotguns
+ and work the Mosin's bolt faster.
+ - imageadd: Bulldog slug magazines now have a unique sprite.
+ Ludox235:
+ - rscdel: Removed an abductee objective that told you to remove all oxygen.
+ - rscadd: Added a new abductee objective to replace the removed one.
+ Sishen1542:
+ - tweak: "\U0001F171\uFE0Foneless"
+ - rscadd: squishy slime emotes
+ timothyteakettle:
+ - tweak: heparin makes you bleed half as much now
+ - tweak: cuts make you bleed 25% less now
+ - rscadd: more items in the loadout and loadout has subcategories now for easier
+ searching
+2020-08-09:
+ Hatterhat:
+ - rscadd: Proto-kinetic glaives (not crushers) can parry now.
+ MrJWhit:
+ - rscadd: Adds a second shutter on the top of the hop line
+ silicons:
+ - bugfix: immovable rods no longer drop down chasms
+ - rscdel: 'fun removal: squeaking objects now have an 1 second cooldown between
+ squeaks, and will have a 33% chance of interrupting any other squeaking object
+ when Cross()ing, meaning no more ear-fuck conveyor belts.'
+2020-08-10:
+ Hatterhat:
+ - bugfix: Parry counterattack text now shows up.
+ - balance: Sterilized gauze is now better at stopping bleeding, and applies slightly
+ faster. Very slightly faster.
+ - balance: Ointment and sutures now hold more in a stack (12 and 15, respectively).
+ - tweak: Sterilized gauze can now be made by just pouring 10u sterilizine onto standard
+ medical gauze, instead of having to craft it. Why you had to craft it, I will
+ honestly never know.
+ - balance: Proto-kinetic glaives are more expensive, stagger/cooldown on failed
+ parries increased slightly, perfect parries required for counterattack.
+ - rscadd: 'New item: Temporal Katana. 2 points for wizards, timestops upon successful
+ parry, bokken quickparry stats (100 force on melee counter!).'
+ - rscadd: Also you can *smirk. This has no mechanical effect, other than being smug.
+ KeRSedChaplain:
+ - rscadd: Added a guide for romerol usage
+ - balance: made infectious zombies not enter softcrit and take no stamina damage
+ LetterN:
+ - tweak: clocktheme color
+ - code_imp: Ports TGUI-4
+ Lynxless:
+ - rscadd: 'Ports TG #51879'
+ Owai-Seek:
+ - tweak: Meatballs now spawn raw from food processors.
+ Putnam3145:
+ - rscadd: Ethereals
+ - rscdel: (Hexa)crocin
+ - rscdel: (Hexa)camphor
+ - tweak: Tweaked wording for marking tickets IC issue.
+ - tweak: Rerolling your traitor goals will ONLY give you "proper" objectives.
+ Seris02:
+ - bugfix: borgs being able to select and use a module when it's too damaged
+ Sishen1542:
+ - balance: gave chairs active block/parry in exchange for removal of block_chance
+ - tweak: replaces box whiteship tbaton with truncheon
+ kappa-sama:
+ - balance: made the Dirty Magazines crate cost 4000 instead of 12000 credits
+ - rscadd: MODS I SPILLED MU JUICE HEJPPHRLP HELPJ JLEP HELP
+ silicons:
+ - balance: player made areas are no longer valid for malf hacking
+ - tweak: default space levels is 4 again.
+ - tweak: rats now swarm instead of stacking on one spot.
+ - balance: getting hit by an explosion will now barely hard knockdown, but will
+ leave you somewhat winded.
+ timothyteakettle:
+ - tweak: speech verbs copy through dna copying now
+2020-08-11:
+ Hatterhat:
+ - bugfix: PDA uplinks can now steal from pens. Properly. Just make sure to have
+ a pen in your PDA, first.
+ kappa-sama:
+ - tweak: tracer no longer gives you full stamheals per use
+ zeroisthebiggay:
+ - rscadd: volaju two
+2020-08-12:
+ DeltaFire15:
+ - balance: 'hellgun single-pack classification: goodies -> armory'
+ Detective-Google:
+ - bugfix: hallway table hallway table
+ Hatterhat:
+ - balance: The temporal katana is now slightly more worthy of the 2 spell point
+ cost, with a smaller, antimagic respecting timestop, less force, and no random
+ blockchance. Society has progressed past the need for blockchance.
+ LetterN:
+ - rscadd: Mafia Component
+ - bugfix: Fixed missing icons and handtele
+ Putnam3145:
+ - bugfix: a whole lot of jank regarding funny part sprite display.
+ Toriate:
+ - rscadd: Opossums have migrated into the maintenance tunnels! Seek them out at
+ your own peril!
+ ancientpower:
+ - tweak: Doors added to the west side of box medbay to make things a bit more manageable.
+ kappa-sama:
+ - tweak: smuggler satchel cost 2->1
+ - tweak: radio jammer cost 5->2
+ - bugfix: smuggler satchel uplink description now implies that persistence is disabled
+ lolman360:
+ - rscadd: renameable necklace (accessory, attaches to suit) and ring (glove slot.)
+ - tweak: custom rename is now 2048 characters? i think it's characters.
+ silicons:
+ - rscadd: You can now use anything as an emoji by doing :/obj/item/path/to/item:.
+ This works for any /atom or subtype.
+ timothyteakettle:
+ - rscadd: syndicate agents now have access to mechanical aim enhancers which allow
+ them to aim bullets to bounce off walls
+ - bugfix: ricochets work properly now for the bullets that support them
+ zeroisthebiggay:
+ - imageadd: hair and some sechuds
+ - balance: ce hardsuit radproofing
+2020-08-13:
+ LetterN:
+ - rscdel: Removes fermisleepers and reverts them to tg ones
+2020-08-14:
+ silicons:
+ - bugfix: abductors can buy things
+2020-08-15:
+ LetterN:
+ - bugfix: missing anomaly core icons
+ - bugfix: wrong state. blame the tg vertion i copied
+ silicons:
+ - tweak: the 8 rotation limit from clockwork chairs has been removed. please don't
+ abuse this.
+ - tweak: ethereals can now wear underwear
+2020-08-16:
+ kiwedespars:
+ - balance: nerfed hypereut chaplain weapon.
+ - balance: 50% rng blockchance -> 0%
+ - balance: parry made much worse because it's an actual weapon and a roundstart
+ one at that.
+ zeroisthebiggay:
+ - rscadd: tips
+ - rscdel: tips
+2020-08-17:
+ DeltaFire15:
+ - bugfix: Cogscarabs are no longer always Pogscarabs
+ Strazyplus:
+ - rscadd: Added drakeborgs
+ - rscadd: Added drakeplushies
+ - imageadd: added drakeborg sprites
+ - imageadd: added drakeplushie sprites
+ - code_imp: changed some code - added drakeplushies to backpack loadout Removed
+ duplicate voresleeper belly sprites from engdrake & jantidrake. [CC BY-NC-SA
+ 3.0](https://creativecommons.org/licenses/by-nc-sa/3.0/)
+ - rscadd: Added CC BY-NC-SA 3.0 license details to icon/mob/cyborg moved drakeborg.dmi
+ to icon/mob/cyborg
+2020-08-18:
+ DeltaFire15:
+ - balance: 'kindle cast time: 15ds -> 25ds'
+ - tweak: Moved the Belligerent Scripture to where it should be in the code
+ Detective-Google:
+ - rscadd: glass floors
+ - rscadd: uncrowbarrable plasma floors tweak:disco inferno's plasma floors can no
+ longer be crowbarred.
+ - rscadd: ghost cafe has funky fresh art
+ - bugfix: you can actually remove glass floors now
+ - code_imp: get_equipped_items is hopefully less gross
+ - balance: plasma cutters are no longer gay
+ Hatterhat:
+ - balance: Slaughter demons (and laughter demons, being a subtype) are MOB_SIZE_LARGE,
+ with one of the more immediate effects being able to mark them with a crusher
+ and backstab them.
+ - balance: The funny blyat men have stumbled upon another surplus of Mosin-Nagants
+ and are starting to pack them into crates again.
+ - balance: Vehicle riders can now, by default, get shot in the face and/or chest.
+ - rscadd: Adminspawn only .357 DumDum rounds! Because sometimes the other guy just
+ really needs to hurt.
+ - rscadd: Bluespace beakers now have a chemical window through the side that shows
+ chemical overlays.
+ - rscadd: Plant DNA manipulators now let you chuck things over them. Or they WOULD,
+ if LETPASSTHROW worked half a damn.
+ LetterN:
+ - bugfix: uplink implant states
+ - tweak: tweaks how role assigning works
+ MrJWhit:
+ - rscadd: Gives ashwalkers nightvision
+ - balance: Makes tesla blast people, not the environment, to save the server.
+ TheObserver-sys:
+ - bugfix: moves Garlic sprites from growing.dmi to growing_vegetable.dmi
+ - rscdel: Removes the unused Electric Lime mutation, it just takes up space with
+ no actual function nor sprites.
+ - imageadd: Gives Catnip growing sprites
+ - imagedel: Removes redundant images in growing.dmi
+ kiwedespars:
+ - rscadd: 10 force to a fucking rubber cock.
+ lolman360:
+ - balance: shotgun stripper clip nerf. ammoboxes can now accept a load_delay that
+ happens when they attack a magazine, internal or external.
+ ported from tg:
+ - rscadd: bronze airlocks and windows can now be built
+ - balance: i also tweaked bronze flooring to be cheaper.
+ silicons:
+ - tweak: stamina draining projectiles without stamina for their primary damage type
+ now has their stamina damage taken into account for shield blocking, rather
+ than the block being done for free for that.
+ timothyteakettle:
+ - refactor: snowflake code tidyup
+ - refactor: snowflake code for mutant bodypart selection has been rewritten to be
+ ~14x shorter
+ - tweak: meat type and horns can now be selected by any species
+2020-08-20:
+ DeltaFire15:
+ - bugfix: The cooking oil damage formula is no longer scuffed.
+ - tweak: Changed the clockie help-link to lead to our own wiki.
+ Fikou:
+ - admin: admins can now do html in ahelps properly
+ Hatterhat:
+ - balance: Pirate threats are now announced as "business propositions", and their
+ arrivals are now also announced properly.
+ tiramisuapimancer:
+ - bugfix: Ethereal hair is now their body color instead of accidentally white
+2020-08-21:
+ LetterN:
+ - rscadd: Updates and adds some of the tips
+ Putnam3145:
+ - admin: added reftracking as a compile flag
+ SmArtKar:
+ - tweak: RSD limitation is now 500 tiles
+ - bugfix: Fixed broken RSD sprites
+ - config: Removed that shuttle limit
+ timothyteakettle:
+ - bugfix: two snouts can once again be chosen in customization
+ - bugfix: lizard snouts work again
+2020-08-22:
+ Time-Green (copypasta'd by lolman360):
+ - rscadd: plumbing
+ - rscadd: automatic hydro trays
+2020-08-23:
+ DeltaFire15:
+ - bugfix: silicons and clockies can now access APCs properly
+ EmeraldSundisk:
+ - rscadd: Medbay now has a smartfridge for organ storage
+ - rscadd: Slight enhancements to the station's electrical wiring layout
+ - rscadd: Very small library renovation
+ - bugfix: Exterior airlocks have been given proper air systems for safety's sake
+ Ghommie:
+ - bugfix: Stops shielded hardsuits from slowly turning the wearer into a big glowing
+ ball of stacked energy shield overlays.
+ - tweak: the shielding overlay is merely visual as result. Aim your clicks.
+ Ludox235:
+ - tweak: no more 10 pop xenos (25pop now)
+ MrJWhit:
+ - tweak: Increases the majority of airlocks by 1 tile.
+ - tweak: Minor adjustments to the TEG engine.
+ Putnam3145:
+ - balance: Simplemobs no longer count in dynamic.
+ - balance: '"Story" storyteller no longer starts at a ludicrously low threat, always.'
+ - balance: Blob threat now scales with coverage.
+ - tweak: One person with their pref on no longer overpowers 40 people who might
+ not even know there is one.
+ - bugfix: Negative-weight rulesets are no longer put into the list.
+ kiwedespars:
+ - tweak: removed durathread from armwraps recipe.
+ lolman360:
+ - rscadd: breath mask balaclava
+ timothyteakettle:
+ - bugfix: lizards are now a recommended species for mam snouts
+ zeroisthebiggay:
+ - rscadd: new sprites for the temporal katana
+ - rscadd: suiciding with the temporal katana omae wa mou shinderius you into the
+ shadow realm
+ - soundadd: twilight isnt earrape
diff --git a/html/changelogs/AutoChangeLog-pr-12960.yml b/html/changelogs/AutoChangeLog-pr-12960.yml
deleted file mode 100644
index e37ac7eff0..0000000000
--- a/html/changelogs/AutoChangeLog-pr-12960.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: "timothyteakettle"
-delete-after: True
-changes:
- - rscadd: "more items in the loadout and loadout has subcategories now for easier searching"
diff --git a/html/changelogs/AutoChangeLog-pr-12992.yml b/html/changelogs/AutoChangeLog-pr-12992.yml
deleted file mode 100644
index 41edfb6414..0000000000
--- a/html/changelogs/AutoChangeLog-pr-12992.yml
+++ /dev/null
@@ -1,7 +0,0 @@
-author: "Hatterhat"
-delete-after: True
-changes:
- - rscadd: "You can now buy a toolbox's worth of Mosin-Nagant ammo for a fairly discounted price."
- - rscadd: "Revolvers from the dedicated kit now have reskinning capabilities."
- - bugfix: "You can now actually buy the riflery primer, which lets you pump shotguns and work the Mosin's bolt faster."
- - imageadd: "Bulldog slug magazines now have a unique sprite."
diff --git a/html/changelogs/AutoChangeLog-pr-13013.yml b/html/changelogs/AutoChangeLog-pr-13013.yml
deleted file mode 100644
index 8e0b953ade..0000000000
--- a/html/changelogs/AutoChangeLog-pr-13013.yml
+++ /dev/null
@@ -1,12 +0,0 @@
-author: "DeltaFire15"
-delete-after: True
-changes:
- - balance: "Roundstart cultists now start with a replica fabricator - no brass though, make your own."
- - balance: "Kindle cast time: 10 > 15, mute after stun end: 2 > 5, slur after mute end: 3 > 5"
- - bugfix: "The ratvarian spear no longer adds negative vitality under very specific circumstances."
- - balance: "The Ratvarian Spear can parry now! Short parries with low leeway, but low cooldown."
- - rscadd: "The brass claw, a implant-based weapon which gains combo on consecutive hits against the same target."
- - rscadd: "The sigil of rites, a sigil used to perform various rites with a cost of power and materials"
- - rscadd: "The Rite of Advancement: Used to add a organ or cyberimplant to a clockie without need for surgery."
- - rscadd: "The Rite of Woundmending: Used to heal all wounds on another cultist, causing toxins damage in return."
- - rscadd: "The Rite of the Claw: Used to summon a brass claw implant. Maximum of 4 uses per round."
diff --git a/html/changelogs/AutoChangeLog-pr-13038.yml b/html/changelogs/AutoChangeLog-pr-13038.yml
deleted file mode 100644
index 442bd37677..0000000000
--- a/html/changelogs/AutoChangeLog-pr-13038.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: "Sishen1542"
-delete-after: True
-changes:
- - rscadd: "squishy slime emotes"
diff --git a/html/changelogs/AutoChangeLog-pr-13039.yml b/html/changelogs/AutoChangeLog-pr-13039.yml
deleted file mode 100644
index de8ea10f35..0000000000
--- a/html/changelogs/AutoChangeLog-pr-13039.yml
+++ /dev/null
@@ -1,5 +0,0 @@
-author: "timothyteakettle"
-delete-after: True
-changes:
- - tweak: "heparin makes you bleed half as much now"
- - tweak: "cuts make you bleed 25% less now"
diff --git a/html/changelogs/AutoChangeLog-pr-13041.yml b/html/changelogs/AutoChangeLog-pr-13041.yml
deleted file mode 100644
index 1ae84967a8..0000000000
--- a/html/changelogs/AutoChangeLog-pr-13041.yml
+++ /dev/null
@@ -1,5 +0,0 @@
-author: "Ludox235"
-delete-after: True
-changes:
- - rscdel: "Removed an abductee objective that told you to remove all oxygen."
- - rscadd: "Added a new abductee objective to replace the removed one."
diff --git a/html/changelogs/AutoChangeLog-pr-13043.yml b/html/changelogs/AutoChangeLog-pr-13043.yml
deleted file mode 100644
index 5a9f2ca46f..0000000000
--- a/html/changelogs/AutoChangeLog-pr-13043.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: "Sishen1542"
-delete-after: True
-changes:
- - tweak: "🅱ï¸oneless"
diff --git a/icons/effects/160x160.dmi b/icons/effects/160x160.dmi
index 0a97573a9b..2adedb6c03 100644
Binary files a/icons/effects/160x160.dmi and b/icons/effects/160x160.dmi differ
diff --git a/icons/effects/96x96.dmi b/icons/effects/96x96.dmi
index b60ff97b2b..34f4adf6ce 100644
Binary files a/icons/effects/96x96.dmi and b/icons/effects/96x96.dmi differ
diff --git a/icons/effects/beam.dmi b/icons/effects/beam.dmi
index 0c4784553c..e5bff44ac4 100644
Binary files a/icons/effects/beam.dmi and b/icons/effects/beam.dmi differ
diff --git a/icons/effects/beam_splash.dmi b/icons/effects/beam_splash.dmi
new file mode 100644
index 0000000000..d7deb3e927
Binary files /dev/null and b/icons/effects/beam_splash.dmi differ
diff --git a/icons/effects/effects.dmi b/icons/effects/effects.dmi
index a2fce4678f..52164e1d33 100644
Binary files a/icons/effects/effects.dmi and b/icons/effects/effects.dmi differ
diff --git a/icons/effects/eldritch.dmi b/icons/effects/eldritch.dmi
new file mode 100644
index 0000000000..82549dccf0
Binary files /dev/null and b/icons/effects/eldritch.dmi differ
diff --git a/icons/effects/mouse_pointers/barn_target.dmi b/icons/effects/mouse_pointers/barn_target.dmi
new file mode 100644
index 0000000000..475a2b88b1
Binary files /dev/null and b/icons/effects/mouse_pointers/barn_target.dmi differ
diff --git a/icons/effects/mouse_pointers/blind_target.dmi b/icons/effects/mouse_pointers/blind_target.dmi
new file mode 100644
index 0000000000..1d33fc7a4c
Binary files /dev/null and b/icons/effects/mouse_pointers/blind_target.dmi differ
diff --git a/icons/effects/mouse_pointers/cult_target.dmi b/icons/effects/mouse_pointers/cult_target.dmi
new file mode 100644
index 0000000000..650feb3361
Binary files /dev/null and b/icons/effects/mouse_pointers/cult_target.dmi differ
diff --git a/icons/effects/mouse_pointers/mecha_mouse-disable.dmi b/icons/effects/mouse_pointers/mecha_mouse-disable.dmi
new file mode 100644
index 0000000000..48924c58c2
Binary files /dev/null and b/icons/effects/mouse_pointers/mecha_mouse-disable.dmi differ
diff --git a/icons/effects/mouse_pointers/mecha_mouse.dmi b/icons/effects/mouse_pointers/mecha_mouse.dmi
new file mode 100644
index 0000000000..4b46a44684
Binary files /dev/null and b/icons/effects/mouse_pointers/mecha_mouse.dmi differ
diff --git a/icons/effects/mouse_pointers/mindswap_target.dmi b/icons/effects/mouse_pointers/mindswap_target.dmi
new file mode 100644
index 0000000000..32ccda154d
Binary files /dev/null and b/icons/effects/mouse_pointers/mindswap_target.dmi differ
diff --git a/icons/effects/mouse_pointers/overload_machine_target.dmi b/icons/effects/mouse_pointers/overload_machine_target.dmi
new file mode 100644
index 0000000000..8bc67cdab6
Binary files /dev/null and b/icons/effects/mouse_pointers/overload_machine_target.dmi differ
diff --git a/icons/effects/mouse_pointers/override_machine_target.dmi b/icons/effects/mouse_pointers/override_machine_target.dmi
new file mode 100644
index 0000000000..77dbb4ba32
Binary files /dev/null and b/icons/effects/mouse_pointers/override_machine_target.dmi differ
diff --git a/icons/effects/supplypod_down_target.dmi b/icons/effects/mouse_pointers/supplypod_down_target.dmi
similarity index 100%
rename from icons/effects/supplypod_down_target.dmi
rename to icons/effects/mouse_pointers/supplypod_down_target.dmi
diff --git a/icons/effects/mouse_pointers/supplypod_pickturf.dmi b/icons/effects/mouse_pointers/supplypod_pickturf.dmi
new file mode 100644
index 0000000000..3ca1131e1a
Binary files /dev/null and b/icons/effects/mouse_pointers/supplypod_pickturf.dmi differ
diff --git a/icons/effects/mouse_pointers/supplypod_pickturf_down.dmi b/icons/effects/mouse_pointers/supplypod_pickturf_down.dmi
new file mode 100644
index 0000000000..113fe47540
Binary files /dev/null and b/icons/effects/mouse_pointers/supplypod_pickturf_down.dmi differ
diff --git a/icons/effects/supplypod_target.dmi b/icons/effects/mouse_pointers/supplypod_target.dmi
similarity index 100%
rename from icons/effects/supplypod_target.dmi
rename to icons/effects/mouse_pointers/supplypod_target.dmi
diff --git a/icons/effects/mouse_pointers/throw_target.dmi b/icons/effects/mouse_pointers/throw_target.dmi
new file mode 100644
index 0000000000..660eafbf2b
Binary files /dev/null and b/icons/effects/mouse_pointers/throw_target.dmi differ
diff --git a/icons/effects/mouse_pointers/wrap_target.dmi b/icons/effects/mouse_pointers/wrap_target.dmi
new file mode 100644
index 0000000000..2e9a338c9e
Binary files /dev/null and b/icons/effects/mouse_pointers/wrap_target.dmi differ
diff --git a/icons/misc/language.dmi b/icons/misc/language.dmi
index 155dbab98d..9501dc9216 100644
Binary files a/icons/misc/language.dmi and b/icons/misc/language.dmi differ
diff --git a/icons/mob/actions/actions_ecult.dmi b/icons/mob/actions/actions_ecult.dmi
new file mode 100644
index 0000000000..0a130f006e
Binary files /dev/null and b/icons/mob/actions/actions_ecult.dmi differ
diff --git a/icons/mob/actions/backgrounds.dmi b/icons/mob/actions/backgrounds.dmi
index 07839588ce..6b983df95a 100644
Binary files a/icons/mob/actions/backgrounds.dmi and b/icons/mob/actions/backgrounds.dmi differ
diff --git a/icons/mob/animal.dmi b/icons/mob/animal.dmi
index 13f97d3761..50bf65b27f 100644
Binary files a/icons/mob/animal.dmi and b/icons/mob/animal.dmi differ
diff --git a/icons/mob/clothing/back.dmi b/icons/mob/clothing/back.dmi
index 9490595484..8594af8ec2 100644
Binary files a/icons/mob/clothing/back.dmi and b/icons/mob/clothing/back.dmi differ
diff --git a/icons/mob/clothing/belt.dmi b/icons/mob/clothing/belt.dmi
index 52ef75020c..4ac82ca299 100644
Binary files a/icons/mob/clothing/belt.dmi and b/icons/mob/clothing/belt.dmi differ
diff --git a/icons/mob/clothing/eyes.dmi b/icons/mob/clothing/eyes.dmi
index cd2b84a143..876159c258 100644
Binary files a/icons/mob/clothing/eyes.dmi and b/icons/mob/clothing/eyes.dmi differ
diff --git a/icons/mob/clothing/head.dmi b/icons/mob/clothing/head.dmi
index 0ad9452994..16571a4aa1 100644
Binary files a/icons/mob/clothing/head.dmi and b/icons/mob/clothing/head.dmi differ
diff --git a/icons/mob/clothing/neck.dmi b/icons/mob/clothing/neck.dmi
index 86dec62a41..7bfecb4158 100644
Binary files a/icons/mob/clothing/neck.dmi and b/icons/mob/clothing/neck.dmi differ
diff --git a/icons/mob/clothing/suit.dmi b/icons/mob/clothing/suit.dmi
index c94e8a46d3..08d276a484 100644
Binary files a/icons/mob/clothing/suit.dmi and b/icons/mob/clothing/suit.dmi differ
diff --git a/icons/mob/clothing/underwear.dmi b/icons/mob/clothing/underwear.dmi
index f0c2cde93d..8cf1144a68 100644
Binary files a/icons/mob/clothing/underwear.dmi and b/icons/mob/clothing/underwear.dmi differ
diff --git a/icons/mob/cyborg/Drakeborg-licensing.txt b/icons/mob/cyborg/Drakeborg-licensing.txt
new file mode 100644
index 0000000000..f2d3ca925c
--- /dev/null
+++ b/icons/mob/cyborg/Drakeborg-licensing.txt
@@ -0,0 +1,69 @@
+Drakeborg & drakeplushies are created by deviantart.com/mizartz
+
+https://creativecommons.org/licenses/by-nc-sa/3.0/
+Attribution-NonCommercial-ShareAlike 3.0 Unported
+
+CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM ITS USE.
+License
+THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED.
+
+BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND CONDITIONS.
+
+1. Definitions
+
+"Adaptation" means a work based upon the Work, or upon the Work and other pre-existing works, such as a translation, adaptation, derivative work, arrangement of music or other alterations of a literary or artistic work, or phonogram or performance and includes cinematographic adaptations or any other form in which the Work may be recast, transformed, or adapted including in any form recognizably derived from the original, except that a work that constitutes a Collection will not be considered an Adaptation for the purpose of this License. For the avoidance of doubt, where the Work is a musical work, performance or phonogram, the synchronization of the Work in timed-relation with a moving image ("synching") will be considered an Adaptation for the purpose of this License.
+"Collection" means a collection of literary or artistic works, such as encyclopedias and anthologies, or performances, phonograms or broadcasts, or other works or subject matter other than works listed in Section 1(g) below, which, by reason of the selection and arrangement of their contents, constitute intellectual creations, in which the Work is included in its entirety in unmodified form along with one or more other contributions, each constituting separate and independent works in themselves, which together are assembled into a collective whole. A work that constitutes a Collection will not be considered an Adaptation (as defined above) for the purposes of this License.
+"Distribute" means to make available to the public the original and copies of the Work or Adaptation, as appropriate, through sale or other transfer of ownership.
+"License Elements" means the following high-level license attributes as selected by Licensor and indicated in the title of this License: Attribution, Noncommercial, ShareAlike.
+"Licensor" means the individual, individuals, entity or entities that offer(s) the Work under the terms of this License.
+"Original Author" means, in the case of a literary or artistic work, the individual, individuals, entity or entities who created the Work or if no individual or entity can be identified, the publisher; and in addition (i) in the case of a performance the actors, singers, musicians, dancers, and other persons who act, sing, deliver, declaim, play in, interpret or otherwise perform literary or artistic works or expressions of folklore; (ii) in the case of a phonogram the producer being the person or legal entity who first fixes the sounds of a performance or other sounds; and, (iii) in the case of broadcasts, the organization that transmits the broadcast.
+"Work" means the literary and/or artistic work offered under the terms of this License including without limitation any production in the literary, scientific and artistic domain, whatever may be the mode or form of its expression including digital form, such as a book, pamphlet and other writing; a lecture, address, sermon or other work of the same nature; a dramatic or dramatico-musical work; a choreographic work or entertainment in dumb show; a musical composition with or without words; a cinematographic work to which are assimilated works expressed by a process analogous to cinematography; a work of drawing, painting, architecture, sculpture, engraving or lithography; a photographic work to which are assimilated works expressed by a process analogous to photography; a work of applied art; an illustration, map, plan, sketch or three-dimensional work relative to geography, topography, architecture or science; a performance; a broadcast; a phonogram; a compilation of data to the extent it is protected as a copyrightable work; or a work performed by a variety or circus performer to the extent it is not otherwise considered a literary or artistic work.
+"You" means an individual or entity exercising rights under this License who has not previously violated the terms of this License with respect to the Work, or who has received express permission from the Licensor to exercise rights under this License despite a previous violation.
+"Publicly Perform" means to perform public recitations of the Work and to communicate to the public those public recitations, by any means or process, including by wire or wireless means or public digital performances; to make available to the public Works in such a way that members of the public may access these Works from a place and at a place individually chosen by them; to perform the Work to the public by any means or process and the communication to the public of the performances of the Work, including by public digital performance; to broadcast and rebroadcast the Work by any means including signs, sounds or images.
+"Reproduce" means to make copies of the Work by any means including without limitation by sound or visual recordings and the right of fixation and reproducing fixations of the Work, including storage of a protected performance or phonogram in digital form or other electronic medium.
+2. Fair Dealing Rights. Nothing in this License is intended to reduce, limit, or restrict any uses free from copyright or rights arising from limitations or exceptions that are provided for in connection with the copyright protection under copyright law or other applicable laws.
+
+3. License Grant. Subject to the terms and conditions of this License, Licensor hereby grants You a worldwide, royalty-free, non-exclusive, perpetual (for the duration of the applicable copyright) license to exercise the rights in the Work as stated below:
+
+to Reproduce the Work, to incorporate the Work into one or more Collections, and to Reproduce the Work as incorporated in the Collections;
+to create and Reproduce Adaptations provided that any such Adaptation, including any translation in any medium, takes reasonable steps to clearly label, demarcate or otherwise identify that changes were made to the original Work. For example, a translation could be marked "The original work was translated from English to Spanish," or a modification could indicate "The original work has been modified.";
+to Distribute and Publicly Perform the Work including as incorporated in Collections; and,
+to Distribute and Publicly Perform Adaptations.
+The above rights may be exercised in all media and formats whether now known or hereafter devised. The above rights include the right to make such modifications as are technically necessary to exercise the rights in other media and formats. Subject to Section 8(f), all rights not expressly granted by Licensor are hereby reserved, including but not limited to the rights described in Section 4(e).
+
+4. Restrictions. The license granted in Section 3 above is expressly made subject to and limited by the following restrictions:
+
+You may Distribute or Publicly Perform the Work only under the terms of this License. You must include a copy of, or the Uniform Resource Identifier (URI) for, this License with every copy of the Work You Distribute or Publicly Perform. You may not offer or impose any terms on the Work that restrict the terms of this License or the ability of the recipient of the Work to exercise the rights granted to that recipient under the terms of the License. You may not sublicense the Work. You must keep intact all notices that refer to this License and to the disclaimer of warranties with every copy of the Work You Distribute or Publicly Perform. When You Distribute or Publicly Perform the Work, You may not impose any effective technological measures on the Work that restrict the ability of a recipient of the Work from You to exercise the rights granted to that recipient under the terms of the License. This Section 4(a) applies to the Work as incorporated in a Collection, but this does not require the Collection apart from the Work itself to be made subject to the terms of this License. If You create a Collection, upon notice from any Licensor You must, to the extent practicable, remove from the Collection any credit as required by Section 4(d), as requested. If You create an Adaptation, upon notice from any Licensor You must, to the extent practicable, remove from the Adaptation any credit as required by Section 4(d), as requested.
+You may Distribute or Publicly Perform an Adaptation only under: (i) the terms of this License; (ii) a later version of this License with the same License Elements as this License; (iii) a Creative Commons jurisdiction license (either this or a later license version) that contains the same License Elements as this License (e.g., Attribution-NonCommercial-ShareAlike 3.0 US) ("Applicable License"). You must include a copy of, or the URI, for Applicable License with every copy of each Adaptation You Distribute or Publicly Perform. You may not offer or impose any terms on the Adaptation that restrict the terms of the Applicable License or the ability of the recipient of the Adaptation to exercise the rights granted to that recipient under the terms of the Applicable License. You must keep intact all notices that refer to the Applicable License and to the disclaimer of warranties with every copy of the Work as included in the Adaptation You Distribute or Publicly Perform. When You Distribute or Publicly Perform the Adaptation, You may not impose any effective technological measures on the Adaptation that restrict the ability of a recipient of the Adaptation from You to exercise the rights granted to that recipient under the terms of the Applicable License. This Section 4(b) applies to the Adaptation as incorporated in a Collection, but this does not require the Collection apart from the Adaptation itself to be made subject to the terms of the Applicable License.
+You may not exercise any of the rights granted to You in Section 3 above in any manner that is primarily intended for or directed toward commercial advantage or private monetary compensation. The exchange of the Work for other copyrighted works by means of digital file-sharing or otherwise shall not be considered to be intended for or directed toward commercial advantage or private monetary compensation, provided there is no payment of any monetary compensation in con-nection with the exchange of copyrighted works.
+If You Distribute, or Publicly Perform the Work or any Adaptations or Collections, You must, unless a request has been made pursuant to Section 4(a), keep intact all copyright notices for the Work and provide, reasonable to the medium or means You are utilizing: (i) the name of the Original Author (or pseudonym, if applicable) if supplied, and/or if the Original Author and/or Licensor designate another party or parties (e.g., a sponsor institute, publishing entity, journal) for attribution ("Attribution Parties") in Licensor's copyright notice, terms of service or by other reasonable means, the name of such party or parties; (ii) the title of the Work if supplied; (iii) to the extent reasonably practicable, the URI, if any, that Licensor specifies to be associated with the Work, unless such URI does not refer to the copyright notice or licensing information for the Work; and, (iv) consistent with Section 3(b), in the case of an Adaptation, a credit identifying the use of the Work in the Adaptation (e.g., "French translation of the Work by Original Author," or "Screenplay based on original Work by Original Author"). The credit required by this Section 4(d) may be implemented in any reasonable manner; provided, however, that in the case of a Adaptation or Collection, at a minimum such credit will appear, if a credit for all contributing authors of the Adaptation or Collection appears, then as part of these credits and in a manner at least as prominent as the credits for the other contributing authors. For the avoidance of doubt, You may only use the credit required by this Section for the purpose of attribution in the manner set out above and, by exercising Your rights under this License, You may not implicitly or explicitly assert or imply any connection with, sponsorship or endorsement by the Original Author, Licensor and/or Attribution Parties, as appropriate, of You or Your use of the Work, without the separate, express prior written permission of the Original Author, Licensor and/or Attribution Parties.
+For the avoidance of doubt:
+
+Non-waivable Compulsory License Schemes. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme cannot be waived, the Licensor reserves the exclusive right to collect such royalties for any exercise by You of the rights granted under this License;
+Waivable Compulsory License Schemes. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme can be waived, the Licensor reserves the exclusive right to collect such royalties for any exercise by You of the rights granted under this License if Your exercise of such rights is for a purpose or use which is otherwise than noncommercial as permitted under Section 4(c) and otherwise waives the right to collect royalties through any statutory or compulsory licensing scheme; and,
+Voluntary License Schemes. The Licensor reserves the right to collect royalties, whether individually or, in the event that the Licensor is a member of a collecting society that administers voluntary licensing schemes, via that society, from any exercise by You of the rights granted under this License that is for a purpose or use which is otherwise than noncommercial as permitted under Section 4(c).
+Except as otherwise agreed in writing by the Licensor or as may be otherwise permitted by applicable law, if You Reproduce, Distribute or Publicly Perform the Work either by itself or as part of any Adaptations or Collections, You must not distort, mutilate, modify or take other derogatory action in relation to the Work which would be prejudicial to the Original Author's honor or reputation. Licensor agrees that in those jurisdictions (e.g. Japan), in which any exercise of the right granted in Section 3(b) of this License (the right to make Adaptations) would be deemed to be a distortion, mutilation, modification or other derogatory action prejudicial to the Original Author's honor and reputation, the Licensor will waive or not assert, as appropriate, this Section, to the fullest extent permitted by the applicable national law, to enable You to reasonably exercise Your right under Section 3(b) of this License (right to make Adaptations) but not otherwise.
+5. Representations, Warranties and Disclaimer
+
+UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING AND TO THE FULLEST EXTENT PERMITTED BY APPLICABLE LAW, LICENSOR OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF IMPLIED WARRANTIES, SO THIS EXCLUSION MAY NOT APPLY TO YOU.
+
+6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+
+7. Termination
+
+This License and the rights granted hereunder will terminate automatically upon any breach by You of the terms of this License. Individuals or entities who have received Adaptations or Collections from You under this License, however, will not have their licenses terminated provided such individuals or entities remain in full compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will survive any termination of this License.
+Subject to the above terms and conditions, the license granted here is perpetual (for the duration of the applicable copyright in the Work). Notwithstanding the above, Licensor reserves the right to release the Work under different license terms or to stop distributing the Work at any time; provided, however that any such election will not serve to withdraw this License (or any other license that has been, or is required to be, granted under the terms of this License), and this License will continue in full force and effect unless terminated as stated above.
+8. Miscellaneous
+
+Each time You Distribute or Publicly Perform the Work or a Collection, the Licensor offers to the recipient a license to the Work on the same terms and conditions as the license granted to You under this License.
+Each time You Distribute or Publicly Perform an Adaptation, Licensor offers to the recipient a license to the original Work on the same terms and conditions as the license granted to You under this License.
+If any provision of this License is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this License, and without further action by the parties to this agreement, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable.
+No term or provision of this License shall be deemed waived and no breach consented to unless such waiver or consent shall be in writing and signed by the party to be charged with such waiver or consent.
+This License constitutes the entire agreement between the parties with respect to the Work licensed here. There are no understandings, agreements or representations with respect to the Work not specified here. Licensor shall not be bound by any additional provisions that may appear in any communication from You. This License may not be modified without the mutual written agreement of the Licensor and You.
+The rights granted under, and the subject matter referenced, in this License were drafted utilizing the terminology of the Berne Convention for the Protection of Literary and Artistic Works (as amended on September 28, 1979), the Rome Convention of 1961, the WIPO Copyright Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996 and the Universal Copyright Convention (as revised on July 24, 1971). These rights and subject matter take effect in the relevant jurisdiction in which the License terms are sought to be enforced according to the corresponding provisions of the implementation of those treaty provisions in the applicable national law. If the standard suite of rights granted under applicable copyright law includes additional rights not granted under this License, such additional rights are deemed to be included in the License; this License is not intended to restrict the license of any rights under applicable law.
+Creative Commons Notice
+Creative Commons is not a party to this License, and makes no warranty whatsoever in connection with the Work. Creative Commons will not be liable to You or any party on any legal theory for any damages whatsoever, including without limitation any general, special, incidental or consequential damages arising in connection to this license. Notwithstanding the foregoing two (2) sentences, if Creative Commons has expressly identified itself as the Licensor hereunder, it shall have all rights and obligations of Licensor.
+
+Except for the limited purpose of indicating to the public that the Work is licensed under the CCPL, Creative Commons does not authorize the use by either party of the trademark "Creative Commons" or any related trademark or logo of Creative Commons without the prior written consent of Creative Commons. Any permitted use will be in compliance with Creative Commons' then-current trademark usage guidelines, as may be published on its website or otherwise made available upon request from time to time. For the avoidance of doubt, this trademark restriction does not form part of this License.
+
+Creative Commons may be contacted at https://creativecommons.org/.
\ No newline at end of file
diff --git a/icons/mob/cyborg/drakemech.dmi b/icons/mob/cyborg/drakemech.dmi
new file mode 100644
index 0000000000..6a4845d983
Binary files /dev/null and b/icons/mob/cyborg/drakemech.dmi differ
diff --git a/icons/mob/eldritch_mobs.dmi b/icons/mob/eldritch_mobs.dmi
new file mode 100644
index 0000000000..8a16d53f88
Binary files /dev/null and b/icons/mob/eldritch_mobs.dmi differ
diff --git a/icons/mob/hud.dmi b/icons/mob/hud.dmi
index c21fa47b9c..1d2007fd63 100644
Binary files a/icons/mob/hud.dmi and b/icons/mob/hud.dmi differ
diff --git a/icons/mob/human_face.dmi b/icons/mob/human_face.dmi
index a4e2a9d5b2..8055233ea7 100644
Binary files a/icons/mob/human_face.dmi and b/icons/mob/human_face.dmi differ
diff --git a/icons/mob/inhands/64x64_lefthand.dmi b/icons/mob/inhands/64x64_lefthand.dmi
index 6b47171066..6dc8d82753 100644
Binary files a/icons/mob/inhands/64x64_lefthand.dmi and b/icons/mob/inhands/64x64_lefthand.dmi differ
diff --git a/icons/mob/inhands/64x64_righthand.dmi b/icons/mob/inhands/64x64_righthand.dmi
index 3750e28906..ca87f74a6f 100644
Binary files a/icons/mob/inhands/64x64_righthand.dmi and b/icons/mob/inhands/64x64_righthand.dmi differ
diff --git a/icons/mob/inhands/weapons/swords_lefthand.dmi b/icons/mob/inhands/weapons/swords_lefthand.dmi
index 0ca36ad43d..23d80af9ef 100644
Binary files a/icons/mob/inhands/weapons/swords_lefthand.dmi and b/icons/mob/inhands/weapons/swords_lefthand.dmi differ
diff --git a/icons/mob/inhands/weapons/swords_righthand.dmi b/icons/mob/inhands/weapons/swords_righthand.dmi
index 59d7bbce69..702c0299b5 100644
Binary files a/icons/mob/inhands/weapons/swords_righthand.dmi and b/icons/mob/inhands/weapons/swords_righthand.dmi differ
diff --git a/icons/mob/mob.dmi b/icons/mob/mob.dmi
index de09fb1c63..23f0e2cc13 100644
Binary files a/icons/mob/mob.dmi and b/icons/mob/mob.dmi differ
diff --git a/icons/obj/assemblies/new_assemblies.dmi b/icons/obj/assemblies/new_assemblies.dmi
index 2283b84c43..32eda1eb49 100644
Binary files a/icons/obj/assemblies/new_assemblies.dmi and b/icons/obj/assemblies/new_assemblies.dmi differ
diff --git a/icons/obj/chemical.dmi b/icons/obj/chemical.dmi
index aea15e2e39..cd12f4a457 100644
Binary files a/icons/obj/chemical.dmi and b/icons/obj/chemical.dmi differ
diff --git a/icons/obj/clothing/accessories.dmi b/icons/obj/clothing/accessories.dmi
index c62a88c829..7d13e3f802 100644
Binary files a/icons/obj/clothing/accessories.dmi and b/icons/obj/clothing/accessories.dmi differ
diff --git a/icons/obj/clothing/glasses.dmi b/icons/obj/clothing/glasses.dmi
index e8ba88a12f..4fce479a9d 100644
Binary files a/icons/obj/clothing/glasses.dmi and b/icons/obj/clothing/glasses.dmi differ
diff --git a/icons/obj/clothing/hats.dmi b/icons/obj/clothing/hats.dmi
index 60304999fa..8fbb2abe1e 100644
Binary files a/icons/obj/clothing/hats.dmi and b/icons/obj/clothing/hats.dmi differ
diff --git a/icons/obj/clothing/suits.dmi b/icons/obj/clothing/suits.dmi
index e8a360bc87..3476b16258 100644
Binary files a/icons/obj/clothing/suits.dmi and b/icons/obj/clothing/suits.dmi differ
diff --git a/icons/obj/decals.dmi b/icons/obj/decals.dmi
index c280ee786f..7dac61a663 100644
Binary files a/icons/obj/decals.dmi and b/icons/obj/decals.dmi differ
diff --git a/icons/obj/eldritch.dmi b/icons/obj/eldritch.dmi
new file mode 100644
index 0000000000..50c2913708
Binary files /dev/null and b/icons/obj/eldritch.dmi differ
diff --git a/icons/obj/food/food.dmi b/icons/obj/food/food.dmi
index 28cdefd331..078cadfd60 100644
Binary files a/icons/obj/food/food.dmi and b/icons/obj/food/food.dmi differ
diff --git a/icons/obj/food/piecake.dmi b/icons/obj/food/piecake.dmi
index 5638235217..935f7e8ad5 100644
Binary files a/icons/obj/food/piecake.dmi and b/icons/obj/food/piecake.dmi differ
diff --git a/icons/obj/guns/projectile.dmi b/icons/obj/guns/projectile.dmi
index 9c6df36f49..00670916db 100644
Binary files a/icons/obj/guns/projectile.dmi and b/icons/obj/guns/projectile.dmi differ
diff --git a/icons/obj/hydroponics/growing.dmi b/icons/obj/hydroponics/growing.dmi
index 5415f47528..0866791ed6 100644
Binary files a/icons/obj/hydroponics/growing.dmi and b/icons/obj/hydroponics/growing.dmi differ
diff --git a/icons/obj/hydroponics/growing_vegetables.dmi b/icons/obj/hydroponics/growing_vegetables.dmi
index e174b4fb0b..ce0beb86ce 100644
Binary files a/icons/obj/hydroponics/growing_vegetables.dmi and b/icons/obj/hydroponics/growing_vegetables.dmi differ
diff --git a/icons/obj/items_and_weapons.dmi b/icons/obj/items_and_weapons.dmi
index 57105cc089..c5aca2394f 100644
Binary files a/icons/obj/items_and_weapons.dmi and b/icons/obj/items_and_weapons.dmi differ
diff --git a/icons/obj/janitor.dmi b/icons/obj/janitor.dmi
index 1f520a42bd..b240391328 100644
Binary files a/icons/obj/janitor.dmi and b/icons/obj/janitor.dmi differ
diff --git a/icons/obj/lavaland/terrain.dmi b/icons/obj/lavaland/terrain.dmi
new file mode 100644
index 0000000000..4db51145ee
Binary files /dev/null and b/icons/obj/lavaland/terrain.dmi differ
diff --git a/icons/obj/license.txt b/icons/obj/license.txt
new file mode 100644
index 0000000000..2e27ba80f2
--- /dev/null
+++ b/icons/obj/license.txt
@@ -0,0 +1,3 @@
+icons/obj/plushies.dmi's icon state of secdrake and meddrake by Mizartz. It has been licensed under the CC BY-NC-SA 3.0 license.
+
+CC BY-NC-SA 3.0 https://creativecommons.org/licenses/by-nc-sa/3.0/
\ No newline at end of file
diff --git a/icons/obj/machines/medipen_refiller.dmi b/icons/obj/machines/medipen_refiller.dmi
new file mode 100644
index 0000000000..300d218d2d
Binary files /dev/null and b/icons/obj/machines/medipen_refiller.dmi differ
diff --git a/icons/obj/machines/research.dmi b/icons/obj/machines/research.dmi
index 7d64c494fd..7dcd4e6bcb 100644
Binary files a/icons/obj/machines/research.dmi and b/icons/obj/machines/research.dmi differ
diff --git a/icons/obj/mafia.dmi b/icons/obj/mafia.dmi
new file mode 100644
index 0000000000..c44b80aba1
Binary files /dev/null and b/icons/obj/mafia.dmi differ
diff --git a/icons/obj/modular_console.dmi b/icons/obj/modular_console.dmi
index cdcf6f1bd5..5d3cd0312c 100644
Binary files a/icons/obj/modular_console.dmi and b/icons/obj/modular_console.dmi differ
diff --git a/icons/obj/modular_laptop.dmi b/icons/obj/modular_laptop.dmi
index 7cf5a77621..fd24d27a97 100644
Binary files a/icons/obj/modular_laptop.dmi and b/icons/obj/modular_laptop.dmi differ
diff --git a/icons/obj/modular_tablet.dmi b/icons/obj/modular_tablet.dmi
index 2f9a0559ae..32edb57475 100644
Binary files a/icons/obj/modular_tablet.dmi and b/icons/obj/modular_tablet.dmi differ
diff --git a/icons/obj/plumbing/fluid_ducts.dmi b/icons/obj/plumbing/fluid_ducts.dmi
new file mode 100644
index 0000000000..87d9d2233b
Binary files /dev/null and b/icons/obj/plumbing/fluid_ducts.dmi differ
diff --git a/icons/obj/plumbing/plumbers.dmi b/icons/obj/plumbing/plumbers.dmi
new file mode 100644
index 0000000000..242622e000
Binary files /dev/null and b/icons/obj/plumbing/plumbers.dmi differ
diff --git a/icons/obj/plushes.dmi b/icons/obj/plushes.dmi
index 11d02a46cc..ac0c338016 100644
Binary files a/icons/obj/plushes.dmi and b/icons/obj/plushes.dmi differ
diff --git a/icons/obj/projectiles.dmi b/icons/obj/projectiles.dmi
index 92e76f78bb..94568b8633 100644
Binary files a/icons/obj/projectiles.dmi and b/icons/obj/projectiles.dmi differ
diff --git a/icons/obj/reagentfillings.dmi b/icons/obj/reagentfillings.dmi
index d7a9a03c41..1ac941bae3 100644
Binary files a/icons/obj/reagentfillings.dmi and b/icons/obj/reagentfillings.dmi differ
diff --git a/icons/obj/watercloset.dmi b/icons/obj/watercloset.dmi
index 4a299f29dd..2b64176878 100644
Binary files a/icons/obj/watercloset.dmi and b/icons/obj/watercloset.dmi differ
diff --git a/icons/turf/floors.dmi b/icons/turf/floors.dmi
index 83dd4f5df8..f2b84bbe03 100644
Binary files a/icons/turf/floors.dmi and b/icons/turf/floors.dmi differ
diff --git a/icons/turf/floors/glass.dmi b/icons/turf/floors/glass.dmi
new file mode 100644
index 0000000000..adf6f57aaa
Binary files /dev/null and b/icons/turf/floors/glass.dmi differ
diff --git a/icons/turf/floors/reinf_glass.dmi b/icons/turf/floors/reinf_glass.dmi
new file mode 100644
index 0000000000..dda99cd07f
Binary files /dev/null and b/icons/turf/floors/reinf_glass.dmi differ
diff --git a/modular_citadel/code/datums/status_effects/chems.dm b/modular_citadel/code/datums/status_effects/chems.dm
index 4fa2a51fac..0e971d4ced 100644
--- a/modular_citadel/code/datums/status_effects/chems.dm
+++ b/modular_citadel/code/datums/status_effects/chems.dm
@@ -583,16 +583,6 @@
C.Stun(60)
to_chat(owner, "Your muscles seize up, then start spasming wildy!")
- //wah intensifies wah-rks
- else if (lowertext(customTriggers[trigger]) == "cum")//aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
- if (lewd)
- if(ishuman(C))
- var/mob/living/carbon/human/H = C
- H.mob_climax(forced_climax=TRUE)
- C.SetStun(10)//We got your stun effects in somewhere, Kev.
- else
- C.throw_at(get_step_towards(hearing_args[HEARING_SPEAKER],C), 3, 1) //cut this if it's too hard to get working
-
//kneel (knockdown)
else if (lowertext(customTriggers[trigger]) == "kneel")//as close to kneeling as you can get, I suppose.
to_chat(owner, "You drop to the ground unsurreptitiously.")
@@ -674,15 +664,6 @@
deltaResist *= 1.25
if (owner.reagents.has_reagent(/datum/reagent/medicine/neurine))
deltaResist *= 1.5
- if (!(owner.client?.prefs.cit_toggles & NO_APHRO) && lewd)
- if (owner.reagents.has_reagent(/datum/reagent/drug/anaphrodisiac))
- deltaResist *= 1.5
- if (owner.reagents.has_reagent(/datum/reagent/drug/anaphrodisiacplus))
- deltaResist *= 2
- if (owner.reagents.has_reagent(/datum/reagent/drug/aphrodisiac))
- deltaResist *= 0.75
- if (owner.reagents.has_reagent(/datum/reagent/drug/aphrodisiacplus))
- deltaResist *= 0.5
//Antag resistance
//cultists are already brainwashed by their god
if(iscultist(owner))
diff --git a/modular_citadel/code/modules/client/loadout/__donator.dm b/modular_citadel/code/modules/client/loadout/__donator.dm
index 54dabba779..378c70d187 100644
--- a/modular_citadel/code/modules/client/loadout/__donator.dm
+++ b/modular_citadel/code/modules/client/loadout/__donator.dm
@@ -4,7 +4,7 @@
name = "IF YOU SEE THIS, PING A CODER RIGHT NOW!"
slot = SLOT_IN_BACKPACK
path = /obj/item/bikehorn/golden
- category = CATEGORY_DONATOR
+ category = LOADOUT_CATEGORY_DONATOR
ckeywhitelist = list("This entry should never appear with this variable set.") //If it does, then that means somebody fucked up the whitelist system pretty hard
/datum/gear/donator/donortestingbikehorn
diff --git a/modular_citadel/code/modules/client/loadout/_loadout.dm b/modular_citadel/code/modules/client/loadout/_loadout.dm
index 9988519471..0ebfa060f2 100644
--- a/modular_citadel/code/modules/client/loadout/_loadout.dm
+++ b/modular_citadel/code/modules/client/loadout/_loadout.dm
@@ -27,7 +27,10 @@ GLOBAL_LIST_EMPTY(loadout_whitelist_ids)
/proc/initialize_global_loadout_items()
load_loadout_config()
for(var/item in subtypesof(/datum/gear))
- var/datum/gear/I = new item
+ var/datum/gear/I = item
+ if(!initial(I.name))
+ continue
+ I = new item
LAZYINITLIST(GLOB.loadout_items[I.category])
LAZYINITLIST(GLOB.loadout_items[I.category][I.subcategory])
GLOB.loadout_items[I.category][I.subcategory][I.name] = I
@@ -43,8 +46,8 @@ GLOBAL_LIST_EMPTY(loadout_whitelist_ids)
/datum/gear
var/name
- var/category = "NOCATEGORY"
- var/subcategory = "NOSUBCATEGORY"
+ var/category = LOADOUT_CATEGORY_NONE
+ var/subcategory = LOADOUT_SUBCATEGORY_NONE
var/slot
var/description
var/path //item-to-spawn path
diff --git a/modular_citadel/code/modules/client/loadout/_medical.dm b/modular_citadel/code/modules/client/loadout/_medical.dm
index bba100faff..e371db94fc 100644
--- a/modular_citadel/code/modules/client/loadout/_medical.dm
+++ b/modular_citadel/code/modules/client/loadout/_medical.dm
@@ -11,21 +11,21 @@
/datum/gear/uniform/bluescrubs
name = "Blue Scrubs"
- subcategory = SUBCATEGORY_UNIFORM_JOBS
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_JOBS
path = /obj/item/clothing/under/rank/medical/doctor/blue
restricted_roles = list("Medical Doctor", "Chief Medical Officer", "Geneticist", "Chemist", "Virologist")
restricted_desc = "Medical"
/datum/gear/uniform/greenscrubs
name = "Green Scrubs"
- subcategory = SUBCATEGORY_UNIFORM_JOBS
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_JOBS
path = /obj/item/clothing/under/rank/medical/doctor/green
restricted_roles = list("Medical Doctor", "Chief Medical Officer", "Geneticist", "Chemist", "Virologist")
restricted_desc = "Medical"
/datum/gear/uniform/purplescrubs
name = "Purple Scrubs"
- subcategory = SUBCATEGORY_UNIFORM_JOBS
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_JOBS
path = /obj/item/clothing/under/rank/medical/doctor/purple
restricted_roles = list("Medical Doctor", "Chief Medical Officer", "Geneticist", "Chemist", "Virologist")
restricted_desc = "Medical"
@@ -33,13 +33,13 @@
/datum/gear/head/nursehat
name = "Nurse Hat"
path = /obj/item/clothing/head/nursehat
- subcategory = SUBCATEGORY_HEAD_JOBS
+ subcategory = LOADOUT_SUBCATEGORY_HEAD_JOBS
restricted_roles = list("Medical Doctor", "Chief Medical Officer", "Geneticist", "Chemist", "Virologist")
restricted_desc = "Medical"
/datum/gear/uniform/nursesuit
name = "Nurse Suit"
path = /obj/item/clothing/under/rank/medical/doctor/nurse
- subcategory = SUBCATEGORY_UNIFORM_JOBS
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_JOBS
restricted_roles = list("Medical Doctor", "Chief Medical Officer", "Geneticist", "Chemist", "Virologist")
restricted_desc = "Medical"
diff --git a/modular_citadel/code/modules/client/loadout/_security.dm b/modular_citadel/code/modules/client/loadout/_security.dm
index 3ec39008c1..ab316d577b 100644
--- a/modular_citadel/code/modules/client/loadout/_security.dm
+++ b/modular_citadel/code/modules/client/loadout/_security.dm
@@ -1,70 +1,70 @@
/datum/gear/uniform/navyblueuniformhos
name = "Head of Security navyblue uniform"
- subcategory = SUBCATEGORY_UNIFORM_JOBS
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_JOBS
path = /obj/item/clothing/under/rank/security/head_of_security/formal
restricted_roles = list("Head of Security")
/datum/gear/head/navybluehosberet
name = "Head of security's navyblue beret"
path = /obj/item/clothing/head/beret/sec/navyhos
- subcategory = SUBCATEGORY_HEAD_JOBS
+ subcategory = LOADOUT_SUBCATEGORY_HEAD_JOBS
restricted_roles = list("Head of Security")
/datum/gear/suit/navybluejackethos
name = "head of security's navyblue jacket"
- subcategory = SUBCATEGORY_SUIT_JOBS
+ subcategory = LOADOUT_SUBCATEGORY_SUIT_JOBS
path = /obj/item/clothing/suit/armor/hos/navyblue
restricted_roles = list("Head of Security")
/datum/gear/suit/navybluejacketofficer
name = "security officer's navyblue jacket"
- subcategory = SUBCATEGORY_SUIT_JOBS
+ subcategory = LOADOUT_SUBCATEGORY_SUIT_JOBS
path = /obj/item/clothing/suit/armor/navyblue
restricted_roles = list("Security Officer")
/datum/gear/head/navyblueofficerberet
name = "Security officer's Navyblue beret"
path = /obj/item/clothing/head/beret/sec/navyofficer
- subcategory = SUBCATEGORY_HEAD_JOBS
+ subcategory = LOADOUT_SUBCATEGORY_HEAD_JOBS
restricted_roles = list("Security Officer")
/datum/gear/uniform/navyblueuniformofficer
name = "Security officer navyblue uniform"
- subcategory = SUBCATEGORY_UNIFORM_JOBS
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_JOBS
path = /obj/item/clothing/under/rank/security/officer/formal
restricted_roles = list("Security Officer")
/datum/gear/suit/navybluejacketwarden
name = "warden navyblue jacket"
- subcategory = SUBCATEGORY_SUIT_JOBS
+ subcategory = LOADOUT_SUBCATEGORY_SUIT_JOBS
path = /obj/item/clothing/suit/armor/vest/warden/navyblue
restricted_roles = list("Warden")
/datum/gear/head/navybluewardenberet
name = "Warden's navyblue beret"
path = /obj/item/clothing/head/beret/sec/navywarden
- subcategory = SUBCATEGORY_HEAD_JOBS
+ subcategory = LOADOUT_SUBCATEGORY_HEAD_JOBS
restricted_roles = list("Warden")
/datum/gear/uniform/navyblueuniformwarden
name = "Warden navyblue uniform"
- subcategory = SUBCATEGORY_UNIFORM_JOBS
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_JOBS
path = /obj/item/clothing/under/rank/security/warden/formal
restricted_roles = list("Warden")
/datum/gear/uniform/secskirt
name = "Security skirt"
- subcategory = SUBCATEGORY_UNIFORM_JOBS
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_JOBS
path = /obj/item/clothing/under/rank/security/officer/skirt
restricted_roles = list("Security Officer", "Warden", "Head of Security")
/datum/gear/uniform/hosskirt
name = "Head of security's skirt"
- subcategory = SUBCATEGORY_UNIFORM_JOBS
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_JOBS
path = /obj/item/clothing/under/rank/security/head_of_security/skirt
restricted_roles = list("Head of Security")
/datum/gear/glasses/sechud
name = "Security Hud"
path = /obj/item/clothing/glasses/hud/security
- restricted_roles = list("Security Officer", "Warden", "Head of Security")
\ No newline at end of file
+ restricted_roles = list("Security Officer", "Warden", "Head of Security")
diff --git a/modular_citadel/code/modules/client/loadout/_service.dm b/modular_citadel/code/modules/client/loadout/_service.dm
index 8949c04604..848ad6233c 100644
--- a/modular_citadel/code/modules/client/loadout/_service.dm
+++ b/modular_citadel/code/modules/client/loadout/_service.dm
@@ -1,33 +1,33 @@
/datum/gear/uniform/greytidestationwide
name = "Staff Assistant's jumpsuit"
path = /obj/item/clothing/under/misc/staffassistant
- subcategory = SUBCATEGORY_UNIFORM_JOBS
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_JOBS
restricted_roles = list("Assistant")
/datum/gear/suit/neetsuit
name = "D.A.B. suit"
path = /obj/item/clothing/suit/assu_suit
- subcategory = SUBCATEGORY_UNIFORM_JOBS
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_JOBS
restricted_roles = list("Assistant")
cost = 2
/datum/gear/head/neethelm
name = "D.A.B. helmet"
path = /obj/item/clothing/head/assu_helmet
- subcategory = SUBCATEGORY_HEAD_JOBS
+ subcategory = LOADOUT_SUBCATEGORY_HEAD_JOBS
restricted_roles = list("Assistant")
cost = 2
/datum/gear/backpack/plushvar
name = "Ratvar Plushie"
path = /obj/item/toy/plush/plushvar
- subcategory = SUBCATEGORY_BACKPACK_TOYS
+ subcategory = LOADOUT_SUBCATEGORY_BACKPACK_TOYS
cost = 5
restricted_roles = list("Chaplain")
/datum/gear/backpack/narplush
name = "Narsie Plushie"
path = /obj/item/toy/plush/narplush
- subcategory = SUBCATEGORY_BACKPACK_TOYS
+ subcategory = LOADOUT_SUBCATEGORY_BACKPACK_TOYS
cost = 5
restricted_roles = list("Chaplain")
diff --git a/modular_citadel/code/modules/client/loadout/backpack.dm b/modular_citadel/code/modules/client/loadout/backpack.dm
index fe308f5b23..0b700b11e2 100644
--- a/modular_citadel/code/modules/client/loadout/backpack.dm
+++ b/modular_citadel/code/modules/client/loadout/backpack.dm
@@ -1,17 +1,17 @@
/datum/gear/backpack
- category = CATEGORY_BACKPACK
- subcategory = SUBCATEGORY_BACKPACK_GENERAL
+ category = LOADOUT_CATEGORY_BACKPACK
+ subcategory = LOADOUT_SUBCATEGORY_BACKPACK_GENERAL
slot = SLOT_IN_BACKPACK
/datum/gear/backpack/plushbox
name = "Plushie Choice Box"
path = /obj/item/choice_beacon/box/plushie
- subcategory = SUBCATEGORY_BACKPACK_TOYS
+ subcategory = LOADOUT_SUBCATEGORY_BACKPACK_TOYS
/datum/gear/backpack/tennis
name = "Classic Tennis Ball"
path = /obj/item/toy/tennis
- subcategory = SUBCATEGORY_BACKPACK_TOYS
+ subcategory = LOADOUT_SUBCATEGORY_BACKPACK_TOYS
/datum/gear/backpack/tennis/red
name = "Red Tennis Ball"
@@ -40,12 +40,12 @@
/datum/gear/backpack/dildo
name = "Customizable dildo"
path = /obj/item/dildo/custom
- subcategory = SUBCATEGORY_BACKPACK_TOYS
+ subcategory = LOADOUT_SUBCATEGORY_BACKPACK_TOYS
/datum/gear/backpack/toykatana
name = "Toy Katana"
path = /obj/item/toy/katana
- subcategory = SUBCATEGORY_BACKPACK_TOYS
+ subcategory = LOADOUT_SUBCATEGORY_BACKPACK_TOYS
cost = 3
/datum/gear/backpack/tapeplayer
@@ -63,7 +63,7 @@
/datum/gear/backpack/crayons
name = "Box of crayons"
path = /obj/item/storage/crayons
- subcategory = SUBCATEGORY_BACKPACK_TOYS
+ subcategory = LOADOUT_SUBCATEGORY_BACKPACK_TOYS
/datum/gear/backpack/multipen
name = "A multicolored pen"
@@ -98,3 +98,7 @@
name = "A diamond ring box"
path = /obj/item/storage/fancy/ringbox/diamond
cost = 5
+
+/datum/gear/backpack/necklace//this is here because loadout doesn't support proper accessories
+ name = "A renameable necklace"
+ path = /obj/item/clothing/accessory/necklace
diff --git a/modular_citadel/code/modules/client/loadout/glasses.dm b/modular_citadel/code/modules/client/loadout/glasses.dm
index 65bb7ad23c..b0eecbbf28 100644
--- a/modular_citadel/code/modules/client/loadout/glasses.dm
+++ b/modular_citadel/code/modules/client/loadout/glasses.dm
@@ -1,5 +1,5 @@
/datum/gear/glasses
- category = CATEGORY_GLASSES
+ category = LOADOUT_CATEGORY_GLASSES
slot = SLOT_GLASSES
/datum/gear/glasses/blindfold
diff --git a/modular_citadel/code/modules/client/loadout/gloves.dm b/modular_citadel/code/modules/client/loadout/gloves.dm
index 43323af43c..ffa4724f63 100644
--- a/modular_citadel/code/modules/client/loadout/gloves.dm
+++ b/modular_citadel/code/modules/client/loadout/gloves.dm
@@ -1,5 +1,5 @@
/datum/gear/gloves
- category = CATEGORY_GLOVES
+ category = LOADOUT_CATEGORY_GLOVES
slot = SLOT_GLOVES
/datum/gear/gloves/fingerless
@@ -24,3 +24,7 @@
name = "A diamond ring"
path = /obj/item/clothing/gloves/ring/diamond
cost = 4
+
+/datum/gear/gloves/customring
+ name = "A ring, renameable"
+ path = /obj/item/clothing/gloves/ring/custom
diff --git a/modular_citadel/code/modules/client/loadout/hands.dm b/modular_citadel/code/modules/client/loadout/hands.dm
index ff4827e6bc..db57fb466b 100644
--- a/modular_citadel/code/modules/client/loadout/hands.dm
+++ b/modular_citadel/code/modules/client/loadout/hands.dm
@@ -1,5 +1,5 @@
/datum/gear/hands
- category = CATEGORY_HANDS
+ category = LOADOUT_CATEGORY_HANDS
slot = SLOT_HANDS
/datum/gear/hands/cane
diff --git a/modular_citadel/code/modules/client/loadout/head.dm b/modular_citadel/code/modules/client/loadout/head.dm
index a1495aa7bd..fd03e2279f 100644
--- a/modular_citadel/code/modules/client/loadout/head.dm
+++ b/modular_citadel/code/modules/client/loadout/head.dm
@@ -1,6 +1,6 @@
/datum/gear/head
- category = CATEGORY_HEAD
- subcategory = SUBCATEGORY_HEAD_GENERAL
+ category = LOADOUT_CATEGORY_HEAD
+ subcategory = LOADOUT_SUBCATEGORY_HEAD_GENERAL
slot = SLOT_HEAD
/datum/gear/head/baseball
@@ -63,33 +63,33 @@
/datum/gear/head/trekcap
name = "Federation Officer's Cap (White)"
path = /obj/item/clothing/head/caphat/formal/fedcover
- subcategory = SUBCATEGORY_HEAD_JOBS
+ subcategory = LOADOUT_SUBCATEGORY_HEAD_JOBS
restricted_roles = list("Captain","Head of Personnel")
/datum/gear/head/trekcapcap
name = "Federation Officer's Cap (Black)"
path = /obj/item/clothing/head/caphat/formal/fedcover/black
- subcategory = SUBCATEGORY_HEAD_JOBS
+ subcategory = LOADOUT_SUBCATEGORY_HEAD_JOBS
restricted_roles = list("Captain","Head of Personnel")
/datum/gear/head/trekcapmedisci
name = "Federation Officer's Cap (Blue)"
path = /obj/item/clothing/head/caphat/formal/fedcover/medsci
- subcategory = SUBCATEGORY_HEAD_JOBS
+ subcategory = LOADOUT_SUBCATEGORY_HEAD_JOBS
restricted_desc = "Medical and Science"
restricted_roles = list("Chief Medical Officer","Medical Doctor","Chemist","Virologist","Paramedic","Geneticist","Research Director","Scientist", "Roboticist")
/datum/gear/head/trekcapeng
name = "Federation Officer's Cap (Yellow)"
path = /obj/item/clothing/head/caphat/formal/fedcover/eng
- subcategory = SUBCATEGORY_HEAD_JOBS
+ subcategory = LOADOUT_SUBCATEGORY_HEAD_JOBS
restricted_desc = "Engineering, Security, and Cargo"
restricted_roles = list("Chief Engineer","Atmospheric Technician","Station Engineer","Warden","Detective","Security Officer","Head of Security","Cargo Technician", "Shaft Miner", "Quartermaster")
/datum/gear/head/trekcapsec
name = "Federation Officer's Cap (Red)"
path = /obj/item/clothing/head/caphat/formal/fedcover/sec
- subcategory = SUBCATEGORY_HEAD_JOBS
+ subcategory = LOADOUT_SUBCATEGORY_HEAD_JOBS
restricted_desc = "Engineering, Security, and Cargo"
restricted_roles = list("Chief Engineer","Atmospheric Technician","Station Engineer","Warden","Detective","Security Officer","Head of Security","Cargo Technician", "Shaft Miner", "Quartermaster")
@@ -98,7 +98,7 @@
name = "Federation Kepi, command"
description = "A visored cap. Intended to be used with ORV uniform."
path = /obj/item/clothing/head/kepi/orvi/command
- subcategory = SUBCATEGORY_HEAD_JOBS
+ subcategory = LOADOUT_SUBCATEGORY_HEAD_JOBS
restricted_desc = "Heads of Staff"
restricted_roles = list("Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Research Director", "Chief Medical Officer", "Quartermaster")
@@ -106,7 +106,7 @@
name = "Federation Kepi, ops/sec"
description = "A visored cap. Intended to be used with ORV uniform."
path = /obj/item/clothing/head/kepi/orvi/engsec
- subcategory = SUBCATEGORY_HEAD_JOBS
+ subcategory = LOADOUT_SUBCATEGORY_HEAD_JOBS
restricted_desc = "Engineering, Security and Cargo"
restricted_roles = list("Chief Engineer", "Atmospheric Technician", "Station Engineer", "Warden", "Detective", "Security Officer", "Head of Security", "Cargo Technician", "Shaft Miner", "Quartermaster")
@@ -114,7 +114,7 @@
name = "Federation Kepi, medsci"
description = "A visored cap. Intended to be used with ORV uniform."
path = /obj/item/clothing/head/kepi/orvi/medsci
- subcategory = SUBCATEGORY_HEAD_JOBS
+ subcategory = LOADOUT_SUBCATEGORY_HEAD_JOBS
restricted_desc = "Medical and Science"
restricted_roles = list("Chief Medical Officer", "Medical Doctor", "Chemist", "Virologist", "Paramedic", "Geneticist", "Research Director", "Scientist", "Roboticist")
@@ -122,7 +122,7 @@
name = "Federation Kepi, service"
description = "A visored cap. Intended to be used with ORV uniform."
path = /obj/item/clothing/head/kepi/orvi/service
- subcategory = SUBCATEGORY_HEAD_JOBS
+ subcategory = LOADOUT_SUBCATEGORY_HEAD_JOBS
restricted_desc = "Service and Civilian, barring Clown, Mime and Lawyer"
restricted_roles = list("Assistant", "Bartender", "Botanist", "Cook", "Curator", "Janitor", "Chaplain")
@@ -130,7 +130,7 @@
name = "Federation Kepi, assistant"
description = "A visored cap. Intended to be used with ORV uniform."
path = /obj/item/clothing/head/kepi/orvi
- subcategory = SUBCATEGORY_HEAD_JOBS
+ subcategory = LOADOUT_SUBCATEGORY_HEAD_JOBS
restricted_roles = list("Assistant")
/*Commenting out Until next Christmas or made automatic
@@ -165,7 +165,7 @@
/datum/gear/head/cowboyhat/sec
name = "Cowboy Hat, Security"
path = /obj/item/clothing/head/cowboyhat/sec
- subcategory = SUBCATEGORY_HEAD_JOBS
+ subcategory = LOADOUT_SUBCATEGORY_HEAD_JOBS
restricted_desc = "Security"
restricted_roles = list("Warden","Detective","Security Officer","Head of Security")
diff --git a/modular_citadel/code/modules/client/loadout/mask.dm b/modular_citadel/code/modules/client/loadout/mask.dm
index ec77eba93c..0d7e32552e 100644
--- a/modular_citadel/code/modules/client/loadout/mask.dm
+++ b/modular_citadel/code/modules/client/loadout/mask.dm
@@ -1,5 +1,5 @@
/datum/gear/mask
- category = CATEGORY_MASK
+ category = LOADOUT_CATEGORY_MASK
slot = SLOT_WEAR_MASK
/datum/gear/mask/balaclava
diff --git a/modular_citadel/code/modules/client/loadout/neck.dm b/modular_citadel/code/modules/client/loadout/neck.dm
index 2e8ec7001d..19311f703a 100644
--- a/modular_citadel/code/modules/client/loadout/neck.dm
+++ b/modular_citadel/code/modules/client/loadout/neck.dm
@@ -1,21 +1,21 @@
/datum/gear/neck
- category = CATEGORY_NECK
- subcategory = SUBCATEGORY_NECK_GENERAL
+ category = LOADOUT_CATEGORY_NECK
+ subcategory = LOADOUT_SUBCATEGORY_NECK_GENERAL
slot = SLOT_NECK
/datum/gear/neck/bluetie
name = "Blue tie"
- subcategory = SUBCATEGORY_NECK_TIE
+ subcategory = LOADOUT_SUBCATEGORY_NECK_TIE
path = /obj/item/clothing/neck/tie/blue
/datum/gear/neck/redtie
name = "Red tie"
- subcategory = SUBCATEGORY_NECK_TIE
+ subcategory = LOADOUT_SUBCATEGORY_NECK_TIE
path = /obj/item/clothing/neck/tie/red
/datum/gear/neck/blacktie
name = "Black tie"
- subcategory = SUBCATEGORY_NECK_TIE
+ subcategory = LOADOUT_SUBCATEGORY_NECK_TIE
path = /obj/item/clothing/neck/tie/black
/datum/gear/neck/collar
@@ -32,7 +32,7 @@
/datum/gear/neck/scarf
name = "White scarf"
- subcategory = SUBCATEGORY_NECK_SCARVES
+ subcategory = LOADOUT_SUBCATEGORY_NECK_SCARVES
path = /obj/item/clothing/neck/scarf
/datum/gear/neck/scarf/black
diff --git a/modular_citadel/code/modules/client/loadout/shoes.dm b/modular_citadel/code/modules/client/loadout/shoes.dm
index f92ccbe17c..76d7305971 100644
--- a/modular_citadel/code/modules/client/loadout/shoes.dm
+++ b/modular_citadel/code/modules/client/loadout/shoes.dm
@@ -1,5 +1,5 @@
/datum/gear/shoes
- category = CATEGORY_SHOES
+ category = LOADOUT_CATEGORY_SHOES
slot = SLOT_SHOES
/datum/gear/shoes/laceup
diff --git a/modular_citadel/code/modules/client/loadout/suit.dm b/modular_citadel/code/modules/client/loadout/suit.dm
index 539187af8b..d0be26a8a4 100644
--- a/modular_citadel/code/modules/client/loadout/suit.dm
+++ b/modular_citadel/code/modules/client/loadout/suit.dm
@@ -1,6 +1,6 @@
/datum/gear/suit
- category = CATEGORY_SUIT
- subcategory = SUBCATEGORY_SUIT_GENERAL
+ category = LOADOUT_CATEGORY_SUIT
+ subcategory = LOADOUT_SUBCATEGORY_SUIT_GENERAL
slot = SLOT_WEAR_SUIT
/datum/gear/suit/poncho
@@ -23,67 +23,67 @@
/datum/gear/suit/jacketbomber
name = "Bomber jacket"
path = /obj/item/clothing/suit/jacket
- subcategory = SUBCATEGORY_SUIT_JACKETS
+ subcategory = LOADOUT_SUBCATEGORY_SUIT_JACKETS
/datum/gear/suit/jacketflannelblack // all of these are reskins of bomber jackets but with the vibe to make you look like a true lumberjack
name = "Black flannel jacket"
path = /obj/item/clothing/suit/jacket/flannel
- subcategory = SUBCATEGORY_SUIT_JACKETS
+ subcategory = LOADOUT_SUBCATEGORY_SUIT_JACKETS
/datum/gear/suit/jacketflannelred
name = "Red flannel jacket"
path = /obj/item/clothing/suit/jacket/flannel/red
- subcategory = SUBCATEGORY_SUIT_JACKETS
+ subcategory = LOADOUT_SUBCATEGORY_SUIT_JACKETS
/datum/gear/suit/jacketflannelaqua
name = "Aqua flannel jacket"
path = /obj/item/clothing/suit/jacket/flannel/aqua
- subcategory = SUBCATEGORY_SUIT_JACKETS
+ subcategory = LOADOUT_SUBCATEGORY_SUIT_JACKETS
/datum/gear/suit/jacketflannelbrown
name = "Brown flannel jacket"
path = /obj/item/clothing/suit/jacket/flannel/brown
- subcategory = SUBCATEGORY_SUIT_JACKETS
+ subcategory = LOADOUT_SUBCATEGORY_SUIT_JACKETS
/datum/gear/suit/jacketleather
name = "Leather jacket"
path = /obj/item/clothing/suit/jacket/leather
- subcategory = SUBCATEGORY_SUIT_JACKETS
+ subcategory = LOADOUT_SUBCATEGORY_SUIT_JACKETS
/datum/gear/suit/overcoatleather
name = "Leather overcoat"
path = /obj/item/clothing/suit/jacket/leather/overcoat
- subcategory = SUBCATEGORY_SUIT_JACKETS
+ subcategory = LOADOUT_SUBCATEGORY_SUIT_JACKETS
/datum/gear/suit/jacketpuffer
name = "Puffer jacket"
path = /obj/item/clothing/suit/jacket/puffer
- subcategory = SUBCATEGORY_SUIT_JACKETS
+ subcategory = LOADOUT_SUBCATEGORY_SUIT_JACKETS
/datum/gear/suit/vestpuffer
name = "Puffer vest"
path = /obj/item/clothing/suit/jacket/puffer/vest
- subcategory = SUBCATEGORY_SUIT_JACKETS
+ subcategory = LOADOUT_SUBCATEGORY_SUIT_JACKETS
/datum/gear/suit/jacketlettermanbrown
name = "Brown letterman jacket"
path = /obj/item/clothing/suit/jacket/letterman
- subcategory = SUBCATEGORY_SUIT_JACKETS
+ subcategory = LOADOUT_SUBCATEGORY_SUIT_JACKETS
/datum/gear/suit/jacketlettermanred
name = "Red letterman jacket"
path = /obj/item/clothing/suit/jacket/letterman_red
- subcategory = SUBCATEGORY_SUIT_JACKETS
+ subcategory = LOADOUT_SUBCATEGORY_SUIT_JACKETS
/datum/gear/suit/jacketlettermanNT
name = "Nanotrasen letterman jacket"
path = /obj/item/clothing/suit/jacket/letterman_nanotrasen
- subcategory = SUBCATEGORY_SUIT_JACKETS
+ subcategory = LOADOUT_SUBCATEGORY_SUIT_JACKETS
/datum/gear/suit/coat
name = "Winter coat"
path = /obj/item/clothing/suit/hooded/wintercoat
- subcategory = SUBCATEGORY_SUIT_COATS
+ subcategory = LOADOUT_SUBCATEGORY_SUIT_COATS
/datum/gear/suit/coat/aformal
name = "Assistant's formal winter coat"
@@ -155,7 +155,7 @@
/datum/gear/suit/militaryjacket
name = "Military Jacket"
path = /obj/item/clothing/suit/jacket/miljacket
- subcategory = SUBCATEGORY_SUIT_JACKETS
+ subcategory = LOADOUT_SUBCATEGORY_SUIT_JACKETS
/datum/gear/suit/ianshirt
name = "Ian Shirt"
@@ -164,13 +164,13 @@
/datum/gear/suit/flakjack
name = "Flak Jacket"
path = /obj/item/clothing/suit/flakjack
- subcategory = SUBCATEGORY_SUIT_JACKETS
+ subcategory = LOADOUT_SUBCATEGORY_SUIT_JACKETS
cost = 2
/datum/gear/suit/trekds9_coat
name = "DS9 Overcoat (use uniform)"
path = /obj/item/clothing/suit/storage/trek/ds9
- subcategory = SUBCATEGORY_SUIT_JOBS
+ subcategory = LOADOUT_SUBCATEGORY_SUIT_JOBS
restricted_desc = "All, barring Service and Civilian"
restricted_roles = list("Head of Security","Captain","Head of Personnel","Chief Engineer","Research Director","Chief Medical Officer","Quartermaster",
"Medical Doctor","Chemist","Virologist","Paramedic","Geneticist","Scientist", "Roboticist",
@@ -180,27 +180,27 @@
/datum/gear/suit/trekcmdcap
name = "Fed (movie) uniform, Black"
path = /obj/item/clothing/suit/storage/fluff/fedcoat/capt
- subcategory = SUBCATEGORY_SUIT_JOBS
+ subcategory = LOADOUT_SUBCATEGORY_SUIT_JOBS
restricted_roles = list("Captain","Head of Personnel")
/datum/gear/suit/trekcmdmov
name = "Fed (movie) uniform, Red"
path = /obj/item/clothing/suit/storage/fluff/fedcoat
- subcategory = SUBCATEGORY_SUIT_JOBS
+ subcategory = LOADOUT_SUBCATEGORY_SUIT_JOBS
restricted_desc = "Heads of Staff and Security"
restricted_roles = list("Head of Security","Captain","Head of Personnel","Chief Engineer","Research Director","Chief Medical Officer","Quartermaster","Warden","Detective","Security Officer")
/datum/gear/suit/trekmedscimov
name = "Fed (movie) uniform, Blue"
path = /obj/item/clothing/suit/storage/fluff/fedcoat/medsci
- subcategory = SUBCATEGORY_SUIT_JOBS
+ subcategory = LOADOUT_SUBCATEGORY_SUIT_JOBS
restricted_desc = "Medical and Science"
restricted_roles = list("Chief Medical Officer","Medical Doctor","Chemist","Virologist","Paramedic","Geneticist","Research Director","Scientist", "Roboticist")
/datum/gear/suit/trekengmov
name = "Fed (movie) uniform, Yellow"
path = /obj/item/clothing/suit/storage/fluff/fedcoat/eng
- subcategory = SUBCATEGORY_SUIT_JOBS
+ subcategory = LOADOUT_SUBCATEGORY_SUIT_JOBS
restricted_desc = "Engineering and Cargo"
restricted_roles = list("Chief Engineer","Atmospheric Technician","Station Engineer","Cargo Technician", "Shaft Miner", "Quartermaster")
@@ -212,38 +212,38 @@
/datum/gear/suit/trekcmdmod
name = "Fed (Modern) uniform, Red"
path = /obj/item/clothing/suit/storage/fluff/modernfedcoat/sec
- subcategory = SUBCATEGORY_SUIT_JOBS
+ subcategory = LOADOUT_SUBCATEGORY_SUIT_JOBS
restricted_desc = "Heads of Staff and Security"
restricted_roles = list("Head of Security","Captain","Head of Personnel","Chief Engineer","Research Director","Chief Medical Officer","Quartermaster","Warden","Detective","Security Officer")
/datum/gear/suit/trekmedscimod
name = "Fed (Modern) uniform, Blue"
path = /obj/item/clothing/suit/storage/fluff/modernfedcoat/medsci
- subcategory = SUBCATEGORY_SUIT_JOBS
+ subcategory = LOADOUT_SUBCATEGORY_SUIT_JOBS
restricted_desc = "Medical and Science"
restricted_roles = list("Chief Medical Officer","Medical Doctor","Chemist","Virologist","Paramedic","Geneticist","Research Director","Scientist", "Roboticist")
/datum/gear/suit/trekengmod
name = "Fed (Modern) uniform, Yellow"
path = /obj/item/clothing/suit/storage/fluff/modernfedcoat/eng
- subcategory = SUBCATEGORY_SUIT_JOBS
+ subcategory = LOADOUT_SUBCATEGORY_SUIT_JOBS
restricted_desc = "Engineering and Cargo"
restricted_roles = list("Chief Engineer","Atmospheric Technician","Station Engineer","Cargo Technician", "Shaft Miner", "Quartermaster")
/datum/gear/suit/christmascoatr
name = "Red Christmas Coat"
path = /obj/item/clothing/suit/hooded/wintercoat/christmascoatr
- subcategory = SUBCATEGORY_SUIT_COATS
+ subcategory = LOADOUT_SUBCATEGORY_SUIT_COATS
/datum/gear/suit/christmascoatg
name = "Green Christmas Coat"
path = /obj/item/clothing/suit/hooded/wintercoat/christmascoatg
- subcategory = SUBCATEGORY_SUIT_COATS
+ subcategory = LOADOUT_SUBCATEGORY_SUIT_COATS
/datum/gear/suit/christmascoatrg
name = "Red and Green Christmas Coat"
path = /obj/item/clothing/suit/hooded/wintercoat/christmascoatrg
- subcategory = SUBCATEGORY_SUIT_COATS
+ subcategory = LOADOUT_SUBCATEGORY_SUIT_COATS
/datum/gear/suit/samurai
name = "Samurai outfit"
diff --git a/modular_citadel/code/modules/client/loadout/uniform.dm b/modular_citadel/code/modules/client/loadout/uniform.dm
index e693f9d2f7..5ce73d1cfd 100644
--- a/modular_citadel/code/modules/client/loadout/uniform.dm
+++ b/modular_citadel/code/modules/client/loadout/uniform.dm
@@ -1,12 +1,12 @@
/datum/gear/uniform
- category = CATEGORY_UNIFORM
- subcategory = SUBCATEGORY_UNIFORM_GENERAL
+ category = LOADOUT_CATEGORY_UNIFORM
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_GENERAL
slot = SLOT_W_UNIFORM
/datum/gear/uniform/suit
name = "Black suit"
path = /obj/item/clothing/under/suit/black
- subcategory = SUBCATEGORY_UNIFORM_SUITS
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_SUITS
/datum/gear/uniform/suit/green
name = "Green suit"
@@ -51,7 +51,7 @@
/datum/gear/uniform/skirt
name = "Black skirt"
path = /obj/item/clothing/under/dress/skirt
- subcategory = SUBCATEGORY_UNIFORM_SKIRTS
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_SKIRTS
/datum/gear/uniform/skirt/blue
name = "Blue skirt"
@@ -88,7 +88,7 @@
/datum/gear/uniform/dress
name = "Striped Dress"
path = /obj/item/clothing/under/dress/striped
- subcategory = SUBCATEGORY_UNIFORM_DRESSES
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_DRESSES
/datum/gear/uniform/dress/sun/white
name = "White Sundress"
@@ -121,7 +121,7 @@
/datum/gear/uniform/pants
name = "Yoga Pants"
path = /obj/item/clothing/under/pants/yoga
- subcategory = SUBCATEGORY_UNIFORM_PANTS
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_PANTS
/datum/gear/uniform/kilt
name = "Kilt"
@@ -134,7 +134,7 @@
/datum/gear/uniform/shorts
name = "Athletic Shorts"
path = /obj/item/clothing/under/shorts/red
- subcategory = SUBCATEGORY_UNIFORM_SHORTS
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_SHORTS
/datum/gear/uniform/pants/bjeans
name = "Black Jeans"
@@ -185,7 +185,7 @@
/datum/gear/uniform/sweater
name = "Cream Commando Sweater"
path = /obj/item/clothing/under/sweater
- subcategory = SUBCATEGORY_UNIFORM_SWEATERS
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_SWEATERS
/datum/gear/uniform/sweater/black
name = "Black Commando Sweater"
@@ -251,21 +251,21 @@
/datum/gear/uniform/trekcmdtos
name = "TOS uniform, cmd"
path = /obj/item/clothing/under/trek/command
- subcategory = SUBCATEGORY_UNIFORM_JOBS
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_JOBS
restricted_desc = "Heads of Staff"
restricted_roles = list("Head of Security","Captain","Head of Personnel","Chief Engineer","Research Director","Chief Medical Officer","Quartermaster")
/datum/gear/uniform/trekmedscitos
name = "TOS uniform, med/sci"
path = /obj/item/clothing/under/trek/medsci
- subcategory = SUBCATEGORY_UNIFORM_JOBS
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_JOBS
restricted_desc = "Medical and Science"
restricted_roles = list("Chief Medical Officer","Medical Doctor","Chemist","Virologist","Paramedic","Geneticist","Research Director","Scientist", "Roboticist")
/datum/gear/uniform/trekengtos
name = "TOS uniform, ops/sec"
path = /obj/item/clothing/under/trek/engsec
- subcategory = SUBCATEGORY_UNIFORM_JOBS
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_JOBS
restricted_desc = "Engineering, Security, and Cargo"
restricted_roles = list("Chief Engineer","Atmospheric Technician","Station Engineer","Warden","Detective","Security Officer","Head of Security","Cargo Technician", "Shaft Miner", "Quartermaster")
@@ -273,21 +273,21 @@
/datum/gear/uniform/trekcmdtng
name = "TNG uniform, cmd"
path = /obj/item/clothing/under/trek/command/next
- subcategory = SUBCATEGORY_UNIFORM_JOBS
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_JOBS
restricted_desc = "Heads of Staff"
restricted_roles = list("Head of Security","Captain","Head of Personnel","Chief Engineer","Research Director","Chief Medical Officer","Quartermaster")
/datum/gear/uniform/trekmedscitng
name = "TNG uniform, med/sci"
path = /obj/item/clothing/under/trek/medsci/next
- subcategory = SUBCATEGORY_UNIFORM_JOBS
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_JOBS
restricted_desc = "Medical and Science"
restricted_roles = list("Chief Medical Officer","Medical Doctor","Chemist","Virologist","Paramedic","Geneticist","Research Director","Scientist", "Roboticist")
/datum/gear/uniform/trekengtng
name = "TNG uniform, ops/sec"
path = /obj/item/clothing/under/trek/engsec/next
- subcategory = SUBCATEGORY_UNIFORM_JOBS
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_JOBS
restricted_desc = "Engineering, Security, and Cargo"
restricted_roles = list("Chief Engineer","Atmospheric Technician","Station Engineer","Warden","Detective","Security Officer","Head of Security","Cargo Technician", "Shaft Miner", "Quartermaster")
@@ -295,21 +295,21 @@
/datum/gear/uniform/trekcmdvoy
name = "VOY uniform, cmd"
path = /obj/item/clothing/under/trek/command/voy
- subcategory = SUBCATEGORY_UNIFORM_JOBS
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_JOBS
restricted_desc = "Heads of Staff"
restricted_roles = list("Head of Security","Captain","Head of Personnel","Chief Engineer","Research Director","Chief Medical Officer","Quartermaster")
/datum/gear/uniform/trekmedscivoy
name = "VOY uniform, med/sci"
path = /obj/item/clothing/under/trek/medsci/voy
- subcategory = SUBCATEGORY_UNIFORM_JOBS
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_JOBS
restricted_desc = "Medical and Science"
restricted_roles = list("Chief Medical Officer","Medical Doctor","Chemist","Virologist","Paramedic","Geneticist","Research Director","Scientist", "Roboticist")
/datum/gear/uniform/trekengvoy
name = "VOY uniform, ops/sec"
path = /obj/item/clothing/under/trek/engsec/voy
- subcategory = SUBCATEGORY_UNIFORM_JOBS
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_JOBS
restricted_desc = "Engineering, Security, and Cargo"
restricted_roles = list("Chief Engineer","Atmospheric Technician","Station Engineer","Warden","Detective","Security Officer","Head of Security","Cargo Technician", "Shaft Miner", "Quartermaster")
@@ -317,21 +317,21 @@
/datum/gear/uniform/trekcmdds9
name = "DS9 uniform, cmd"
path = /obj/item/clothing/under/trek/command/ds9
- subcategory = SUBCATEGORY_UNIFORM_JOBS
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_JOBS
restricted_desc = "Heads of Staff"
restricted_roles = list("Head of Security","Captain","Head of Personnel","Chief Engineer","Research Director","Chief Medical Officer","Quartermaster")
/datum/gear/uniform/trekmedscids9
name = "DS9 uniform, med/sci"
path = /obj/item/clothing/under/trek/medsci/ds9
- subcategory = SUBCATEGORY_UNIFORM_JOBS
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_JOBS
restricted_desc = "Medical and Science"
restricted_roles = list("Chief Medical Officer","Medical Doctor","Chemist","Virologist","Paramedic","Geneticist","Research Director","Scientist", "Roboticist")
/datum/gear/uniform/trekengds9
name = "DS9 uniform, ops/sec"
path = /obj/item/clothing/under/trek/engsec/ds9
- subcategory = SUBCATEGORY_UNIFORM_JOBS
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_JOBS
restricted_desc = "Engineering, Security, and Cargo"
restricted_roles = list("Chief Engineer","Atmospheric Technician","Station Engineer","Warden","Detective","Security Officer","Head of Security","Cargo Technician", "Shaft Miner", "Quartermaster")
@@ -339,21 +339,21 @@
/datum/gear/uniform/trekcmdent
name = "ENT uniform, cmd"
path = /obj/item/clothing/under/trek/command/ent
- subcategory = SUBCATEGORY_UNIFORM_JOBS
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_JOBS
restricted_desc = "Heads of Staff"
restricted_roles = list("Head of Security","Captain","Head of Personnel","Chief Engineer","Research Director","Chief Medical Officer","Quartermaster")
/datum/gear/uniform/trekmedscient
name = "ENT uniform, med/sci"
path = /obj/item/clothing/under/trek/medsci/ent
- subcategory = SUBCATEGORY_UNIFORM_JOBS
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_JOBS
restricted_desc = "Medical and Science"
restricted_roles = list("Chief Medical Officer","Medical Doctor","Chemist","Virologist","Paramedic","Geneticist","Research Director","Scientist", "Roboticist")
/datum/gear/uniform/trekengent
name = "ENT uniform, ops/sec"
path = /obj/item/clothing/under/trek/engsec/ent
- subcategory = SUBCATEGORY_UNIFORM_JOBS
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_JOBS
restricted_desc = "Engineering, Security, and Cargo"
restricted_roles = list("Chief Engineer","Atmospheric Technician","Station Engineer","Warden","Detective","Security Officer","Head of Security","Cargo Technician", "Shaft Miner", "Quartermaster")
@@ -361,7 +361,7 @@
/datum/gear/uniform/trekfedutil
name = "TMP uniform"
path = /obj/item/clothing/under/trek/fedutil
- subcategory = SUBCATEGORY_UNIFORM_JOBS
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_JOBS
restricted_desc = "All, barring Service and Civilian"
restricted_roles = list("Head of Security","Captain","Head of Personnel","Chief Engineer","Research Director","Chief Medical Officer","Quartermaster",
"Medical Doctor","Chemist","Virologist","Paramedic","Geneticist","Scientist", "Roboticist",
@@ -371,13 +371,13 @@
/datum/gear/uniform/trekfedtrainee
name = "TMP uniform, trainee"
path = /obj/item/clothing/under/trek/fedutil/trainee
- subcategory = SUBCATEGORY_UNIFORM_JOBS
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_JOBS
restricted_roles = list("Assistant", "Janitor", "Cargo Technician")
/datum/gear/uniform/trekfedservice
name = "TMP uniform, service"
path = /obj/item/clothing/under/trek/fedutil/service
- subcategory = SUBCATEGORY_UNIFORM_JOBS
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_JOBS
restricted_desc = "Service and Civilian, barring Clown, Mime and Lawyer"
restricted_roles = list("Assistant", "Bartender", "Botanist", "Cook", "Curator", "Janitor", "Chaplain")
@@ -385,52 +385,52 @@
/datum/gear/uniform/orvcmd
name = "ORV uniform, cmd"
path = /obj/item/clothing/under/trek/command/orv
- subcategory = SUBCATEGORY_UNIFORM_JOBS
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_JOBS
restricted_desc = "Heads of Staff"
restricted_roles = list("Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Research Director", "Chief Medical Officer", "Quartermaster")
/datum/gear/uniform/orvcmd_capt
name = "ORV uniform, capt"
- subcategory = SUBCATEGORY_UNIFORM_JOBS
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_JOBS
path = /obj/item/clothing/under/trek/command/orv/captain
restricted_roles = list("Captain")
/datum/gear/uniform/orvmedsci
name = "ORV uniform, med/sci"
path = /obj/item/clothing/under/trek/medsci/orv
- subcategory = SUBCATEGORY_UNIFORM_JOBS
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_JOBS
restricted_desc = "Medical and Science"
restricted_roles = list("Chief Medical Officer", "Medical Doctor", "Chemist", "Virologist", "Paramedic", "Geneticist", "Research Director", "Scientist", "Roboticist")
/datum/gear/uniform/orvcmd_medsci
name = "ORV uniform, med/sci, cmd"
path = /obj/item/clothing/under/trek/command/orv/medsci
- subcategory = SUBCATEGORY_UNIFORM_JOBS
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_JOBS
restricted_roles = list("Chief Medical Officer", "Research Director")
/datum/gear/uniform/orvops
name = "ORV uniform, ops/sec"
path = /obj/item/clothing/under/trek/engsec/orv
- subcategory = SUBCATEGORY_UNIFORM_JOBS
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_JOBS
restricted_desc = "Engineering, Security and Cargo"
restricted_roles = list("Chief Engineer", "Atmospheric Technician", "Station Engineer", "Warden", "Detective", "Security Officer", "Head of Security", "Cargo Technician", "Shaft Miner", "Quartermaster")
/datum/gear/uniform/orvcmd_ops
name = "ORV uniform, ops/sec, cmd"
path = /obj/item/clothing/under/trek/command/orv/engsec
- subcategory = SUBCATEGORY_UNIFORM_JOBS
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_JOBS
restricted_roles = list("Chief Engineer", "Head of Security")
/datum/gear/uniform/orvass
name = "ORV uniform, assistant"
path = /obj/item/clothing/under/trek/orv
- subcategory = SUBCATEGORY_UNIFORM_JOBS
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_JOBS
restricted_roles = list("Assistant")
/datum/gear/uniform/orvsrv
name = "ORV uniform, service"
path = /obj/item/clothing/under/trek/orv/service
- subcategory = SUBCATEGORY_UNIFORM_JOBS
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_JOBS
restricted_roles = list("Assistant", "Bartender", "Botanist", "Cook", "Curator", "Janitor", "Chaplain")
restricted_desc = "Service and Civilian, barring Clown, Mime and Lawyer"
@@ -476,37 +476,37 @@
/datum/gear/uniform/qipao
name = "Qipao, Black"
path = /obj/item/clothing/under/costume/qipao
- subcategory = SUBCATEGORY_UNIFORM_DRESSES
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_DRESSES
cost = 3
/datum/gear/uniform/qipao/white
name = "Qipao, White"
path = /obj/item/clothing/under/costume/qipao/white
- subcategory = SUBCATEGORY_UNIFORM_DRESSES
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_DRESSES
cost = 3
/datum/gear/uniform/qipao/red
name = "Qipao, Red"
path = /obj/item/clothing/under/costume/qipao/red
- subcategory = SUBCATEGORY_UNIFORM_DRESSES
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_DRESSES
cost = 3
/datum/gear/uniform/cheongsam
name = "Cheongsam, Black"
path = /obj/item/clothing/under/costume/cheongsam
- subcategory = SUBCATEGORY_UNIFORM_DRESSES
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_DRESSES
cost = 3
/datum/gear/uniform/cheongsam/white
name = "Cheongsam, White"
path = /obj/item/clothing/under/costume/cheongsam/white
- subcategory = SUBCATEGORY_UNIFORM_DRESSES
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_DRESSES
cost = 3
/datum/gear/uniform/cheongsam/red
name = "Cheongsam, Red"
path = /obj/item/clothing/under/costume/cheongsam/red
- subcategory = SUBCATEGORY_UNIFORM_DRESSES
+ subcategory = LOADOUT_SUBCATEGORY_UNIFORM_DRESSES
cost = 3
/datum/gear/uniform/dress/black
@@ -539,4 +539,4 @@
/datum/gear/uniform/kimono/sakura
name = "Sakura kimono"
- path = /obj/item/clothing/under/costume/kimono/sakura
\ No newline at end of file
+ path = /obj/item/clothing/under/costume/kimono/sakura
diff --git a/modular_citadel/code/modules/projectiles/guns/ballistic/handguns.dm b/modular_citadel/code/modules/projectiles/guns/ballistic/handguns.dm
index 20917c4ba5..4829fd921c 100644
--- a/modular_citadel/code/modules/projectiles/guns/ballistic/handguns.dm
+++ b/modular_citadel/code/modules/projectiles/guns/ballistic/handguns.dm
@@ -16,13 +16,12 @@
spread = 20
actions_types = list()
-/obj/item/gun/ballistic/automatic/toy/pistol/stealth/update_icon()
- ..()
+/obj/item/gun/ballistic/automatic/toy/pistol/stealth/update_overlays()
+ . = ..()
if(magazine)
- cut_overlays()
- add_overlay("foamsp-magazine")
- else
- cut_overlays()
+ . += "foamsp-magazine"
+
+/obj/item/gun/ballistic/automatic/toy/pistol/stealth/update_icon_state()
icon_state = "[initial(icon_state)][chambered ? "" : "-e"]"
/////////RAYGUN MEMES/////////
diff --git a/modular_citadel/code/modules/projectiles/guns/ballistic/rifles.dm b/modular_citadel/code/modules/projectiles/guns/ballistic/rifles.dm
index 8a1310d2f1..c2bf251de9 100644
--- a/modular_citadel/code/modules/projectiles/guns/ballistic/rifles.dm
+++ b/modular_citadel/code/modules/projectiles/guns/ballistic/rifles.dm
@@ -155,11 +155,15 @@
casing_ejector = 0
spread = 10
recoil = 0.05
+ automatic_burst_overlay = FALSE
+ var/magtype = "flechettegun"
-/obj/item/gun/ballistic/automatic/flechette/update_icon()
- cut_overlays()
+/obj/item/gun/ballistic/automatic/flechette/update_overlays()
+ . = ..()
if(magazine)
- add_overlay("flechettegun-magazine")
+ . += "[magtype]-magazine"
+
+/obj/item/gun/ballistic/automatic/flechette/update_icon_state()
icon_state = "[initial(icon_state)][chambered ? "" : "-e"]"
///unique variant///
@@ -185,12 +189,7 @@
w_class = WEIGHT_CLASS_SMALL
spread = 15
recoil = 0.1
-
-/obj/item/gun/ballistic/automatic/flechette/shredder/update_icon()
- cut_overlays()
- if(magazine)
- add_overlay("shreddergun-magazine")
- icon_state = "[initial(icon_state)][chambered ? "" : "-e"]"
+ magtype = "shreddergun"
/*/////////////////////////////////////////////////////////////
//////////////////////// Zero's Meme //////////////////////////
@@ -218,17 +217,19 @@
burst_size = 4 //Shh.
fire_delay = 1
var/body_color = "#3333aa"
+ automatic_burst_overlay = FALSE
-/obj/item/gun/ballistic/automatic/AM4B/update_icon()
- ..()
+/obj/item/gun/ballistic/automatic/AM4B/ComponentInitialize()
+ . = ..()
+ AddElement(/datum/element/update_icon_updates_onmob)
+
+/obj/item/gun/ballistic/automatic/AM4B/update_overlays()
+ . = ..()
var/mutable_appearance/body_overlay = mutable_appearance('modular_citadel/icons/obj/guns/cit_guns.dmi', "AM4-Body")
if(body_color)
body_overlay.color = body_color
- cut_overlays() //So that it doesn't keep stacking overlays non-stop on top of each other
- add_overlay(body_overlay)
- if(ismob(loc))
- var/mob/M = loc
- M.update_inv_hands()
+ . += body_overlay
+
/obj/item/gun/ballistic/automatic/AM4B/AltClick(mob/living/user)
. = ..()
if(!in_range(src, user)) //Basic checks to prevent abuse
diff --git a/modular_citadel/code/modules/projectiles/guns/ballistic/spinfusor.dm b/modular_citadel/code/modules/projectiles/guns/ballistic/spinfusor.dm
index 3c0a47bfd7..c4cf8fc00f 100644
--- a/modular_citadel/code/modules/projectiles/guns/ballistic/spinfusor.dm
+++ b/modular_citadel/code/modules/projectiles/guns/ballistic/spinfusor.dm
@@ -55,8 +55,7 @@
/obj/item/gun/ballistic/automatic/spinfusor/attack_self(mob/living/user)
return //caseless rounds are too glitchy to unload properly. Best to make it so that you cannot remove disks from the spinfusor
-/obj/item/gun/ballistic/automatic/spinfusor/update_icon()
- ..()
+/obj/item/gun/ballistic/automatic/spinfusor/update_icon_state()
icon_state = "spinfusor[magazine ? "-[get_ammo(1)]" : ""]"
/obj/item/ammo_box/aspinfusor
diff --git a/modular_citadel/code/modules/projectiles/guns/energy/energy_gun.dm b/modular_citadel/code/modules/projectiles/guns/energy/energy_gun.dm
index 49d48e0000..65609f5830 100644
--- a/modular_citadel/code/modules/projectiles/guns/energy/energy_gun.dm
+++ b/modular_citadel/code/modules/projectiles/guns/energy/energy_gun.dm
@@ -14,16 +14,16 @@ obj/item/gun/energy/e_gun/cx
flight_y_offset = 10
var/body_color = "#252528"
-obj/item/gun/energy/e_gun/cx/update_icon()
- ..()
+obj/item/gun/energy/e_gun/cx/ComponentInitialize()
+ . = ..()
+ AddElement(/datum/element/update_icon_updates_onmob)
+
+obj/item/gun/energy/e_gun/cx/update_overlays()
+ . = ..()
var/mutable_appearance/body_overlay = mutable_appearance('modular_citadel/icons/obj/guns/cit_guns.dmi', "cxegun_body")
if(body_color)
body_overlay.color = body_color
- add_overlay(body_overlay)
-
- if(ismob(loc))
- var/mob/M = loc
- M.update_inv_hands()
+ . += body_overlay
obj/item/gun/energy/e_gun/cx/AltClick(mob/living/user)
. = ..()
diff --git a/modular_citadel/code/modules/projectiles/guns/pumpenergy.dm b/modular_citadel/code/modules/projectiles/guns/pumpenergy.dm
index 03a124e306..e81c7c18d3 100644
--- a/modular_citadel/code/modules/projectiles/guns/pumpenergy.dm
+++ b/modular_citadel/code/modules/projectiles/guns/pumpenergy.dm
@@ -12,7 +12,7 @@
/obj/item/gun/energy/pumpaction/emp_act(severity) //makes it not rack itself when emp'd
cell.use(round(cell.charge / severity))
- chambered = 0 //we empty the chamber
+ chambered = null //we empty the chamber
update_icon()
/obj/item/gun/energy/pumpaction/process() //makes it not rack itself when self-charging
@@ -20,7 +20,7 @@
charge_tick++
if(charge_tick < charge_delay)
return
- charge_tick = 0
+ charge_tick = null
if(selfcharge == EGUN_SELFCHARGE_BORG)
var/atom/owner = loc
if(istype(owner, /obj/item/robot_module))
@@ -44,7 +44,7 @@
if(chambered && !chambered.BB) //if BB is null, i.e the shot has been fired...
var/obj/item/ammo_casing/energy/shot = chambered
cell.use(shot.e_cost)//... drain the cell cell
- chambered = 0 //either way, released the prepared shot
+ chambered = null //either way, released the prepared shot
/obj/item/gun/energy/pumpaction/post_set_firemode()
var/has_shot = chambered
@@ -52,13 +52,13 @@
if(has_shot)
recharge_newshot(TRUE)
-/obj/item/gun/energy/pumpaction/update_icon() //adds racked indicators
+/obj/item/gun/energy/pumpaction/update_overlays() //adds racked indicators
..()
var/obj/item/ammo_casing/energy/shot = ammo_type[current_firemode_index]
if(chambered)
- add_overlay("[icon_state]_rack_[shot.select_name]")
+ . += "[icon_state]_rack_[shot.select_name]"
else
- add_overlay("[icon_state]_rack_empty")
+ . += "[icon_state]_rack_empty"
/obj/item/gun/energy/pumpaction/proc/pump(mob/M) //pumping proc. Checks if the gun is empty and plays a different sound if it is.
var/obj/item/ammo_casing/energy/shot = ammo_type[current_firemode_index]
diff --git a/modular_citadel/code/modules/reagents/chemistry/recipes/fermi.dm b/modular_citadel/code/modules/reagents/chemistry/recipes/fermi.dm
index a24a5beaad..39ba69bd61 100644
--- a/modular_citadel/code/modules/reagents/chemistry/recipes/fermi.dm
+++ b/modular_citadel/code/modules/reagents/chemistry/recipes/fermi.dm
@@ -178,7 +178,7 @@
name = "Sucubus milk"
id = /datum/reagent/fermi/breast_enlarger
results = list(/datum/reagent/fermi/breast_enlarger = 8)
- required_reagents = list(/datum/reagent/medicine/salglu_solution = 1, /datum/reagent/consumable/milk = 1, /datum/reagent/medicine/synthflesh = 2, /datum/reagent/silicon = 3, /datum/reagent/drug/aphrodisiac = 3)
+ required_reagents = list(/datum/reagent/medicine/salglu_solution = 2, /datum/reagent/consumable/milk = 1, /datum/reagent/medicine/synthflesh = 2, /datum/reagent/silicon = 5)
mix_message = "the reaction gives off a mist of milk."
//FermiChem vars:
OptimalTempMin = 200
@@ -218,7 +218,7 @@
name = "Incubus draft"
id = /datum/reagent/fermi/penis_enlarger
results = list(/datum/reagent/fermi/penis_enlarger = 8)
- required_reagents = list(/datum/reagent/blood = 5, /datum/reagent/medicine/synthflesh = 2, /datum/reagent/carbon = 2, /datum/reagent/drug/aphrodisiac = 2, /datum/reagent/medicine/salglu_solution = 1)
+ required_reagents = list(/datum/reagent/blood = 5, /datum/reagent/medicine/synthflesh = 2, /datum/reagent/carbon = 5, /datum/reagent/medicine/salglu_solution = 2)
mix_message = "the reaction gives off a spicy mist."
//FermiChem vars:
OptimalTempMin = 200
@@ -384,7 +384,7 @@
name = "Furranium"
id = /datum/reagent/fermi/furranium
results = list(/datum/reagent/fermi/furranium = 5)
- required_reagents = list(/datum/reagent/drug/aphrodisiac = 1, /datum/reagent/moonsugar = 1, /datum/reagent/silver = 2, /datum/reagent/medicine/salglu_solution = 1)
+ required_reagents = list(/datum/reagent/pax/catnip = 1, /datum/reagent/silver = 2, /datum/reagent/medicine/salglu_solution = 2)
mix_message = "You think you can hear a howl come from the beaker."
//FermiChem vars:
OptimalTempMin = 350
@@ -402,10 +402,6 @@
FermiChem = TRUE
PurityMin = 0.3
-/datum/chemical_reaction/fermi/furranium/organic
- id = "furranium_organic"
- required_reagents = list(/datum/reagent/drug/aphrodisiac = 1, /datum/reagent/pax/catnip = 1, /datum/reagent/silver = 2, /datum/reagent/medicine/salglu_solution = 1)
-
//FOR INSTANT REACTIONS - DO NOT MULTIPLY LIMIT BY 10.
//There's a weird rounding error or something ugh.
@@ -607,4 +603,4 @@
ThermicConstant = 0
HIonRelease = 0.01
RateUpLim = 15
- FermiChem = TRUE
\ No newline at end of file
+ FermiChem = TRUE
diff --git a/modular_citadel/icons/mob/mam_snouts.dmi b/modular_citadel/icons/mob/mam_snouts.dmi
index ab1a4654b4..4f6682f789 100644
Binary files a/modular_citadel/icons/mob/mam_snouts.dmi and b/modular_citadel/icons/mob/mam_snouts.dmi differ
diff --git a/modular_citadel/icons/mob/widerobot.dmi b/modular_citadel/icons/mob/widerobot.dmi
index 50c29bb75f..29eb35c715 100644
Binary files a/modular_citadel/icons/mob/widerobot.dmi and b/modular_citadel/icons/mob/widerobot.dmi differ
diff --git a/sound/ambience/antag/ecult_op.ogg b/sound/ambience/antag/ecult_op.ogg
new file mode 100644
index 0000000000..9944e833a6
Binary files /dev/null and b/sound/ambience/antag/ecult_op.ogg differ
diff --git a/sound/effects/footstep/rustystep1.ogg b/sound/effects/footstep/rustystep1.ogg
new file mode 100644
index 0000000000..bf90d52779
Binary files /dev/null and b/sound/effects/footstep/rustystep1.ogg differ
diff --git a/sound/machines/sm/accent/delam/1.ogg b/sound/machines/sm/accent/delam/1.ogg
new file mode 100644
index 0000000000..75c79f89ab
Binary files /dev/null and b/sound/machines/sm/accent/delam/1.ogg differ
diff --git a/sound/machines/sm/accent/delam/10.ogg b/sound/machines/sm/accent/delam/10.ogg
new file mode 100644
index 0000000000..c87b63b526
Binary files /dev/null and b/sound/machines/sm/accent/delam/10.ogg differ
diff --git a/sound/machines/sm/accent/delam/11.ogg b/sound/machines/sm/accent/delam/11.ogg
new file mode 100644
index 0000000000..c7f678245b
Binary files /dev/null and b/sound/machines/sm/accent/delam/11.ogg differ
diff --git a/sound/machines/sm/accent/delam/12.ogg b/sound/machines/sm/accent/delam/12.ogg
new file mode 100644
index 0000000000..a395942183
Binary files /dev/null and b/sound/machines/sm/accent/delam/12.ogg differ
diff --git a/sound/machines/sm/accent/delam/13.ogg b/sound/machines/sm/accent/delam/13.ogg
new file mode 100644
index 0000000000..934f17947d
Binary files /dev/null and b/sound/machines/sm/accent/delam/13.ogg differ
diff --git a/sound/machines/sm/accent/delam/14.ogg b/sound/machines/sm/accent/delam/14.ogg
new file mode 100644
index 0000000000..4175e5b947
Binary files /dev/null and b/sound/machines/sm/accent/delam/14.ogg differ
diff --git a/sound/machines/sm/accent/delam/15.ogg b/sound/machines/sm/accent/delam/15.ogg
new file mode 100644
index 0000000000..dcf73deb84
Binary files /dev/null and b/sound/machines/sm/accent/delam/15.ogg differ
diff --git a/sound/machines/sm/accent/delam/16.ogg b/sound/machines/sm/accent/delam/16.ogg
new file mode 100644
index 0000000000..20bc19399b
Binary files /dev/null and b/sound/machines/sm/accent/delam/16.ogg differ
diff --git a/sound/machines/sm/accent/delam/17.ogg b/sound/machines/sm/accent/delam/17.ogg
new file mode 100644
index 0000000000..b517fb3d3d
Binary files /dev/null and b/sound/machines/sm/accent/delam/17.ogg differ
diff --git a/sound/machines/sm/accent/delam/18.ogg b/sound/machines/sm/accent/delam/18.ogg
new file mode 100644
index 0000000000..4ef138d27a
Binary files /dev/null and b/sound/machines/sm/accent/delam/18.ogg differ
diff --git a/sound/machines/sm/accent/delam/19.ogg b/sound/machines/sm/accent/delam/19.ogg
new file mode 100644
index 0000000000..f638a6971b
Binary files /dev/null and b/sound/machines/sm/accent/delam/19.ogg differ
diff --git a/sound/machines/sm/accent/delam/2.ogg b/sound/machines/sm/accent/delam/2.ogg
new file mode 100644
index 0000000000..5b480daa2e
Binary files /dev/null and b/sound/machines/sm/accent/delam/2.ogg differ
diff --git a/sound/machines/sm/accent/delam/20.ogg b/sound/machines/sm/accent/delam/20.ogg
new file mode 100644
index 0000000000..6072bc6227
Binary files /dev/null and b/sound/machines/sm/accent/delam/20.ogg differ
diff --git a/sound/machines/sm/accent/delam/21.ogg b/sound/machines/sm/accent/delam/21.ogg
new file mode 100644
index 0000000000..1223dd946d
Binary files /dev/null and b/sound/machines/sm/accent/delam/21.ogg differ
diff --git a/sound/machines/sm/accent/delam/22.ogg b/sound/machines/sm/accent/delam/22.ogg
new file mode 100644
index 0000000000..9ccfb9b55a
Binary files /dev/null and b/sound/machines/sm/accent/delam/22.ogg differ
diff --git a/sound/machines/sm/accent/delam/23.ogg b/sound/machines/sm/accent/delam/23.ogg
new file mode 100644
index 0000000000..6399a8376a
Binary files /dev/null and b/sound/machines/sm/accent/delam/23.ogg differ
diff --git a/sound/machines/sm/accent/delam/24.ogg b/sound/machines/sm/accent/delam/24.ogg
new file mode 100644
index 0000000000..b51d359807
Binary files /dev/null and b/sound/machines/sm/accent/delam/24.ogg differ
diff --git a/sound/machines/sm/accent/delam/25.ogg b/sound/machines/sm/accent/delam/25.ogg
new file mode 100644
index 0000000000..823f22f136
Binary files /dev/null and b/sound/machines/sm/accent/delam/25.ogg differ
diff --git a/sound/machines/sm/accent/delam/26.ogg b/sound/machines/sm/accent/delam/26.ogg
new file mode 100644
index 0000000000..24b2a2f040
Binary files /dev/null and b/sound/machines/sm/accent/delam/26.ogg differ
diff --git a/sound/machines/sm/accent/delam/27.ogg b/sound/machines/sm/accent/delam/27.ogg
new file mode 100644
index 0000000000..4b4b145b7b
Binary files /dev/null and b/sound/machines/sm/accent/delam/27.ogg differ
diff --git a/sound/machines/sm/accent/delam/28.ogg b/sound/machines/sm/accent/delam/28.ogg
new file mode 100644
index 0000000000..7bc71bf0e6
Binary files /dev/null and b/sound/machines/sm/accent/delam/28.ogg differ
diff --git a/sound/machines/sm/accent/delam/29.ogg b/sound/machines/sm/accent/delam/29.ogg
new file mode 100644
index 0000000000..7fec2f271c
Binary files /dev/null and b/sound/machines/sm/accent/delam/29.ogg differ
diff --git a/sound/machines/sm/accent/delam/3.ogg b/sound/machines/sm/accent/delam/3.ogg
new file mode 100644
index 0000000000..5b57cc2707
Binary files /dev/null and b/sound/machines/sm/accent/delam/3.ogg differ
diff --git a/sound/machines/sm/accent/delam/30.ogg b/sound/machines/sm/accent/delam/30.ogg
new file mode 100644
index 0000000000..ed1ec7d89f
Binary files /dev/null and b/sound/machines/sm/accent/delam/30.ogg differ
diff --git a/sound/machines/sm/accent/delam/31.ogg b/sound/machines/sm/accent/delam/31.ogg
new file mode 100644
index 0000000000..0baa82e246
Binary files /dev/null and b/sound/machines/sm/accent/delam/31.ogg differ
diff --git a/sound/machines/sm/accent/delam/32.ogg b/sound/machines/sm/accent/delam/32.ogg
new file mode 100644
index 0000000000..e925b32d67
Binary files /dev/null and b/sound/machines/sm/accent/delam/32.ogg differ
diff --git a/sound/machines/sm/accent/delam/33.ogg b/sound/machines/sm/accent/delam/33.ogg
new file mode 100644
index 0000000000..9ddec0e84a
Binary files /dev/null and b/sound/machines/sm/accent/delam/33.ogg differ
diff --git a/sound/machines/sm/accent/delam/4.ogg b/sound/machines/sm/accent/delam/4.ogg
new file mode 100644
index 0000000000..aa4f4da071
Binary files /dev/null and b/sound/machines/sm/accent/delam/4.ogg differ
diff --git a/sound/machines/sm/accent/delam/5.ogg b/sound/machines/sm/accent/delam/5.ogg
new file mode 100644
index 0000000000..be438f6f15
Binary files /dev/null and b/sound/machines/sm/accent/delam/5.ogg differ
diff --git a/sound/machines/sm/accent/delam/6.ogg b/sound/machines/sm/accent/delam/6.ogg
new file mode 100644
index 0000000000..b89d52a564
Binary files /dev/null and b/sound/machines/sm/accent/delam/6.ogg differ
diff --git a/sound/machines/sm/accent/delam/7.ogg b/sound/machines/sm/accent/delam/7.ogg
new file mode 100644
index 0000000000..3a9cfc62ca
Binary files /dev/null and b/sound/machines/sm/accent/delam/7.ogg differ
diff --git a/sound/machines/sm/accent/delam/8.ogg b/sound/machines/sm/accent/delam/8.ogg
new file mode 100644
index 0000000000..7bc0a727fa
Binary files /dev/null and b/sound/machines/sm/accent/delam/8.ogg differ
diff --git a/sound/machines/sm/accent/delam/9.ogg b/sound/machines/sm/accent/delam/9.ogg
new file mode 100644
index 0000000000..5c1bd37405
Binary files /dev/null and b/sound/machines/sm/accent/delam/9.ogg differ
diff --git a/sound/machines/sm/accent/normal/1.ogg b/sound/machines/sm/accent/normal/1.ogg
new file mode 100644
index 0000000000..e92beed7fe
Binary files /dev/null and b/sound/machines/sm/accent/normal/1.ogg differ
diff --git a/sound/machines/sm/accent/normal/10.ogg b/sound/machines/sm/accent/normal/10.ogg
new file mode 100644
index 0000000000..9efb616f0b
Binary files /dev/null and b/sound/machines/sm/accent/normal/10.ogg differ
diff --git a/sound/machines/sm/accent/normal/11.ogg b/sound/machines/sm/accent/normal/11.ogg
new file mode 100644
index 0000000000..2af0981ef1
Binary files /dev/null and b/sound/machines/sm/accent/normal/11.ogg differ
diff --git a/sound/machines/sm/accent/normal/12.ogg b/sound/machines/sm/accent/normal/12.ogg
new file mode 100644
index 0000000000..2fab78b02d
Binary files /dev/null and b/sound/machines/sm/accent/normal/12.ogg differ
diff --git a/sound/machines/sm/accent/normal/13.ogg b/sound/machines/sm/accent/normal/13.ogg
new file mode 100644
index 0000000000..784e84e4a8
Binary files /dev/null and b/sound/machines/sm/accent/normal/13.ogg differ
diff --git a/sound/machines/sm/accent/normal/14.ogg b/sound/machines/sm/accent/normal/14.ogg
new file mode 100644
index 0000000000..af170394dd
Binary files /dev/null and b/sound/machines/sm/accent/normal/14.ogg differ
diff --git a/sound/machines/sm/accent/normal/15.ogg b/sound/machines/sm/accent/normal/15.ogg
new file mode 100644
index 0000000000..05c88c6b29
Binary files /dev/null and b/sound/machines/sm/accent/normal/15.ogg differ
diff --git a/sound/machines/sm/accent/normal/16.ogg b/sound/machines/sm/accent/normal/16.ogg
new file mode 100644
index 0000000000..46b0e33980
Binary files /dev/null and b/sound/machines/sm/accent/normal/16.ogg differ
diff --git a/sound/machines/sm/accent/normal/17.ogg b/sound/machines/sm/accent/normal/17.ogg
new file mode 100644
index 0000000000..e432b2ee02
Binary files /dev/null and b/sound/machines/sm/accent/normal/17.ogg differ
diff --git a/sound/machines/sm/accent/normal/18.ogg b/sound/machines/sm/accent/normal/18.ogg
new file mode 100644
index 0000000000..1e0e91abc8
Binary files /dev/null and b/sound/machines/sm/accent/normal/18.ogg differ
diff --git a/sound/machines/sm/accent/normal/19.ogg b/sound/machines/sm/accent/normal/19.ogg
new file mode 100644
index 0000000000..31de063e02
Binary files /dev/null and b/sound/machines/sm/accent/normal/19.ogg differ
diff --git a/sound/machines/sm/accent/normal/2.ogg b/sound/machines/sm/accent/normal/2.ogg
new file mode 100644
index 0000000000..05e3c9ff17
Binary files /dev/null and b/sound/machines/sm/accent/normal/2.ogg differ
diff --git a/sound/machines/sm/accent/normal/20.ogg b/sound/machines/sm/accent/normal/20.ogg
new file mode 100644
index 0000000000..36810bd8f1
Binary files /dev/null and b/sound/machines/sm/accent/normal/20.ogg differ
diff --git a/sound/machines/sm/accent/normal/21.ogg b/sound/machines/sm/accent/normal/21.ogg
new file mode 100644
index 0000000000..306e8856e5
Binary files /dev/null and b/sound/machines/sm/accent/normal/21.ogg differ
diff --git a/sound/machines/sm/accent/normal/22.ogg b/sound/machines/sm/accent/normal/22.ogg
new file mode 100644
index 0000000000..38286aa98b
Binary files /dev/null and b/sound/machines/sm/accent/normal/22.ogg differ
diff --git a/sound/machines/sm/accent/normal/23.ogg b/sound/machines/sm/accent/normal/23.ogg
new file mode 100644
index 0000000000..89f85fed91
Binary files /dev/null and b/sound/machines/sm/accent/normal/23.ogg differ
diff --git a/sound/machines/sm/accent/normal/24.ogg b/sound/machines/sm/accent/normal/24.ogg
new file mode 100644
index 0000000000..7c12a3e768
Binary files /dev/null and b/sound/machines/sm/accent/normal/24.ogg differ
diff --git a/sound/machines/sm/accent/normal/25.ogg b/sound/machines/sm/accent/normal/25.ogg
new file mode 100644
index 0000000000..f89175ceb1
Binary files /dev/null and b/sound/machines/sm/accent/normal/25.ogg differ
diff --git a/sound/machines/sm/accent/normal/26.ogg b/sound/machines/sm/accent/normal/26.ogg
new file mode 100644
index 0000000000..9efd1d8ef3
Binary files /dev/null and b/sound/machines/sm/accent/normal/26.ogg differ
diff --git a/sound/machines/sm/accent/normal/27.ogg b/sound/machines/sm/accent/normal/27.ogg
new file mode 100644
index 0000000000..1fb1edbb5a
Binary files /dev/null and b/sound/machines/sm/accent/normal/27.ogg differ
diff --git a/sound/machines/sm/accent/normal/28.ogg b/sound/machines/sm/accent/normal/28.ogg
new file mode 100644
index 0000000000..890c5ea429
Binary files /dev/null and b/sound/machines/sm/accent/normal/28.ogg differ
diff --git a/sound/machines/sm/accent/normal/29.ogg b/sound/machines/sm/accent/normal/29.ogg
new file mode 100644
index 0000000000..cd2aa40714
Binary files /dev/null and b/sound/machines/sm/accent/normal/29.ogg differ
diff --git a/sound/machines/sm/accent/normal/3.ogg b/sound/machines/sm/accent/normal/3.ogg
new file mode 100644
index 0000000000..38de5571a4
Binary files /dev/null and b/sound/machines/sm/accent/normal/3.ogg differ
diff --git a/sound/machines/sm/accent/normal/30.ogg b/sound/machines/sm/accent/normal/30.ogg
new file mode 100644
index 0000000000..87d1782768
Binary files /dev/null and b/sound/machines/sm/accent/normal/30.ogg differ
diff --git a/sound/machines/sm/accent/normal/31.ogg b/sound/machines/sm/accent/normal/31.ogg
new file mode 100644
index 0000000000..9ce3eeb72e
Binary files /dev/null and b/sound/machines/sm/accent/normal/31.ogg differ
diff --git a/sound/machines/sm/accent/normal/32.ogg b/sound/machines/sm/accent/normal/32.ogg
new file mode 100644
index 0000000000..26ca056142
Binary files /dev/null and b/sound/machines/sm/accent/normal/32.ogg differ
diff --git a/sound/machines/sm/accent/normal/33.ogg b/sound/machines/sm/accent/normal/33.ogg
new file mode 100644
index 0000000000..24964c1ce9
Binary files /dev/null and b/sound/machines/sm/accent/normal/33.ogg differ
diff --git a/sound/machines/sm/accent/normal/4.ogg b/sound/machines/sm/accent/normal/4.ogg
new file mode 100644
index 0000000000..2e71e976e8
Binary files /dev/null and b/sound/machines/sm/accent/normal/4.ogg differ
diff --git a/sound/machines/sm/accent/normal/5.ogg b/sound/machines/sm/accent/normal/5.ogg
new file mode 100644
index 0000000000..04852e10f2
Binary files /dev/null and b/sound/machines/sm/accent/normal/5.ogg differ
diff --git a/sound/machines/sm/accent/normal/6.ogg b/sound/machines/sm/accent/normal/6.ogg
new file mode 100644
index 0000000000..bf06c06bbe
Binary files /dev/null and b/sound/machines/sm/accent/normal/6.ogg differ
diff --git a/sound/machines/sm/accent/normal/7.ogg b/sound/machines/sm/accent/normal/7.ogg
new file mode 100644
index 0000000000..d29821701f
Binary files /dev/null and b/sound/machines/sm/accent/normal/7.ogg differ
diff --git a/sound/machines/sm/accent/normal/8.ogg b/sound/machines/sm/accent/normal/8.ogg
new file mode 100644
index 0000000000..0b94b9dbe0
Binary files /dev/null and b/sound/machines/sm/accent/normal/8.ogg differ
diff --git a/sound/machines/sm/accent/normal/9.ogg b/sound/machines/sm/accent/normal/9.ogg
new file mode 100644
index 0000000000..545b038be1
Binary files /dev/null and b/sound/machines/sm/accent/normal/9.ogg differ
diff --git a/sound/machines/sm/loops/calm.ogg b/sound/machines/sm/loops/calm.ogg
new file mode 100644
index 0000000000..cee14fcd13
Binary files /dev/null and b/sound/machines/sm/loops/calm.ogg differ
diff --git a/sound/machines/sm/loops/delamming.ogg b/sound/machines/sm/loops/delamming.ogg
new file mode 100644
index 0000000000..7d79f0e3c4
Binary files /dev/null and b/sound/machines/sm/loops/delamming.ogg differ
diff --git a/sound/machines/sm/supermatter1.ogg b/sound/machines/sm/supermatter1.ogg
index 1860e78800..be5185009e 100644
Binary files a/sound/machines/sm/supermatter1.ogg and b/sound/machines/sm/supermatter1.ogg differ
diff --git a/sound/machines/sm/supermatter2.ogg b/sound/machines/sm/supermatter2.ogg
index fb2d39fe26..5c98d28ed1 100644
Binary files a/sound/machines/sm/supermatter2.ogg and b/sound/machines/sm/supermatter2.ogg differ
diff --git a/sound/machines/sm/supermatter3.ogg b/sound/machines/sm/supermatter3.ogg
index 93ac3d505b..fb8e09166c 100644
Binary files a/sound/machines/sm/supermatter3.ogg and b/sound/machines/sm/supermatter3.ogg differ
diff --git a/sound/music/twilight.ogg b/sound/music/twilight.ogg
new file mode 100644
index 0000000000..635663314d
Binary files /dev/null and b/sound/music/twilight.ogg differ
diff --git a/sound/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/round_start_sounds.txt b/strings/round_start_sounds.txt
index c67bf6b4a6..177a3ea0a8 100644
--- a/strings/round_start_sounds.txt
+++ b/strings/round_start_sounds.txt
@@ -25,3 +25,4 @@ sound/music/rocketridersprayer.ogg
sound/music/theend.ogg
sound/music/flyinghigh.ogg
sound/music/samsara.ogg
+sound/music/twilight.ogg
\ No newline at end of file
diff --git a/strings/sillytips.txt b/strings/sillytips.txt
index bc59a109f0..e6710de95e 100644
--- a/strings/sillytips.txt
+++ b/strings/sillytips.txt
@@ -25,6 +25,7 @@ This game is older than most of the people playing it.
Do not go gentle into that good night.
Flashbangs can weaken blob tiles, allowing for you and the crew to easily destroy them.
Just the tip?
+You can grab someone by clicking on them with the grab intent, then upgrade the grab by clicking on them once more. An aggressive grab will momentarily stun someone, allow you to place Mekhi on a table by clicking on it, or throw them by toggling on throwing.
Some people are unable to read text on a game where half of it is based on text.
As the Captain, you can use a whetstone to sharpen your fancy fountain pen for extra robustness.
As the Lawyer, you are the last bastion of roleplay-focused jobs. Even the curator got a whip to go fight people with, that sellout!
@@ -43,5 +44,8 @@ Plasma men are a powerful race with many perks! No really, I swear! So what if t
As a Cargo Tech make sure to always buy a tesla to sell back to CC. They love those those. Trust me!
Help.
Maints.
-BZ stops or slows down Lings chem regeneration drastically, make sure to BZ flood the station when lings are confirmed!
Admins always regret meme options in their polls.
+Putting cat ears on securitrons makes them table people and nya. Mekhi isn't a cat, but he still goes on the table, just roll with it.
+As a Changeling, you can live without a head as they are merely vestigal to you, now, finally, you can be a Dullahan without it being Halloween.
+People actually have fictional sex between fictional characters in this game.
+When in doubt, take a break. A long break, preferably. If the game is wearing down your mental state and it's starting to lose any semblance of fun value, go and do something else for a month or two. By the time you come back, everything you liked will have been changed anyways.
diff --git a/strings/tips.txt b/strings/tips.txt
index 043405c7a0..5dc4e1b985 100644
--- a/strings/tips.txt
+++ b/strings/tips.txt
@@ -1,83 +1,91 @@
Where the space map levels connect is randomized every round, but are otherwise kept consistent within rounds. Remember that they are not necessarily bidirectional!
You can catch thrown items by toggling on your throw mode with an empty hand active.
-To crack the safe in the vault, use a stethoscope or explosives on it.
+To crack the safe in the vault, have a stethoscope in one of your hands and fiddle with the tumbler or you can alternatively use several concentrated explosive charges on it. Remember that the latter may result in the contents of the safe becoming a pile of ash.
You can climb onto a table by dragging yourself onto one. This takes time and drops the items in your hands on the table. Clicking on a table that someone else is climbing onto will knock them down.
You can drag other players onto yourself to open the strip menu, letting you remove their equipment or force them to wear something. Note that exosuits or helmets will block your access to the clothing beneath them, and that certain items take longer to strip or put on than others.
Clicking on a windoor rather then bumping into it will keep it open, you can click it again to close it.
You can spray a fire extinguisher, throw items or fire a gun while floating through space to change your direction. Simply fire opposite to where you want to go.
You can change the control scheme by pressing tab. One is WASD, the other is the arrow keys. Keep in mind that hotkeys are also changed with this.
-All vending machines can be hacked to obtain some contraband items from them, and many can be fed with coins to gain access to premium items.
+All vending machines can be hacked to obtain some contraband items from them, and many may charge extra credits to give you premium items.
Firesuits and winter coats offer mild protection from the cold, allowing you to spend longer periods of time near breaches and space than if wearing nothing at all.
Glass shards can be welded to make glass, and metal rods can be welded to make metal. Ores can be welded too, but this takes a lot of fuel.
If you need to drag multiple people either to safety or to space, bring a locker or crate over and stuff them all in before hauling them off.
-You can grab someone by clicking on them with the grab intent, then upgrade the grab by clicking on them once more. An aggressive grab will momentarily stun someone, allow you to place Mekhi on a table by clicking on it, or throw them by toggling on throwing.
+You can grab someone by clicking on them with the grab intent, then upgrade the grab by clicking on them once more. An aggressive grab can temporarily stun someone depending on their luck with resisting out of it, allowing you to slam them on a table by clicking on it, or throw them by toggling on throwing.
Holding alt and left clicking a tile will allow you to see its contents in the top right window pane, which is much faster than right clicking.
The resist button will allow you to resist out of handcuffs, being buckled to a chair or bed, out of locked lockers and more. Whenever you're stuck, try resisting!
You can move an item out of the way by dragging it and then clicking on an adjacent tile with an empty hand.
-You can recolor certain items like jumpsuits and gloves in washing machines by also throwing in a crayon.
+You can recolor certain items like jumpsuits and gloves in washing machines by also throwing in a crayon. For more advanced fashion you can spray items with a spray can to tint its colors. Some items work better than others at displaying their tints, like sterile and paper masks, or darkly colored gloves.
Maintenance is full of equipment that is randomized every round. Look around and see if anything is worth using.
-Some roles cannot be antagonists by default, but antag selection is decided first. For instance, you can set Security Officer to High without affecting your chances of becoming an antag -- the game will just select a different role.
+Some roles cannot be antagonists by default, but antag selection is decided first. For instance, you can set Security Officer to High without affecting your chances of becoming an antag - the game will just assign you to your next preferred role - or in the case that you have no such preferences set, a random role entirely.
There are many places around the station to hide contraband. A few for starters: linen boxes, toilet cisterns, body bags. Experiment to find more!
On all maps, you can use a machine in the vault to deposit space cash for cargo points. Otherwise, use it to steal the station's cash and get out before the alarm goes off.
-As the Captain, you are one of the highest priority targets on the station. Everything from revolutions, to nuclear operatives, to traitors that need to rob you of your unique lasgun or your life are things to worry about.
-As the Captain, always take the nuclear disk and pinpointer with you every shift. It's a good idea to give one of these to another head you can trust with keeping it safe, such as the Head of Security.
+As the Captain, you are one of the highest priority targets on the station. Everything from revolutions looking to thwart your rule, to nuclear operatives seeking the disk, to traitors that need to rob you of your several high value items - or your life are all things to be concerned about.
+As the Captain, always take the nuclear disk and pinpointer with you every shift. It's a good idea to give one of these to another head you can trust with keeping it safe, such as the Head of Personnel.
As the Captain, you have absolute access and control over the station, but this does not mean that being a horrible person won't result in mutiny and a ban.
As the Captain, you have a fancy pen that can be used as a holdout dagger or even as a scalpel in surgery!
As the Captain, you can purchase a new emergency shuttle using a communications console. Some require credits, while others give you credits in exchange. Keep in mind that purchasing dangerous shuttles will incur the ire of your crew.
-As the Chief Medical Officer, your hypospray is like a refillable instant injection syringe that can hold 30 units as opposed to the standard 15.
-As the Chief Medical Officer, coordinate and communicate with your doctors, chemists, and geneticists during a nuclear emergency, blob infestation, or some other crisis to keep people alive and fighting.
-As a Medical Doctor, pester Research for improved surgical tools. They work faster, don't cost much and are typically more deadly.
+As the Chief Medical Officer, your hypospray is like the ones that your Medical Doctors can buy, except it comes in a fancy box that can hold several more hypovials than the standard, and already comes preloaded with specially-made high-capacity hypovials that hold double the reagents the standard ones do.
+As the Chief Medical Officer, coordinate and communicate with your doctors, chemists, and paramedics during a nuclear emergency, blob infestation, or some other crisis to keep people alive and fighting.
+As a Medical Doctor, pester Research for improved surgical tools. They work faster, combine the purposes of several tools in one (scalpel/saw, retractor/hemostat, drill/cautery), and don't cost many materials to boot!
+As a Medical Doctor, the surgical saw and drill are both powerful weapons, the saw is sharp and can slice and dice, while the drill can quickly blind someone if aimed for the eyes. The laser scalpel is an upgraded version producible with Research's aid, and it has the highest force of most common place weapons, while still remaining sharp.
As a Medical Doctor, your belt can hold a full set of surgical tools. Using sterilizine before each attempt during surgery will reduce your failure chance on tricky steps or when using less-than-optimal equipment.
As a Medical Doctor, you can attempt to drain blood from a husk with a syringe to determine the cause. If you can extract blood, it was caused by extreme temperatures or lasers, if there is no blood to extract, you have confirmed the presence of changelings.
As a Medical Doctor, while both heal toxin damage, the difference between charcoal and antitoxin is that charcoal will actively remove all other reagents from one's body, while antitoxin only removes various toxins - but can overdose.
-As a Medical Doctor, you can surgically implant or extract things from people's chests. This can range from putting in a bomb to pulling out an alien larva.
+As a Medical Doctor, you can surgically implant or extract things from people's chests by performing a cavity implant. This could range from inserting a suicide bomb to embedding the nuke disk into the Captain's chest.
+As a Medical Doctor, it's of utmost urgency that you tend to anyone who's been hugged by a facehugger. You only have a couple of minutes from the initial attachment to perform organ manipulation to their chest and remove the rapidly developing alien embryo before it bursts out and immediately kills your patient.
As a Medical Doctor, you must target the correct limb and be on help intent when trying to perform surgery on someone. Using disarm attempt will intentionally fail the surgery step.
-As a Medical Doctor, corpses with the "...and their soul has departed" description no longer have a ghost attached to them and aren't usually revivable or cloneable. However it may prove useful to be creative in your revivification techniques with these bodies.
-As a Medical Doctor, treating plasmamen is not impossible! Salbutamol stops them from suffocating and showers stop them from burning alive. You can even perform surgery on them by doing the procedure on a roller bed under a shower.
+As a Medical Doctor, corpses with the "...and their soul has departed" description no longer have a ghost attached to them and can't be revived. However it may prove useful to be creative in your revivification techniques with these bodies.
+As a Medical Doctor, treating plasmamen is not impossible! Salbutamol and epinephrine stops them from suffocating due to lack of internals and showers stop them from burning alive. You can even perform surgery on them by doing the procedure on a roller bed under a shower.
As a Medical Doctor, you can point your penlight at people to create a medical hologram. This lets them know that you're coming to treat them.
As a Medical Doctor, you can extract implants by holding an empty implant case in your offhand while performing the extraction step.
As a Medical Doctor, clone scanning people will implant them with a health tracker that displays their vitals in the clone records. Useful to check on crew members that didn't activate suit sensors!
-As a Medical Doctor, medical gauze stops bleeding as well as healing 5 brute damage, this even works on the dead! Make sure to always have some gauze on you to stop bleeding before dragging someone.
-As a Chemist, there are dozens of chemicals that can heal, and even more that can cause harm. Experiment!
+As a Medical Doctor, you can deal with patients who have absurd amounts of wounds by putting them in cryo. This will slowly treat all of their wounds simultaneously, but is much slower than direct treatment.
+As a Medical Doctor, Critical Slash wounds are one of the most dangerous conditions someone can have. Apply gauze, epipens, sutures, cauteries, whatever you can, as soon as possible!
+As a Medical Doctor, Saline-Glucose not only acts as a temporary boost to a patient's blood level, it also speeds regeneration! Perfect for drained patients!
+As a Medical Doctor, medical gauze is an incredibly underrated tool. It can be used to entirely halt a limb from bleeding or sling one that's been shattered until it can be given proper attention. This even works on the dead, too! Be sure to stop someone's bleeding whether they're in critical condition or a corpse, as dragging someone whom is bleeding will rapidly deplete them of all their blood.
+As a Chemist, there are dozens of chemicals that can heal, and even more that can cause harm. See which chemicals have the best synergy, both in healing, and in harming. Experiment!
As a Chemist, some chemicals can only be synthesized by heating up the contents in the chemical heater.
As a Chemist, you will be expected to supply crew with certain chemicals. For example, clonexadone and mannitol for the cryo tubes, unstable mutagen and saltpetre for botany as well as healing pills and patches for the front desk.
As a Chemist, you can make 100u bottles from plastic sheets. The ChemMaster can produce infinite 30u glass bottles as well.
+As a Chemist, be sure to stock up some hypovials with useful chemicals for any doctors looking to heal on the go, you can also print out the deluxe hypovials at an autolathe specifically for the CMO's special hypospray.
+As a Chemist, the reagent dartgun, while neutered in its ability to harm - can still be loaded up with morphine for a ghetto sedation weapon, and a quick shot of charcoal can make a slime hybrid regret their life choices in an instant.
As a Geneticist, you can eject someone from cloning early by clicking on the cloner pod with your ID. Note that they will suffer more genetic damage and may lose vital organs from this.
-As a Geneticist, becoming a hulk makes you capable of dealing high melee damage, stunlocking people, and punching through walls. However, you can't fire guns, will lose your hulk status if you take too much damage, and are not considered a human by the AI while you are a hulk.
+As a Geneticist, becoming a hulk makes you capable of dealing high melee damage, becoming immune to most traditional stuns, and punching through walls. However, you can't fire guns, and will lose your hulk status if you take too much damage.
As the Virologist, your viruses can range from healing powers so great that you can heal out of critical status, or diseases so dangerous they can kill the entire crew with airborne spontaneous combustion. Experiment!
As the Virologist, you only require small amounts of vaccine to heal a sick patient. Work with the Chemist to distribute your cures more efficiently.
As the Research Director, you can take AIs out of their cores by loading them into an intelliCard, and then from there into an AI system integrity restorer computer to revive and/or repair them.
As the Research Director, you can lock down cyborgs instead of blowing them up. Then you can have their laws reset or if that doesn't work, safely dismantled.
As the Research Director, you can upgrade your modular console with better computer parts to speed up its functions. This can be useful when using the AI system integrity restorer.
As the Research Director, your console's NTnet monitoring tool can be used to retrieve airlock passkeys, provided that someone used a door remote.
-As a Scientist, you can use the mutation toxin obtained from green slimes to turn yourself into a jelly mutant. Each subspecies has unique features - for example telepathic powers, duplicating bodies or integrating slime extracts!
+As a Scientist, you can use the mutation toxin obtained from green slimes to turn yourself into a jelly mutant. Each subspecies has unique features - for example telepathic powers, duplicating bodies or integrating slime extracts for several unique effects!
As a Scientist, you can maximize the number of uses you get out of a slime by feeding it slime steroid, created from purple slimes, while alive. You can then apply extract enhancer, created from cerulean slimes, on each extract.
-As a Scientist, you can disable anomalies by scanning them with an analyzer, then send a signal on the frequency it gives you with a remote signalling device. This will leave behind an anomaly core, which can be used to construct a Phazon mech, or be used in the destructive analyzer for a 10,000 point bonus!
+As a Scientist, you can disable anomalies by scanning them with an analyzer, and then sending a signal on the frequency it gives you with a remote signalling device. Alternatively, you can print out anomaly defusal tools which can instantly disable an anomaly at the protolathe with some research, both of these methods will leave behind an anomaly core, which can be used to construct a Phazon mech, or be used in the destructive analyzer for a 10,000 point bonus!
As a Scientist, researchable stock parts can seriously improve the efficiency and speed of machines around the station. In some cases, it can even unlock new functions.
As a Scientist, you can generate research points by letting the tachyon-doppler array record increasingly large explosions.
As a Scientist, getting drunk just enough will speed up research. Skol!
As a Scientist, you can get points by placing slime cores into the destructive analyzer! This even works with crossbred slime cores.
-As a Scientist, work with botanists to get different types of seeds, as each type of seed can be used in the destructive analyzer for a small amount of Rnd type points!
+As a Scientist, you can get a minuscule amount of points by sacrificing a packet of seeds from Hydroponics into the destructive analyzer! While each individual one may not yield many points per, you can quite easily amass a very large variety of seeds, which could add up over time for a couple extra minutes shaved off of maxing out RND.
As a Roboticist, keep an ear out for anomaly announcements. If you get your hands on an anomaly core, you can build a Phazon mech!
As a Roboticist, you can repair your cyborgs with a welding tool. If they have taken burn damage from lasers, you can remove their battery, expose the wiring with a screwdriver and replace their wires with a cable coil.
As a Roboticist, you can reset a cyborg's module by cutting and mending the reset wire with a wire cutter.
+As a Roboticist, pay mind when toying with a cyborg's wires. It's best to pulse wires before immediately cutting them, as cutting them right away without knowing what they do may sever them from the AI, or disable their camera.
As a Roboticist, you can greatly help out Shaft Miners by building a Firefighter APLU equipped with a hydraulic clamp and plasma cutter. The mech is ash storm proof and can even walk across lava!
-As a Roboticist, you can augment people with cyborg limbs. Augmented limbs can easily be repaired with cables and welders.
+As a Roboticist, you can augment people with cyborg limbs. Augmented limbs are immune to the vacuum of space and temperatures while they can very easily be repaired with welders (brute) and cable coils (burn).
As a Roboticist, you can use your printer that is linked to the ore silo to teleport mats into your work place!
As a Roboticist, you can upgrade cleanbots with adv mops and brooms to make them faster and better!
-As a Roboticist, you can upgrade medical bots with diamond-tipped syringes, MK.II Hypospray, dispenser-sleeper-chemheater boards to make them inject faster, harder and better chems!
-As the AI, you can click on people's names to look at them. This only works if there are cameras that can see them.
+As a Roboticist, you can upgrade medical bots with diamond-tipped syringes, hyposprays, and chemistry machine boards to make their injections pierce hardsuits, work faster, and inject higher quality medicines!
+As the AI, you can click on people's names when they speak over the radio to jump your eye to them. This only works if there are cameras that can see them and are not wearing anything which would obsfuscate their face or tracking capabilities.
As the AI, you can quickly open and close doors by holding shift while clicking them, bolt them when holding ctrl, and even shock them while holding alt.
-As the AI, you can take pictures with your camera and upload them to newscasters.
-As a Cyborg, choose your module carefully, as only cutting and mending your reset wire will let you repick it. If possible, refrain from choosing a module until a situation that requires one occurs.
-As a Cyborg, you are immune to most forms of stunning, and excel at almost everything far better than humans. However, flashes can easily stunlock you and you cannot do any precision work as you lack hands.
+As the AI, you can take pictures with your camera and upload them to newscasters. Cyborgs also share from this pool of pictures.
+As a Cyborg, choose your module carefully, as only having your reset wire cut and mended by someone capable of manipulation will let you repick it. If possible, refrain from choosing a module until a situation that requires one occurs.
+As a Cyborg, you are immune to most forms of stunning, and excel at almost everything far better than humans. However, flashes and EMPs can easily stunlock you and you fall short in performing any tasks which require hands.
As a Cyborg, you are impervious to fires and heat. If you are rogue, you can release plasma fires everywhere and walk through them without a care in the world!
As a Cyborg, you are extremely vulnerable to EMPs as EMPs both stun you and damage you. The ion rifle in the armory or a traitor with an EMP kit can kill you in seconds.
As a Service Cyborg, your spray can knocks people down. However, it is blocked by gas masks.
-As an Engineering Cyborg, you can attach air alarm/fire alarm/APC frames to walls by placing them on the floor and using a screwdriver on them.
-As a Medical Cyborg, you can fully perform surgery and even augment people. Best of all, they have a 0% failure chance.
-As a Janitor Cyborg, you are the bane of all slaughter demons and even Bubblegum himself. Cleaning up blood stains will severely gimp them.
-As a Janitor Cyborg, you get a fancy bottle of drying agent! If you want to be nice, spray the janitor boots with them to magically upgrade them to absorbent galoshes.
+As an Engineering Cyborg, you can attach air alarm/fire alarm/APC frames to walls by placing them on the floor and using a screwdriver on them. Alternatively, you can use your in-built pseudo-hand manipulator to show those organics who's boss! It can even perform complex tasks such as removing cells from APCs, or inserting plasma canisters into radiation collectors.
+As a Medical Cyborg, you can fully perform surgery and even augment people. Best of all, they have a 0% failure chance, even if done on the floor.
+As a Janitor Cyborg, you are the bane of all slaughter demons and can even foil Bubblegum himself. Cleaning up blood stains will severely gimp them, although the latter may just turn you into robotic paste.
+As a Janitor Cyborg, you get a fancy bottle of drying agent! If you want to be nice, spray the janitor's galoshes with them to magically upgrade them to absorbent galoshes which automatically dry tiles.
As the Chief Engineer, you can rename areas or create entirely new ones using your station blueprints.
As the Chief Engineer, your hardsuit is significantly better than everybody else's. It has the best features of both engineering and atmospherics hardsuits - boasting nigh-invulnerability to radiation and all atmospheric conditions.
As the Chief Engineer, you can spy on and even forge PDA communications with the message monitor console! The key is in your office.
@@ -94,14 +102,14 @@ As an Engineer, you can convert tesla coils into corona analyzers by using a scr
As an Engineer, you can use radiation collectors to generate research points. Load them with a 50/50 oxygen/tritium tank and use a multitool to switch them to research mode.
As an Engineer, don't underestimate the humble P.A.C.M.A.N. generators. With upgraded parts, a couple units working in tandem are sufficient to take over for an exploded engine or shattered solars.
As an Engineer, your departmental protolathe and circuit printer can manufacture the necessary circuit boards and components to build just about anything. Make extra medical machinery everywhere! Build a gibber for security! Set up an array of emitters pointing down the hall! The possibilities are endless!
-As an Engineer, you can pry open secure storage by disabling the engine room APC's main breaker. This is obviously a bad idea if the engine is running.
-Don't forget that Cargo has access to a meteor defense satellite that can be ordered BEFORE meteors hit the station. Any idle Engineers should have this on their to-do list.
-As an Engineer, your RCD can be reloaded with mineral sheets instead of just compressed matter cartridges.
+As an Engineer, you can pry open secure storage by disabling the engine room APC's environmental breaker. This is obviously a bad idea if the engine is running.
+As an Engineer, don't forget that Cargo has access to a meteor defense satellite that can be ordered BEFORE meteors hit the station. Any idle Engineers should have this on their to-do list.
+As an Engineer, your RCD can be reloaded with mineral sheets instead of just compressed matter cartridges. Materials which are combined alloys of other materials (such as reinforced glass and plasteel) provide more matter per sheet to the RCD.
As an Atmospheric Technician, you can unwrench a pipe regardless of the pressures of the gases inside, but if they're too high they can burst out and injure you!
As an Atmospheric Technician, look into replacing your gas pumps with volumetric gas pumps, as those move air in flat numerical amounts, rather than percentages which leave trace gases.
-As an Atmospheric Technician, you are better suited to fighting fires than anyone else. As such, you have access to better firesuits, backpack firefighter tanks, and a completely heat and fire proof rigsuit.
+As an Atmospheric Technician, you are better suited to fighting fires than anyone else. As such, you have access to better firesuits, backpack firefighter tanks, and a completely heat and fire proof hardsuit.
As an Atmospheric Technician, your backpack firefighter tank can launch resin. This resin will extinguish fires and replace any gases with a safe, room-temperature airmix.
-As an Atmospheric Technician, your ATMOS holofan projector blocks gases while allowing objects to pass through. With it, you can quickly contain gas spills, fires and hull breaches. Or, use it to seal a plasmaman cloning room.
+As an Atmospheric Technician, your ATMOS holofan projectors can blocks gases and heat while allowing objects to pass through. With it, you can quickly contain gas spills, fires and hull breaches. Or, use it to create a plasmaman friendly lounge.
As an Atmospheric Technician, burning a plasma/oxygen mix inside the incinerator will not only produce power, but also gases such as tritium and water vapor.
As an Atmospheric Technician, you can change the layer of a pipe by clicking with it on a wrenched pipe or other atmos component of the desired layer.
As an Atmospheric Technician, you can take a few cans worth of N2/N2O and cool it down at local freezers. This is a good idea when dealing with (or preparing for) a supermatter meltdown.
@@ -111,65 +119,70 @@ As the Head of Security, don't let the power go to your head. You may have high
As the Warden, your duty is to be the watchdog of the brig and handler of prisoners when little is happening, and to hand out equipment and weapons to the security officers when a crisis strikes.
As the Warden, keep a close eye on the armory at all times, as it is a favored strike point of nuclear operatives and cocky traitors.
As the Warden, if a prisoner's crimes are heinous enough you can put them in permabrig or the gulag. Make sure to check on them once in a while!
-As the Warden, never underestimate the power of tech slugs! Scattershot fires a cone of weaker lasers, Ion slugs fires EMPs that only effect the tiles they hit, and Pulse slugs fire a singular laser that can one-hit almost any wall!
-As the Warden, you can use a surgical saw on riot shotguns to shorten the barrel, making them able to fit in your backpack.
+As the Warden, never underestimate the power of tech slugs! Scattershot fires a cone of weaker lasers with little damage fall off, Ion slugs fires EMPs that only effect the tiles they hit, and Pulse slugs fire a singular laser that can one-hit almost any wall!
+As the Warden, you can use a surgical saw on riot shotguns to shorten the barrel, making them able to fit in your backpack. Make sure to empty them prior lest you blast yourself in the face!
As the Warden, you can implant criminals you suspect might re-offend with devices that will track their location and allow you to remotely inject them with disabling chemicals.
As the Warden, you can use handcuffs on orange prisoner shoes to turn them into cuffed shoes, forcing prisoners to walk and potentially thwarting an escape.
-As the Warden, tracker implants can be used on sec officers. Doing this will let you track their corpse even without suits, though the implant will biodegrade after 5 minutes.
-As the Warden, cryostasis shotgun darts hold 10u of chemicals that will not react untill it hits someone.
-As the Warden, chemical implants can be loaded with a cocktail of healing or combat chems, perfect for the Hos or other sec officers to use. Be sure to keep a eye on them though, it will not auto inject! EMPs or starvation mite lead to the chemical implant to go off as well.
-As the Warden, tracker implants can be used on sec officers. Doing this will let you be able to message them when telecoms are out, or when you suspect coms are compromised. This is also good against rogue AIs as the prisoner tracker doesn't leave logs or alarms for the AI.
+As the Warden, tracker implants can be used on crewmembers. Doing this will let you track their person even without suit sensors and even instantly teleport to them at the local teleporter, although the implant will biodegrade after 5 minutes if its holder ever expires.
+As the Warden, cryostasis shotgun darts hold 10u of chemicals that will not react until it hits someone.
+As the Warden, chemical implants can be loaded with a cocktail of healing or combat chems, perfect for the HoS or other security officers to make use of in a pinch. Be sure to keep a eye on them though, as they cannot be injected without the prisoner management console! EMPs or starvation might lead to the chemical implant going off preemptively.
+As the Warden, tracker implants can be used on your security officers. Doing this will let you be able to message them when telecomms are out, or when you suspect comms are compromised. This is also good against rogue AIs as the prisoner tracker doesn't leave logs or alarms for the AI.
As a Security Officer, remember that correlation does not equal causation. Someone may have just been at the wrong place at the wrong time!
-As a Security Officer, remember that your belt can hold more then one stun baton.
-As a Security Officer, remember harm battoning someone in the head can deconvert them form a being a rev! This sadly doesn't work against the cult, nor does this protect them from getting reconverted.
-As a Security Officer, remember that you can attach a sec-lite to your taser or your helmet!
+As a Security Officer, remember that your belt can hold more than one stun baton.
+As a Security Officer, remember harm beating someone in the head with a blunt object can deconvert them form a being a revolutionary! This sadly doesn't work against either cult, nor does this protect them from getting reconverted unlike a mindshield implant.
+As a Security Officer, remember that you can attach a seclite to your taser or your helmet!
As a Security Officer, communicate and coordinate with your fellow officers using the security channel (:s) to avoid confusion.
-As a Security Officer, your sechuds or HUDsunglasses can not only see crewmates' job assignments and criminal status, but also if they are mindshield implanted. Use this to your advantage in a revolution to definitively tell who is on your side!
+As a Security Officer, your security HUDglasses can not only see crewmates' job assignments and criminal status, but also if they are mindshield implanted. Use this to your advantage in a revolution to definitively tell who is on your side!
As a Security Officer, mindshield implants can only prevent someone from being turned into a cultist: unlike revolutionaries, it will not de-cult them if they have already been converted.
-As a Security Officer, examining someone while wearing sechuds or HUDsunglasses will let you set their arrest level, which will cause Beepsky and other security bots to chase after them.
-As a Security Officer, you can take out the power cell on your baton to replace it with a better or fully charged one. Just use a screwdriver on your baton to remove the old cell
-As a Security Officer, you can place riot shotguns on your armor, this even works with winter sec coats!
+As a Security Officer, examining someone while wearing your security HUDglasses can allow you to swiftly edit their records and criminal status. Be sure to set someone to WANTED if you can't catch up to them, as it'll alert other officers of who's the bad guy, and cause the little security droids to chase after them for you.
+As a Security Officer, you can take out the power cell on your baton to replace it with a better or fully charged one. Just use a screwdriver on your baton to remove the old cell.
+As a Security Officer, you can just about any firearm on your vest, this even works with other non-standard armor-substitutes like security winter coats!
As the Detective, people leave fingerprints everywhere and on everything. With the exception of white latex, gloves will hide them. All is not lost, however, as gloves leave fibers specific to their kind such as black or nitrile, pointing to a general department.
-As the Detective, you can use your forensics scanner from a distance.
-As the Detective, your revolver can be loaded with .357 ammunition obtained from a hacked autolathe. Firing it has a decent chance to blow up your revolver.
+As the Detective, you can use your forensics scanner from a distance. Use this to scan boxes or other storage containers.
+As the Detective, your revolver can be loaded with .357 ammunition. Use a screwdriver to permanently modify your revolver into using this type of ammunition, be warned however, firing it has a decent chance to cause the revolver to misfire and shoot you in the foot.
As the Lawyer, try to negotiate with the Warden if sentences seem too high for the crime.
-As the Lawyer, you can try to convince the captain and Head of Security to hold trials for prisoners in the courtroom.
+As the Lawyer, you can try to convince the Captain and Head of Security to hold trials for prisoners in the courtroom.
As the Head of Personnel, you are not higher ranking than other heads of staff, even though you are expected to take the Captain's place first should he go missing. If the situation seems too rough for you, consider allowing another head to become temporary Captain.
-As the Head of Personnel, you are just as large a target as the Captain because of the potential power your ID and computer can hand out.
+As the Head of Personnel, you are just as large a target as the Captain because of the potential power your ID and computer can hand out and your comparative vulnerability.
As the Mime, your invisible wall power blocks people as well as projectiles. You can use it in a pinch to delay your pursuer.
-As the Mime, you can use :r and :l to speak through your ventriloquist dummy.
+As the Mime, you can use :r and :l to speak through your ventriloquist dummy. Sadly, this only works if your vow is broken, but at least you don't have to sacrifice your dignity by actually talking.
As the Mime, your oath of silence is your source of power. Breaking it robs you of your powers and of your honor.
+As the Mime, breaking your vow of silence is seen as incredibly dishonorable. Most people will seek to trouble and generally ignore a talking Mime.
As the Clown, if you lose your banana peel, you can still slip people with your PDA! Honk!
As the Clown, eating bananas heals you slightly. Honk!
As the Clown, your Holy Grail is the mineral bananium, which can be given to the Roboticist to build you a fun and robust mech beloved by everyone.
-As the Clown, you can use your stamp on a sheet of cardboard as the first step of making a honkbot. Fun for the whole crew!
-As the Chaplain, your null rod has a lot of functions: it can convert water into holy water, which if spread on the ground prevents wizards from jaunting away, can destroy cultist runes by hitting them, and is a very powerful weapon to boot!
-The Chaplain can bless any container with water by hitting it with their bible. Holy water has a myriad of uses against both cults and large amounts of it are a great contributor to success against them.
-The Chaplain's holy weapon will kill clockwork marauders in two hits.
-As the Chaplain, your bible is also a container that can store small items. Depending on your god, your starting bible may come with a surprise!
-As the Chaplain, you are much more likely to get a response by praying to the gods than most people. To boost your chances, make altars with colorful crayon runes, lit candles, and wire art.
+As the Clown, you can use your stamp on a sheet of flattened cardboard as the first step of making a honkbot. Fun for the whole crew!
+As the Clown, your number one way to win over the crew's favor is by telling jokes and putting forth effort into being comedic. Everyone loves a good clown, but everyone despises a bad one.
+As the Chaplain, your null rod has a lot of functions: while being an incredibly powerful weapon with an array of potential utilities depending upon the skin you chose for it, it also nulls cultist and wizard magic entirely, making you immune to them both and in some cases even harming the caster so long as you keep it in a pocket or in your hands.
+As the Chaplain, you can bless any water container by hitting it with your bible to turn it into holy water. Holy water has a myriad of uses against both cults and large amounts of it are a great contributor to success against them.
+As the Chaplain, your null rod will kill clockwork marauders in two hits while actively hindering their overall combat capabilities just by being nearby to them.
+As the Chaplain, your bible is also a container that can store a singular small item. Depending on your God, your starting bible may come with a surprise!
+As the Chaplain, you are much more likely to get a response by praying to the Gods than most people as your prayers will send a special noise cue directly to them! To further your chances of getting a response even further, pretty up your altar with crayon runes and wire art, and be sure to put a decent amount of effort into your prayers themselves. The Gods don't like lazy bums.
As a Botanist, you can hack the MegaSeed Vendor to get access to more exotic seeds. These seeds can alternatively be ordered from cargo.
As a Botanist, you can mutate the plants growing in your hydroponics trays with unstable mutagen or, as an alternative, crude radioactives from chemistry to get special variations.
-As a Botanist, you should look into increasing the potency of your plants. This increases the size, amount of chemicals, points gained from grinding them in the biogenerator, and lets people know you are a proficient botanist.
-As a Botanist, you can combine production trait chemicals just like a Chemist. Chlorine (blumpkin) + radium and phosphorus (glowshrooms) equals unstable mutagen!
+As a Botanist, you should look into increasing the potency of your plants. This is shown by the size of the plant's sprite, and can increase the amount of chemicals, points gained from grinding them in the biogenerator, and lets people know you are a proficient botanist.
+As a Botanist, you can combine production trait chemicals and mix your own complex chemicals inside of the plants themselves using precursors. Chlorine (blumpkin) + radium and phosphorus (glowshrooms) equals unstable mutagen!
+As a Botanist, earthsblood is an incredibly powerful chemical found in Ambrosia Gaia, it heals all types of damages very rapidly but causes lingering brain damage and has a nasty overdose. You can combine the chemicals from watermelons (water), grass (hydrogen), and cherries (sugar) to mix mannitol in with your earthsblood to completely counteract its main drawback!
+As a Botanist, Ambrosia Gaia is a plant mutated from Ambrosia Deus, which is a plant mutated from Ambrosia Vulgaris. The reagent contained within this plant known as earthsblood can make your trays and soil plots completely self sufficient when a plant containing such reagent is composted into them, meaning they won't need nutrients or water, and they'll automatically kill their own weeds and pests.
As a Cook, you can load your food into snack vending machines.
As a Cook, you can rename your custom made food with a pen.
As a Cook, any food you make will be much healthier than the junk food found in vendors. Having the crew routinely eating from you will provide minor buffs.
-As a Cook, being in the kitchen will make you remember the basics of Close Quarters Cooking. It is highly effective at removing Assistants from your workplace.
+As a Cook, being in the kitchen will make you remember the basics of Close Quarters Cooking (CQC). It is highly effective at removing Assistants from your workplace.
As a Cook, your Kitchenmate can vend out trays that fit on your belt slot. These trays pick up 7 food items at a time and are a quick way to transport large meals.
As a Cook, the advanced roasting stick is used to cook food at a distance, and can be used on SME, singularity, and other objects that cook food normally.
+As a Cook, the deep frier is a tool which can turn very large quantities of seemingly useless objects into food, albeit nutritionally poor and awful tasting food, but hey, food is food.
As the Bartender, the drinks you start with only give you the basics. If you want more advanced mixtures, look into working with chemistry, hydroponics, or even mining for things to grind up and throw in!
-As the Bartender, you can use a circular saw on your shotgun to make it easier to store.
-As a Janitor, if someone steals your janicart, you can instead use your space cleaner spray, grenades, water sprayer, exact bloody revenge or order another from Cargo.
+As the Bartender, you can use a circular saw on your shotgun to make it easier to store. Make sure to empty them prior lest you blast yourself in the face!
+As a Janitor, if someone steals your janicart, you can instead use your spray bottles, soap, and arsenal of slippery objects to exact your bloody revenge.. ..or just order another one from Cargo.
As a Janitor, the trash bag can be used to hold more than trash. Tools, medical equipment, smuggled nuclear disks... You name it!
-As a Janitor, mousetraps can be used to create bombs or booby-trap containers.
-Beware the Curator, for they are not completely defenseless. The curator's whip always disarms people, their laser pointer can blind humans and cyborgs, and can hide items in wirecut books.
+As a Janitor, mousetraps can be used as bomb triggers to booby-trap containers.
+As the Curator, for what it's worth, your toys and position are fairly robust. You can order a claymore, a whip, or a free space suit all at roundstart. The claymore is fairly underwhelming, however the whip is an incredibly robust weapon capable of always disarming, and that space suit is also better than the ones in EVA.
As the Curator, be sure to keep the shelves stocked and the library clean for crew.
As a Cargo Technician, you can hack MULEbots to make them faster, run over people in their way, and even let you ride them!
As a Cargo Technician, you can order contraband items from the supply shuttle console by de-constructing it and using a multitool on the circuit board, the re-assembling it.
As a Cargo Technician, you can earn more cargo points by shipping back crates from maintenance, liquid containers, plasma sheets, rare seeds from hydroponics, and more!
As a Cargo Technician, you get 400 points per packet! Stamp the manifest and sending back the crate will give you 200 points for the paperwork and 200 points for the crate!
-As a Cargo Technician, paperwork is an alternative option to shipping off plasma sheets and other goods. Order Paperwork crates and go into the crafting menu to turn pens and undone paper work into completed grant paper work to get 50 points per sheet!
+As a Cargo Technician, paperwork and glass blowing are alternative options to shipping off plasma sheets and other goods. Order their respective kits and get to work! Paperwork can be done quickly via the crafting menu for a quick buck, while glass blowing is much more lucrative, but may require some more effort and time.
As the Quartermaster, be sure to check the manifests on crates you receive to make sure all the info is correct. If there's a mistake, stamp the manifest DENIED and send it back in a crate with the items untouched for a refund!
As the Quartermaster, you can construct an express supply console that instantly delivers crates by drop pod. The impact will cause a small explosion as well.
As a Shaft Miner, the northern side of Lavaland has a lot more rare minerals than on the south.
@@ -177,25 +190,25 @@ As a Shaft Miner, every monster on Lavaland has a pattern you can exploit to min
As a Shaft Miner, you can harvest goliath plates from goliaths and upgrade your explorer's suit, mining hardsuits as well as Firefighter APLUs with them, greatly reducing incoming melee damage.
As a Shaft Miner, always have a GPS on you, so a fellow miner or cyborg can come to save you if you die.
As a Shaft Miner, you can craft a variety of equipment from the local fauna. Bone axes, lava boats and ash drake armour are just a few of them!
-As a Traitor, the cryptographic sequencer (emag) can not only open doors, but also lockers, crates, APCs and more. It can hack cyborgs, and even cause bots to go berserk. Use it on the right machines, and you can even order more traitor gear or contact the Syndicate. Experiment!
+As a Traitor, the cryptographic sequencer (emag) can not only open lockers, crates, APCs and more. It can also do things like hack cyborgs, and even cause bots to go berserk. Use it on the right machines, and you can even contact the Syndicate. Experiment!
As a Traitor, subverting the AI to serve you can make it an extremely powerful ally. However, be careful of the wording in the laws you give it, as it may use your poorly written laws against you!
As a Traitor, the Captain and the Head of Security are two of the most difficult to kill targets on the station. If either one is your target, plan carefully.
-As a Traitor, you can manufacture and recycle revolver bullets at a hacked autolathe, making the revolver an extremely powerful tool.
+As a Traitor, you can manufacture and recycle revolver bullets at a hacked autolathe, making the revolver an extremely powerful tool if you manage to nab an autolathe for yourself.
As a Traitor, you may sometimes be assigned to hunt other traitors, and in turn be hunted by others.
As a Traitor, the syndicate encryption key is very useful for coordinating plans with your fellow traitors -- or, of course, betraying them.
As a Traitor, plasma can be injected into many things to sabotage them. Power cells, light bulbs, cigars and e-cigs will all explode when used.
As a Nuclear Operative, communication is key! Use :t or :h to speak to your fellow operatives and coordinate an attack plan.
-As a Nuclear Operative, you should look into purchasing a syndicate cyborg, as they can provide heavy fire support, full access, are immune to conventional stuns, and can easily take down the AI.
+As a Nuclear Operative, you should look into purchasing one of the three Syndicate cyborgs in your uplink, as they can provide useful tactical support, function as walking access machines, are immune to conventional stuns, and can easily take down the AI.
As a Nuclear Operative, stick together! While your equipment is robust, your fellow operatives are much better at saving your life: they can drag you away from danger while stunned and provide cover fire.
As a Nuclear Operative, you might end up in a situation where the AI has bolted you into a room. Having some spare C4 in your pocket can save your life.
As a Monkey, you can crawl through air or scrubber vents by alt+left clicking them. You must drop everything you are wearing and holding to do this, however.
As a Monkey, you can still wear a few human items, such as backpacks, gas masks and hats, and still have two free hands.
As the Malfunctioning AI, you can shunt to an APC if the situation gets bad. This disables your doomsday device if it is active.
-As the Malfunctioning AI, you should either order your cyborgs to dismantle the robotics console or blow it up yourself in order to protect them.
+As the Malfunctioning AI, you should either order your cyborgs to dismantle the robotics console or blow it up yourself in order to protect them. Do note that this will prevent you from hacking any cyborg made in the future.
As the Malfunctioning AI, look into flooding the station with plasma fires to kill off large portions of the crew, letting you pick off the remaining few with space suits who escaped.
-Xenomorphs? Science can craft deadly tech shells like pulse slugs and laser scatter shot that are highly effective against any alien threat.
-When fighting aliens, it can be a good idea to turn off the gravity due to the alien's lack of zero-gravity control.
-When fighting xenomorph aliens, consider a shield. Shields can block their pounces and be worn on the back, but beware of neurotoxin.
+Xenomorphs? Any source of burn damage severely harms them. Science can craft deadly tech shells like pulse slugs and laser scatter shot that are highly effective against any alien threat.
+When fighting Aliens, it can be a good idea to turn off the gravity due to the every caste of alien's lack of zero-gravity control, especially Hunters and Drones, which are completely and utterly helpless.
+When fighting Aliens, consider a shield. A raised shield can halt their attempts to slash at you, and their disarms will always remove an item in your hand before knocking you over, always having something in your hand, no matter how small or worthless, can save your life.
As an Alien, your melee prowess is unmatched, but your ranged abilities are sorely lacking. Make use of corners to force a melee confrontation!
As an Alien, you take double damage from all burn attacks, such as lasers, welding tools, and fires. Furthermore, fire can destroy your resin and eggs. Expose areas to space to starve away any flamethrower fires before they can do damage!
As an Alien, resin floors not only regenerate your plasma supply, but also passively heal you. Fight on resin floors to gain a home turf advantage!
@@ -212,6 +225,7 @@ As the Blob, you can produce a Blobbernaut from a factory for 40 resources. Blob
As the Blob, you can expand by clicking, create strong blobs with ctrl-click, rally spores with middle-click, and remove blobs with alt-click. You do not need to have your camera over the tile to do this.
As the Blob, removing strong blobs, resource nodes, factories, and nodes will give you 4, 15, 25, and 25 resources back, respectively.
As the Blob, talking will send a message to all other overminds and all Blobbernauts, allowing you to direct attacks and coordinate.
+As the Blob, always make sure where you land is where you want to be, as it is very unlikely you will be getting too far away from it. Land in key points like the armory or the medical bay to immediately cripple the crew before they even find out you exist. Of course, always take into mind if being immediately discovered may outweight the benefits, and stick to maintenance close to these key points of interest to you.
As a Blobbernaut, you can communicate with overminds and other Blobbernauts via :b.
As a Blobbernaut, your HUD shows your health and the core health of the overmind that created you.
As a Revolutionary, you cannot convert a head of staff or someone who has a mindshield implant, such as a security officer or those they implant. Implants can however be surgically removed, and do not carry over with cloning. Take control of medbay to keep control of conversions!
@@ -220,6 +234,11 @@ As a Revolutionary, cargo can be your best friend or your worst nightmare. In th
As a Revolutionary, your main power comes from how quickly you spread. Convert people as fast as you can and overwhelm the heads of staff before security can arm up.
As a Changeling, the Extract DNA sting counts for your genome absorb objective, but does not let you respec your powers.
As a Changeling, you can absorb someone by strangling them and using the Absorb verb; this gives you the ability to rechoose your powers, the DNA of whoever you absorbed, the memory of the absorbed, and some samples of things the absorbed said.
+As a Changeling, absorbing someone will give you their full memory. This can include things such as a Traitor's uplink, thus absorbing one will allow you to access the Traitor uplink and buy toys for your Changeling self to abuse.
+As a Changeling, absorbing another Changeling will permanently boost your chemical reserve, allow you to pick more abilities, and make the victim unable to revive. Be careful when exposing your identity to other Changelings, as they may be out of those wonderful benefits.
+As a Changeling, BZ gas will dramatically slow down or even halt your natural chemical regeneration, be sure to avoid it at all costs as some lunatics may try and flood portions of the station to deal with you.
+As a Changeling, death is not the end for you! You can revive after two minutes from being dead by triggering your stasis ability, and then waiting for the prompt to resurrect yourself to show up.
+As a Changeling, your Regenerate Limbs power will quickly heal all of your wounds, but they'll still leave scars. Changelings can use Fleshmend to get rid of scars, or you can ingest Carpotoxin to get rid of them like a normal person.
As a Cultist, do not cause too much chaos before your objective is completed. If the shuttle gets called too soon, you may not have enough time to win.
As a Cultist, your team starts off very weak, but if necessary can quickly convert everything they have into raw power. Make sure you have the numbers and equipment to support going loud, or the cult will fall flat on its face.
As a Cultist, the Blood Boil rune will deal massive amounts of brute damage to non-cultists, stamina damage to Ratvarian scum, and some damage to fellow cultists of Nar-Sie nearby, but will create a fire where the rune stands on use.
@@ -239,9 +258,10 @@ You can deconvert Cultists of Nar-Sie and Servants of Ratvar by feeding them lar
Tiles sprayed with holy water will permanently block Servants of Ratvar from teleporting onto them.
As a Wizard, you can turn people to stone, then animate the resulting statue with a staff of animation to create an extremely powerful minion, for all of 5 minutes at least.
As a Wizard, the fireball spell performs very poorly at close range, as it can easily catch you in the blast. It is best used as a form of artillery down long hallways.
-As a Wizard, summoning guns will turn a large portion of the crew against themselves, but will also give everyone anything from a pea shooter to a BFG 9000. Use at your own risk!
+As a Wizard, summoning guns will turn a large portion of the crew against themselves, but will also give everyone anything from a energy pistol to a pulse rifle. Use at your own risk!
As a Wizard, the staff of chaos can fire any type of bolts from the magical wands. This can range from bolts of instant death to healing or reviving someone.
As a Wizard, most spells become unusable if you are not wearing your robe, hat, and sandals.
+As a Wizard, it's advisable that you don't dump all of your limited spell points into solely offensive spells, if you can't defend yourself then you're sure to get dunked.
As an Abductor, you can select where your victims will be sent on the ship control console.
As an Abductor Agent, the combat mode vest has much higher resistance to every kind of weapon, and your helmet prevents the AI from tracking you.
As an Abductor, the baton can cycle between four modes: stun, sleep, cuff and probe.
@@ -260,17 +280,24 @@ As a Drone, you can ping other drones to alert them of areas in the station in n
As a Drone, you can repair yourself by using a screwdriver on yourself and standing still!
As a Ghost, you can see the inside of a container on the ground by clicking on it.
As a Ghost, you can double click on just about anything to follow it. Or just warp around!
+As a Ghost, there's a button in the OOC tab labeled Observe, it lets you see through someone's eyes as if you were the one who's playing them.
As a Devil, you gain power for every three souls you control, however you also become more obvious.
As a Devil, as long as you control at least one other soul, you will automatically resurrect, as long as a banishment ritual is not performed.
At which time a Devil's nameth is spake on the tongue of man, the Devil may appeareth.
You can swap floor tiles by holding a crowbar in one hand and a stack of tiles in the other.
-When hacking doors, cutting and mending the "test light wire" will restore power to the door.
-When hacking, remote singulars pulse when attached to a wire and pinged. This can allow you to hack things or set traps from far away.
+When hacking doors, cutting and mending a "test light wire" will restore power to the door.
When crafting most items, you can either manually combine parts or use the crafting menu.
Suit storage units not only remove blood and dirt from clothing, but also radiation!
Remote devices will work when used through cameras. For example: Bluespace RPEDs and door remotes.
+You can light a cigar on a supermatter crystal.
+Using sticky tape on items can make them stick to people and walls! Be careful, grenades might stick to your hand during the moment of truth!
+In a pinch, stripping yourself naked will give you a sizeable resistance to being tackled. What do you value more, your freedom or your dignity?
+Wearing riot armor makes you significantly more effective at performing tackle takedowns, but will use extra stamina with each leap! It will also significantly protect you from other tackles!
+Epipens contain a powerful coagulant that drastically reduces bleeding on all bleeding wounds. If you don't have time to properly treat someone with lots of slashes or piercings, stick them with a pen to buy some time!
+Anything you can light a cigarette with, you can use to cauterize a bleeding wound. Technically, that includes the supermatter.
+Suit storage units entirely purge radiation from any carbon mob put inside of them when cycling, at the cost of some horrific burns, this is a very effective strategy to clean someone up after they bathed in the engine.
Laser pointers can be upgraded by replacing its micro laser with a better one from RnD! Use a screwdriver on it to remove the old laser. Upgrading the laser pointer gives you better odds of stunning a cyborg, and even blinding people with sunglasses.
-Being out of combat mode makes makes you deal less damage to people and objects when attacking.
-Resting makes you deal less damage to people and objects when attacking.
+Being out of combat mode makes makes you deal less damage to people and objects when attacking. This stacks with the penalty incurred by resting.
+Resting makes you deal less damage to people and objects when attacking. This stacks with the penalty incurred by being out of combat mode.
You do not regenerate as much stamina while in combat mode. Resting (being on the ground) makes you regenerate stamina faster.
Remember to be in combat mode while in combat, as otherwise you will be penalized by taking more incoming damage and dealing less damage to your adversary.
diff --git a/strings/traumas.json b/strings/traumas.json
index f461c5f5fd..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..79c4274722 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"
@@ -80,6 +81,7 @@
#include "code\__DEFINES\networks.dm"
#include "code\__DEFINES\pinpointers.dm"
#include "code\__DEFINES\pipe_construction.dm"
+#include "code\__DEFINES\plumbing.dm"
#include "code\__DEFINES\pool.dm"
#include "code\__DEFINES\power.dm"
#include "code\__DEFINES\preferences.dm"
@@ -300,6 +302,7 @@
#include "code\controllers\subsystem\events.dm"
#include "code\controllers\subsystem\fail2topic.dm"
#include "code\controllers\subsystem\fire_burning.dm"
+#include "code\controllers\subsystem\fluid.dm"
#include "code\controllers\subsystem\garbage.dm"
#include "code\controllers\subsystem\holodeck.dm"
#include "code\controllers\subsystem\icon_smooth.dm"
@@ -375,6 +378,7 @@
#include "code\datums\datumvars.dm"
#include "code\datums\dna.dm"
#include "code\datums\dog_fashion.dm"
+#include "code\datums\ductnet.dm"
#include "code\datums\emotes.dm"
#include "code\datums\ert.dm"
#include "code\datums\explosion.dm"
@@ -430,6 +434,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"
@@ -487,6 +492,11 @@
#include "code\datums\components\fantasy\affix.dm"
#include "code\datums\components\fantasy\prefixes.dm"
#include "code\datums\components\fantasy\suffixes.dm"
+#include "code\datums\components\plumbing\_plumbing.dm"
+#include "code\datums\components\plumbing\chemical_acclimator.dm"
+#include "code\datums\components\plumbing\filter.dm"
+#include "code\datums\components\plumbing\reaction_chamber.dm"
+#include "code\datums\components\plumbing\splitter.dm"
#include "code\datums\components\storage\storage.dm"
#include "code\datums\components\storage\ui.dm"
#include "code\datums\components\storage\concrete\_concrete.dm"
@@ -556,6 +566,7 @@
#include "code\datums\elements\_element.dm"
#include "code\datums\elements\art.dm"
#include "code\datums\elements\beauty.dm"
+#include "code\datums\elements\bsa_blocker.dm"
#include "code\datums\elements\cleaning.dm"
#include "code\datums\elements\decal.dm"
#include "code\datums\elements\dusts_on_catatonia.dm"
@@ -725,6 +736,7 @@
#include "code\game\gamemodes\dynamic\dynamic_rulesets_midround.dm"
#include "code\game\gamemodes\dynamic\dynamic_rulesets_roundstart.dm"
#include "code\game\gamemodes\dynamic\dynamic_storytellers.dm"
+#include "code\game\gamemodes\eldritch_cult\eldritch_cult.dm"
#include "code\game\gamemodes\extended\extended.dm"
#include "code\game\gamemodes\gangs\dominator.dm"
#include "code\game\gamemodes\gangs\dominator_countdown.dm"
@@ -1278,6 +1290,7 @@
#include "code\game\objects\structures\crates_lockers\crates\secure.dm"
#include "code\game\objects\structures\crates_lockers\crates\wooden.dm"
#include "code\game\objects\structures\icemoon\cave_entrance.dm"
+#include "code\game\objects\structures\lavaland\geyser.dm"
#include "code\game\objects\structures\lavaland\necropolis_tendril.dm"
#include "code\game\objects\structures\signs\_signs.dm"
#include "code\game\objects\structures\signs\signs_departments.dm"
@@ -1294,6 +1307,7 @@
#include "code\game\turfs\open.dm"
#include "code\game\turfs\turf.dm"
#include "code\game\turfs\openspace\openspace.dm"
+#include "code\game\turfs\openspace\transparent.dm"
#include "code\game\turfs\simulated\chasm.dm"
#include "code\game\turfs\simulated\dirtystation.dm"
#include "code\game\turfs\simulated\floor.dm"
@@ -1389,6 +1403,7 @@
#include "code\modules\admin\view_variables\mark_datum.dm"
#include "code\modules\admin\view_variables\mass_edit_variables.dm"
#include "code\modules\admin\view_variables\modify_variables.dm"
+#include "code\modules\admin\view_variables\reference_tracking.dm"
#include "code\modules\admin\view_variables\topic.dm"
#include "code\modules\admin\view_variables\topic_basic.dm"
#include "code\modules\admin\view_variables\topic_list.dm"
@@ -1407,6 +1422,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"
@@ -1595,6 +1611,16 @@
#include "code\modules\antagonists\disease\disease_disease.dm"
#include "code\modules\antagonists\disease\disease_event.dm"
#include "code\modules\antagonists\disease\disease_mob.dm"
+#include "code\modules\antagonists\eldritch_cult\eldritch_antag.dm"
+#include "code\modules\antagonists\eldritch_cult\eldritch_book.dm"
+#include "code\modules\antagonists\eldritch_cult\eldritch_effects.dm"
+#include "code\modules\antagonists\eldritch_cult\eldritch_items.dm"
+#include "code\modules\antagonists\eldritch_cult\eldritch_knowledge.dm"
+#include "code\modules\antagonists\eldritch_cult\eldritch_magic.dm"
+#include "code\modules\antagonists\eldritch_cult\eldritch_monster_antag.dm"
+#include "code\modules\antagonists\eldritch_cult\knowledge\ash_lore.dm"
+#include "code\modules\antagonists\eldritch_cult\knowledge\flesh_lore.dm"
+#include "code\modules\antagonists\eldritch_cult\knowledge\rust_lore.dm"
#include "code\modules\antagonists\ert\ert.dm"
#include "code\modules\antagonists\fugitive\fugitive.dm"
#include "code\modules\antagonists\fugitive\fugitive_outfits.dm"
@@ -2271,6 +2297,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"
@@ -2285,6 +2312,11 @@
#include "code\modules\lighting\lighting_setup.dm"
#include "code\modules\lighting\lighting_source.dm"
#include "code\modules\lighting\lighting_turf.dm"
+#include "code\modules\mafia\_defines.dm"
+#include "code\modules\mafia\controller.dm"
+#include "code\modules\mafia\map_pieces.dm"
+#include "code\modules\mafia\outfits.dm"
+#include "code\modules\mafia\roles.dm"
#include "code\modules\mapping\map_config.dm"
#include "code\modules\mapping\map_orientation_pattern.dm"
#include "code\modules\mapping\map_template.dm"
@@ -2499,6 +2531,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"
@@ -2579,6 +2612,7 @@
#include "code\modules\mob\living\simple_animal\constructs.dm"
#include "code\modules\mob\living\simple_animal\corpse.dm"
#include "code\modules\mob\living\simple_animal\damage_procs.dm"
+#include "code\modules\mob\living\simple_animal\eldritch_demons.dm"
#include "code\modules\mob\living\simple_animal\parrot.dm"
#include "code\modules\mob\living\simple_animal\pickle.dm"
#include "code\modules\mob\living\simple_animal\shade.dm"
@@ -2612,6 +2646,7 @@
#include "code\modules\mob\living\simple_animal\friendly\penguin.dm"
#include "code\modules\mob\living\simple_animal\friendly\pet.dm"
#include "code\modules\mob\living\simple_animal\friendly\plushie.dm"
+#include "code\modules\mob\living\simple_animal\friendly\possum.dm"
#include "code\modules\mob\living\simple_animal\friendly\sloth.dm"
#include "code\modules\mob\living\simple_animal\friendly\snake.dm"
#include "code\modules\mob\living\simple_animal\friendly\drone\_drone.dm"
@@ -2719,6 +2754,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 +2775,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"
@@ -2829,6 +2868,21 @@
#include "code\modules\photography\photos\album.dm"
#include "code\modules\photography\photos\frame.dm"
#include "code\modules\photography\photos\photo.dm"
+#include "code\modules\plumbing\ducts.dm"
+#include "code\modules\plumbing\plumbers\_plumb_machinery.dm"
+#include "code\modules\plumbing\plumbers\acclimator.dm"
+#include "code\modules\plumbing\plumbers\autohydro.dm"
+#include "code\modules\plumbing\plumbers\bottler.dm"
+#include "code\modules\plumbing\plumbers\destroyer.dm"
+#include "code\modules\plumbing\plumbers\fermenter.dm"
+#include "code\modules\plumbing\plumbers\filter.dm"
+#include "code\modules\plumbing\plumbers\grinder_chemical.dm"
+#include "code\modules\plumbing\plumbers\medipenrefill.dm"
+#include "code\modules\plumbing\plumbers\pill_press.dm"
+#include "code\modules\plumbing\plumbers\pumps.dm"
+#include "code\modules\plumbing\plumbers\reaction_chamber.dm"
+#include "code\modules\plumbing\plumbers\splitters.dm"
+#include "code\modules\plumbing\plumbers\synthesizer.dm"
#include "code\modules\pool\pool_controller.dm"
#include "code\modules\pool\pool_drain.dm"
#include "code\modules\pool\pool_effects.dm"
@@ -3078,6 +3132,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"
@@ -3247,9 +3302,9 @@
#include "code\modules\spells\spell.dm"
#include "code\modules\spells\spell_types\aimed.dm"
#include "code\modules\spells\spell_types\area_teleport.dm"
-#include "code\modules\spells\spell_types\barnyard.dm"
#include "code\modules\spells\spell_types\bloodcrawl.dm"
#include "code\modules\spells\spell_types\charge.dm"
+#include "code\modules\spells\spell_types\cone_spells.dm"
#include "code\modules\spells\spell_types\conjure.dm"
#include "code\modules\spells\spell_types\construct_spells.dm"
#include "code\modules\spells\spell_types\curse.dm"
@@ -3268,7 +3323,6 @@
#include "code\modules\spells\spell_types\lichdom.dm"
#include "code\modules\spells\spell_types\lightning.dm"
#include "code\modules\spells\spell_types\mime.dm"
-#include "code\modules\spells\spell_types\mind_transfer.dm"
#include "code\modules\spells\spell_types\projectile.dm"
#include "code\modules\spells\spell_types\rightandwrong.dm"
#include "code\modules\spells\spell_types\rod_form.dm"
@@ -3285,6 +3339,10 @@
#include "code\modules\spells\spell_types\turf_teleport.dm"
#include "code\modules\spells\spell_types\voice_of_god.dm"
#include "code\modules\spells\spell_types\wizard.dm"
+#include "code\modules\spells\spell_types\pointed\barnyard.dm"
+#include "code\modules\spells\spell_types\pointed\blind.dm"
+#include "code\modules\spells\spell_types\pointed\mind_transfer.dm"
+#include "code\modules\spells\spell_types\pointed\pointed.dm"
#include "code\modules\station_goals\bsa.dm"
#include "code\modules\station_goals\dna_vault.dm"
#include "code\modules\station_goals\shield.dm"
@@ -3360,15 +3418,14 @@
#include "code\modules\tgs\includes.dm"
#include "code\modules\tgui\external.dm"
#include "code\modules\tgui\states.dm"
-#include "code\modules\tgui\subsystem.dm"
#include "code\modules\tgui\tgui.dm"
+#include "code\modules\tgui\tgui_window.dm"
#include "code\modules\tgui\states\admin.dm"
#include "code\modules\tgui\states\always.dm"
#include "code\modules\tgui\states\conscious.dm"
#include "code\modules\tgui\states\contained.dm"
#include "code\modules\tgui\states\deep_inventory.dm"
#include "code\modules\tgui\states\default.dm"
-#include "code\modules\tgui\states\default_contained.dm"
#include "code\modules\tgui\states\hands.dm"
#include "code\modules\tgui\states\human_adjacent.dm"
#include "code\modules\tgui\states\inventory.dm"
diff --git a/tgui/.eslintrc.yml b/tgui/.eslintrc.yml
index 67e74085c7..9fd4db9fd2 100644
--- a/tgui/.eslintrc.yml
+++ b/tgui/.eslintrc.yml
@@ -8,6 +8,8 @@ env:
es6: true
browser: true
node: true
+globals:
+ Byond: readonly
plugins:
- react
settings:
@@ -388,7 +390,7 @@ rules:
## Enforce a particular style for multiline comments
# multiline-comment-style: error
## Enforce newlines between operands of ternary expressions
- multiline-ternary: [error, always-multiline]
+ # multiline-ternary: [error, always-multiline]
## Require constructor names to begin with a capital letter
# new-cap: error
## Enforce or disallow parentheses when invoking a constructor with no
diff --git a/tgui/.gitattributes b/tgui/.gitattributes
index 0016cc3bf6..9382416e69 100644
--- a/tgui/.gitattributes
+++ b/tgui/.gitattributes
@@ -2,9 +2,18 @@
## Enforce text mode and LF line breaks
*.js text eol=lf
+*.jsx text eol=lf
+*.ts text eol=lf
+*.tsx text eol=lf
*.css text eol=lf
+*.scss text eol=lf
*.html text eol=lf
*.json text eol=lf
+*.yml text eol=lf
+*.md text eol=lf
+*.bat text eol=lf
+yarn.lock text eol=lf
+bin/tgui text eol=lf
## Treat bundles as binary and ignore them during conflicts
*.bundle.* binary merge=tgui-merge-bundle
diff --git a/tgui/README.md b/tgui/README.md
index 5ddeb18fdd..9eab0196de 100644
--- a/tgui/README.md
+++ b/tgui/README.md
@@ -67,8 +67,9 @@ Run one of the following:
game as you code it. Very useful, highly recommended.
- In order to use it, you should start the game server first, connect to it
and wait until the world has been properly loaded and you are no longer
- in the lobby. Start tgui dev server. You'll know that it's hooked correctly
- if data gets dumped to the log when tgui windows are opened.
+ in the lobby. Start tgui dev server, and once it has finished building,
+ press F5 on any tgui window. You'll know that it's hooked correctly if
+ you see a green bug icon in titlebar and data gets dumped to the console.
- `bin/tgui --dev --reload` - reload byond cache once.
- `bin/tgui --dev --debug` - run server with debug logging enabled.
- `bin/tgui --dev --no-hot` - disable hot module replacement (helps when
@@ -134,11 +135,11 @@ logs and time spent on rendering. Use this information to optimize your
code, and try to keep re-renders below 16ms.
**Kitchen Sink.**
-Press `Ctrl+Alt+=` to open the KitchenSink interface. This interface is a
+Press `F12` to open the KitchenSink interface. This interface is a
playground to test various tgui components.
**Layout Debugger.**
-Press `Ctrl+Alt+-` to toggle the *layout debugger*. It will show outlines of
+Press `F11` to toggle the *layout debugger*. It will show outlines of
all tgui elements, which makes it easy to understand how everything comes
together, and can reveal certain layout bugs which are not normally visible.
@@ -180,8 +181,11 @@ See: [Component Reference](docs/component-reference.md).
## License
-All code is licensed with the parent license of *tgstation*, **AGPL-3.0**.
+Source code is covered by /tg/station's parent license - **AGPL-3.0**
+(see the main [README](../README.md)), unless otherwise indicated.
-See the main [README](../README.md) for more details.
+Some files are annotated with a copyright header, which explicitly states
+the copyright holder and license of the file. Most of the core tgui
+source code is available under the **MIT** license.
The Authors retain all copyright to their respective work here submitted.
diff --git a/tgui/bin/tgui b/tgui/bin/tgui
index eb1f200b31..97a86159e6 100755
--- a/tgui/bin/tgui
+++ b/tgui/bin/tgui
@@ -52,6 +52,7 @@ task-dev-server() {
task-eslint() {
cd "${base_dir}"
eslint ./packages "${@}"
+ echo "tgui: eslint check passed"
}
## Mr. Proper
@@ -153,6 +154,13 @@ if [[ ${1} == '--lint-harder' ]]; then
exit 0
fi
+if [[ ${1} == '--fix' ]]; then
+ shift 1
+ task-install
+ task-eslint --fix "${@}"
+ exit 0
+fi
+
## Analyze the bundle
if [[ ${1} == '--analyze' ]]; then
task-install
diff --git a/tgui/docs/component-reference.md b/tgui/docs/component-reference.md
index a2a0066a70..ff1b4e7dfd 100644
--- a/tgui/docs/component-reference.md
+++ b/tgui/docs/component-reference.md
@@ -30,6 +30,8 @@ Make sure to add new items to this list if you document new components.
- [`Icon`](#icon)
- [`Input`](#input)
- [`Knob`](#knob)
+ - [`LabeledControls`](#labeledcontrols)
+ - [`LabeledControls.Item`](#labeledcontrolsitem)
- [`LabeledList`](#labeledlist)
- [`LabeledList.Item`](#labeledlistitem)
- [`LabeledList.Divider`](#labeledlistdivider)
@@ -239,7 +241,7 @@ A ghetto checkbox, made entirely using existing Button API.
### `Button.Confirm`
-A button with a an extra confirmation step, using native button component.
+A button with an extra confirmation step, using native button component.
**Props:**
@@ -273,11 +275,11 @@ interface.
Example (button):
-```
+```jsx
@@ -285,11 +287,10 @@ Example (button):
Example (map):
-```
+```jsx
```
@@ -584,6 +585,24 @@ the input, or successfully enter a number.
- `onDrag: (e, value) => void` - An event, which fires about every 500ms
when you drag the input up and down, on release and on manual editing.
+### `LabeledControls`
+
+LabeledControls is a horizontal grid, that is designed to hold various
+controls, like [Knobs](#knob) or small [Buttons](#button). Every item in
+this grid is labeled at the bottom.
+
+**Props:**
+
+- See inherited props: [Box](#box)
+- `children: LabeledControls.Item` - Items to render.
+
+### `LabeledControls.Item`
+
+**Props:**
+
+- See inherited props: [Box](#box)
+- `label: string` - Item label.
+
### `LabeledList`
LabeledList is a continuous, vertical list of text and other content, where
@@ -962,6 +981,7 @@ Example:
- `className: string` - Applies a CSS class to the element.
- `theme: string` - A name of the theme.
- For a list of themes, see `packages/tgui/styles/themes`.
+- `title: string` - Window title.
- `resizable: boolean` - Controls resizability of the window.
- `children: any` - Child elements, which are rendered directly inside the
window. If you use a [Dimmer](#dimmer) or [Modal](#modal) in your UI,
diff --git a/tgui/docs/converting-old-tgui-interfaces.md b/tgui/docs/converting-old-tgui-interfaces.md
index a42724e05c..fe2feebfee 100644
--- a/tgui/docs/converting-old-tgui-interfaces.md
+++ b/tgui/docs/converting-old-tgui-interfaces.md
@@ -73,6 +73,7 @@ This might look a bit intimidating compared to the reactive part but it's not as
You don't really need to know all this to understand how to use it, but I find it helps with understanding when things go wrong.
Ractive conditionals can have an `else` as well
+
```ractive
{{#if data.condition}}
value
@@ -116,7 +117,7 @@ and you can mix string literals, values, and tags as well.
Ractive has loops for iterating over data and inserting something for each
member of an array or object
-```
+```ractive
{{#each data.list_of_foo}}
foo {{number}} is here.
{{/each}}
@@ -135,6 +136,7 @@ Objects are represented by `{}`, arrays by `[]`
`list("bla", "blo")` would become `["bla", "blo"]` and `list("foo" = 1, "bar" = 2)` would become `{"foo": 1, "bar": 2}`
First things first, above the `return` of the function you're making the interface in, you're going to want to add something like this
+
```jsx
const things = data.things || [];
```
@@ -142,6 +144,7 @@ const things = data.things || [];
This ensures that you'll never be reading a null entry by mistake. Substitute `{}` for objects as appropriate.
If it's an array, you'll want to do this in the template
+
```jsx
{things.map(thing => (
@@ -187,7 +190,7 @@ const fooArray = toArray(fooObject);
Also occasionally you'd see an else:
-```
+```ractive
{{#each data.potentially_empty_list}}
Thing "{{name}}" is in this list!
{{else}}
@@ -220,7 +223,7 @@ This will be a reference of tgui components and the tgui-next equivalent.
Equivalent of `` is ``
-```
+```ractive
Contents
@@ -236,7 +239,7 @@ becomes
A feature sometimes used is if `ui-display` has the `button` property, it will contain a `partial` command. This becomes the `buttons` property on `Section`:
-```
+```ractive
{{#partial button}}
// lots more button bullshit here
@@ -263,7 +266,7 @@ Very important to note `ui-section` is NOT the equivalent of `Section`
`` does not have a direct equivalent, but the closest equivalent is ``
-```
+```ractive
No Power
@@ -293,7 +296,7 @@ Also good to know that if you need the contents of a `LabeledList.Item` to be co
`` has a direct equivalent in ``
-```
+```ractive
Notice stuff!
@@ -311,7 +314,7 @@ becomes
The equivalent of `ui-button` is `Button` but it works quite a bit differently.
-```
+```ractive
{
@@ -294,10 +292,10 @@ here's what you need (note that you'll probably be forced to clean your shit up
upon code review):
```dm
-/obj/copypasta/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, datum/tgui/master_ui = null, datum/ui_state/state = default_state) // Remember to use the appropriate state.
- ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+/obj/copypasta/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
- ui = new(user, src, ui_key, "copypasta", name, 300, 300, master_ui, state)
+ ui = new(user, src, "copypasta")
ui.open()
/obj/copypasta/ui_data(mob/user)
@@ -345,7 +343,7 @@ export const SampleInterface = (props, context) => {
diff --git a/tgui/package.json b/tgui/package.json
index bfceeec49e..8dd15925b2 100644
--- a/tgui/package.json
+++ b/tgui/package.json
@@ -13,7 +13,7 @@
},
"dependencies": {
"babel-eslint": "^10.0.3",
- "eslint": "^6.7.2",
+ "eslint": "^7.4.0",
"eslint-plugin-react": "^7.17.0"
}
}
diff --git a/tgui/packages/common/collections.js b/tgui/packages/common/collections.js
index 1e5c978942..b542967d47 100644
--- a/tgui/packages/common/collections.js
+++ b/tgui/packages/common/collections.js
@@ -1,3 +1,9 @@
+/**
+ * @file
+ * @copyright 2020 Aleksej Komarov
+ * @license MIT
+ */
+
/**
* Converts a given collection to an array.
*
diff --git a/tgui/packages/common/fp.js b/tgui/packages/common/fp.js
index 4be45877e2..7aa00a00f3 100644
--- a/tgui/packages/common/fp.js
+++ b/tgui/packages/common/fp.js
@@ -1,3 +1,9 @@
+/**
+ * @file
+ * @copyright 2020 Aleksej Komarov
+ * @license MIT
+ */
+
/**
* Creates a function that returns the result of invoking the given
* functions, where each successive invocation is supplied the return
diff --git a/tgui/packages/common/logging.js b/tgui/packages/common/logging.js
index 4ae1855fda..0dc222ae3d 100644
--- a/tgui/packages/common/logging.js
+++ b/tgui/packages/common/logging.js
@@ -1,3 +1,9 @@
+/**
+ * @file
+ * @copyright 2020 Aleksej Komarov
+ * @license MIT
+ */
+
const inception = Date.now();
// Runtime detection
diff --git a/tgui/packages/common/math.js b/tgui/packages/common/math.js
index cc7a309563..c3013b5a32 100644
--- a/tgui/packages/common/math.js
+++ b/tgui/packages/common/math.js
@@ -1,3 +1,9 @@
+/**
+ * @file
+ * @copyright 2020 Aleksej Komarov
+ * @license MIT
+ */
+
/**
* Limits a number to the range between 'min' and 'max'.
*/
diff --git a/tgui/packages/common/perf.js b/tgui/packages/common/perf.js
new file mode 100644
index 0000000000..319b77cea3
--- /dev/null
+++ b/tgui/packages/common/perf.js
@@ -0,0 +1,44 @@
+/**
+ * Ghetto performance measurement tools.
+ *
+ * Uses NODE_ENV to redact itself from production bundles.
+ *
+ * @file
+ * @copyright 2020 Aleksej Komarov
+ * @license MIT
+ */
+
+let markersByLabel = {};
+
+/**
+ * Marks a certain spot in the code for later measurements.
+ */
+const mark = (label, timestamp) => {
+ if (process.env.NODE_ENV !== 'production') {
+ markersByLabel[label] = timestamp || Date.now();
+ }
+};
+
+/**
+ * Calculates and returns the difference between two markers as a string.
+ *
+ * Use logger.log() to print the measurement.
+ */
+const measure = (markerA, markerB) => {
+ if (process.env.NODE_ENV !== 'production') {
+ return timeDiff(
+ markersByLabel[markerA],
+ markersByLabel[markerB]);
+ }
+};
+
+const timeDiff = (startedAt, finishedAt) => {
+ const diff = Math.abs(finishedAt - startedAt);
+ const diffFrames = (diff / 16.6667).toFixed(2);
+ return `${diff}ms (${diffFrames} frames)`;
+};
+
+export const perf = {
+ mark,
+ measure,
+};
diff --git a/tgui/packages/common/react.js b/tgui/packages/common/react.js
index 0828bb8864..dba84b7b10 100644
--- a/tgui/packages/common/react.js
+++ b/tgui/packages/common/react.js
@@ -1,3 +1,9 @@
+/**
+ * @file
+ * @copyright 2020 Aleksej Komarov
+ * @license MIT
+ */
+
/**
* Helper for conditionally adding/removing classes in React
*
diff --git a/tgui/packages/common/redux.js b/tgui/packages/common/redux.js
index 257a9eebf5..dc486ff7b8 100644
--- a/tgui/packages/common/redux.js
+++ b/tgui/packages/common/redux.js
@@ -1,3 +1,9 @@
+/**
+ * @file
+ * @copyright 2020 Aleksej Komarov
+ * @license MIT
+ */
+
import { compose } from './fp';
/**
@@ -63,3 +69,31 @@ export const applyMiddleware = (...middlewares) => {
};
};
};
+
+/**
+ * Combines reducers by running them in their own object namespaces as
+ * defined in reducersObj paramter.
+ *
+ * Main difference from redux/combineReducers is that it preserves keys
+ * in the state that are not present in the reducers object. This function
+ * is also more flexible than the redux counterpart.
+ */
+export const combineReducers = reducersObj => {
+ const keys = Object.keys(reducersObj);
+ let hasChanged = false;
+ return (prevState, action) => {
+ const nextState = { ...prevState };
+ for (let key of keys) {
+ const reducer = reducersObj[key];
+ const prevDomainState = prevState[key];
+ const nextDomainState = reducer(prevDomainState, action);
+ if (prevDomainState !== nextDomainState) {
+ hasChanged = true;
+ nextState[key] = nextDomainState;
+ }
+ }
+ return hasChanged
+ ? nextState
+ : prevState;
+ };
+};
diff --git a/tgui/packages/common/storage.js b/tgui/packages/common/storage.js
new file mode 100644
index 0000000000..8e1d2183e4
--- /dev/null
+++ b/tgui/packages/common/storage.js
@@ -0,0 +1,76 @@
+/**
+ * Browser-agnostic abstraction of key-value web storage.
+ *
+ * @file
+ * @copyright 2020 Aleksej Komarov
+ * @license MIT
+ */
+
+export const STORAGE_NONE = 0;
+export const STORAGE_LOCAL_STORAGE = 1;
+export const STORAGE_INDEXED_DB = 2;
+
+const createMock = () => {
+ let storage = {};
+ const get = key => storage[key];
+ const set = (key, value) => {
+ storage[key] = value;
+ };
+ const remove = key => {
+ storage[key] = undefined;
+ };
+ const clear = () => {
+ // NOTE: On IE8, this will probably leak memory if used often.
+ storage = {};
+ };
+ return {
+ get,
+ set,
+ remove,
+ clear,
+ engine: STORAGE_NONE,
+ };
+};
+
+const createLocalStorage = () => {
+ const get = key => {
+ const value = localStorage.getItem(key);
+ if (typeof value !== 'string') {
+ return;
+ }
+ return JSON.parse(value);
+ };
+ const set = (key, value) => {
+ localStorage.setItem(key, JSON.stringify(value));
+ };
+ const remove = key => {
+ localStorage.removeItem(key);
+ };
+ const clear = () => {
+ localStorage.clear();
+ };
+ return {
+ get,
+ set,
+ remove,
+ clear,
+ engine: STORAGE_LOCAL_STORAGE,
+ };
+};
+
+const testLocalStorage = () => {
+ // Localstorage can sometimes throw an error, even if DOM storage is not
+ // disabled in IE11 settings.
+ // See: https://superuser.com/questions/1080011
+ try {
+ return Boolean(window.localStorage && window.localStorage.getItem);
+ }
+ catch {
+ return false;
+ }
+};
+
+export const storage = (
+ testLocalStorage() && createLocalStorage()
+ || createMock()
+);
diff --git a/tgui/packages/common/string.babel-plugin.cjs b/tgui/packages/common/string.babel-plugin.cjs
index ba25e3f91d..68295aefcf 100644
--- a/tgui/packages/common/string.babel-plugin.cjs
+++ b/tgui/packages/common/string.babel-plugin.cjs
@@ -1,5 +1,7 @@
/**
- * @file
+ * This plugin saves overall about 10KB on the final bundle size, so it's
+ * sort of worth it.
+ *
* We are using a .cjs extension because:
*
* 1. Webpack CLI only supports CommonJS modules;
@@ -9,8 +11,9 @@
* We need to copy-paste the whole "multiline" function because we can't
* synchronously import an ES module from a CommonJS module.
*
- * This plugin saves overall about 10KB on the final bundle size, so it's
- * sort of worth it.
+ * @file
+ * @copyright 2020 Aleksej Komarov
+ * @license MIT
*/
/**
diff --git a/tgui/packages/common/string.js b/tgui/packages/common/string.js
index d05dbfb6fc..16a0921a25 100644
--- a/tgui/packages/common/string.js
+++ b/tgui/packages/common/string.js
@@ -1,3 +1,9 @@
+/**
+ * @file
+ * @copyright 2020 Aleksej Komarov
+ * @license MIT
+ */
+
/**
* Removes excess whitespace and indentation from the string.
*/
diff --git a/tgui/packages/common/timer.js b/tgui/packages/common/timer.js
index e3feb69ca9..f4e26fa5aa 100644
--- a/tgui/packages/common/timer.js
+++ b/tgui/packages/common/timer.js
@@ -1,3 +1,9 @@
+/**
+ * @file
+ * @copyright 2020 Aleksej Komarov
+ * @license MIT
+ */
+
/**
* Returns a function, that, as long as it continues to be invoked, will
* not be triggered. The function will be called after it stops being
diff --git a/tgui/packages/common/vector.js b/tgui/packages/common/vector.js
index fa98597896..c3ac350a4e 100644
--- a/tgui/packages/common/vector.js
+++ b/tgui/packages/common/vector.js
@@ -1,14 +1,14 @@
-import { map, reduce, zipWith } from './collections';
-
/**
- * Creates a vector, with as many dimensions are there are arguments.
+ * N-dimensional vector manipulation functions.
+ *
+ * Vectors are plain number arrays, i.e. [x, y, z].
+ *
+ * @file
+ * @copyright 2020 Aleksej Komarov
+ * @license MIT
*/
-export const vecCreate = (...components) => {
- if (Array.isArray(components[0])) {
- return [...components[0]];
- }
- return components;
-};
+
+import { map, reduce, zipWith } from './collections';
const ADD = (a, b) => a + b;
const SUB = (a, b) => a - b;
diff --git a/tgui/packages/tgui-dev-server/index.js b/tgui/packages/tgui-dev-server/index.js
index 1e7683080c..f4e8155d29 100644
--- a/tgui/packages/tgui-dev-server/index.js
+++ b/tgui/packages/tgui-dev-server/index.js
@@ -1,3 +1,9 @@
+/**
+ * @file
+ * @copyright 2020 Aleksej Komarov
+ * @license MIT
+ */
+
import { setupWebpack, getWebpackConfig } from './webpack.js';
import { reloadByondCache } from './reloader.js';
diff --git a/tgui/packages/tgui-dev-server/link/client.js b/tgui/packages/tgui-dev-server/link/client.js
index 774b7ed6db..4671b340c5 100644
--- a/tgui/packages/tgui-dev-server/link/client.js
+++ b/tgui/packages/tgui-dev-server/link/client.js
@@ -1,3 +1,9 @@
+/**
+ * @file
+ * @copyright 2020 Aleksej Komarov
+ * @license MIT
+ */
+
let socket;
const queue = [];
const subscribers = [];
@@ -92,8 +98,8 @@ const sendRawMessage = msg => {
socket.send(json);
}
else {
- // Keep only 10 latest messages in the queue
- if (queue.length > 10) {
+ // Keep only 100 latest messages in the queue
+ if (queue.length > 100) {
queue.shift();
}
queue.push(json);
diff --git a/tgui/packages/tgui-dev-server/link/retrace.js b/tgui/packages/tgui-dev-server/link/retrace.js
index 74c87e6a55..e0b17a01d6 100644
--- a/tgui/packages/tgui-dev-server/link/retrace.js
+++ b/tgui/packages/tgui-dev-server/link/retrace.js
@@ -1,3 +1,9 @@
+/**
+ * @file
+ * @copyright 2020 Aleksej Komarov
+ * @license MIT
+ */
+
import { createLogger } from 'common/logging.js';
import fs from 'fs';
import { basename } from 'path';
diff --git a/tgui/packages/tgui-dev-server/link/server.js b/tgui/packages/tgui-dev-server/link/server.js
index 84f3700048..94a79c9ad5 100644
--- a/tgui/packages/tgui-dev-server/link/server.js
+++ b/tgui/packages/tgui-dev-server/link/server.js
@@ -1,3 +1,9 @@
+/**
+ * @file
+ * @copyright 2020 Aleksej Komarov
+ * @license MIT
+ */
+
import { createLogger, directLog } from 'common/logging.js';
import http from 'http';
import { inspect } from 'util';
diff --git a/tgui/packages/tgui-dev-server/reloader.js b/tgui/packages/tgui-dev-server/reloader.js
index 394e55afdb..e33f7226b9 100644
--- a/tgui/packages/tgui-dev-server/reloader.js
+++ b/tgui/packages/tgui-dev-server/reloader.js
@@ -1,9 +1,16 @@
+/**
+ * @file
+ * @copyright 2020 Aleksej Komarov
+ * @license MIT
+ */
+
import { createLogger } from 'common/logging.js';
import fs from 'fs';
import os from 'os';
import { basename } from 'path';
import { promisify } from 'util';
import { resolveGlob, resolvePath } from './util.js';
+import { regQuery } from './winreg.js';
const logger = createLogger('reloader');
@@ -40,6 +47,21 @@ export const findCacheRoot = async () => {
return cacheRoot;
}
}
+ // Query the Windows Registry
+ if (process.platform === 'win32') {
+ logger.log('querying windows registry');
+ let userpath = await regQuery(
+ 'HKCU\\Software\\Dantom\\BYOND',
+ 'userpath');
+ if (userpath) {
+ cacheRoot = userpath
+ .replace(/\\$/, '')
+ .replace(/\\/g, '/')
+ + '/cache';
+ logger.log(`found cache at '${cacheRoot}'`);
+ return cacheRoot;
+ }
+ }
logger.log('found no cache directories');
};
diff --git a/tgui/packages/tgui-dev-server/util.js b/tgui/packages/tgui-dev-server/util.js
index db34626721..50c0baad5a 100644
--- a/tgui/packages/tgui-dev-server/util.js
+++ b/tgui/packages/tgui-dev-server/util.js
@@ -1,3 +1,9 @@
+/**
+ * @file
+ * @copyright 2020 Aleksej Komarov
+ * @license MIT
+ */
+
import glob from 'glob';
import { resolve as resolvePath } from 'path';
import fs from 'fs';
diff --git a/tgui/packages/tgui-dev-server/webpack.js b/tgui/packages/tgui-dev-server/webpack.js
index 778469a15b..c625827409 100644
--- a/tgui/packages/tgui-dev-server/webpack.js
+++ b/tgui/packages/tgui-dev-server/webpack.js
@@ -1,3 +1,9 @@
+/**
+ * @file
+ * @copyright 2020 Aleksej Komarov
+ * @license MIT
+ */
+
import { createLogger } from 'common/logging.js';
import fs from 'fs';
import { createRequire } from 'module';
diff --git a/tgui/packages/tgui-dev-server/winreg.js b/tgui/packages/tgui-dev-server/winreg.js
new file mode 100644
index 0000000000..974135e76d
--- /dev/null
+++ b/tgui/packages/tgui-dev-server/winreg.js
@@ -0,0 +1,47 @@
+/**
+ * Tools for dealing with Windows Registry bullshit.
+ *
+ * @file
+ * @copyright 2020 Aleksej Komarov
+ * @license MIT
+ */
+
+import { exec } from 'child_process';
+import { createLogger } from 'common/logging.js';
+import { promisify } from 'util';
+
+const logger = createLogger('winreg');
+
+export const regQuery = async (path, key) => {
+ if (process.platform !== 'win32') {
+ return null;
+ }
+ try {
+ const command = `reg query "${path}" /v ${key}`;
+ const { stdout } = await promisify(exec)(command);
+ const keyPattern = ` ${key} `;
+ const indexOfKey = stdout.indexOf(keyPattern);
+ if (indexOfKey === -1) {
+ logger.error('could not find the registry key');
+ return null;
+ }
+ const indexOfEol = stdout.indexOf('\r\n', indexOfKey);
+ if (indexOfEol === -1) {
+ logger.error('could not find the end of the line');
+ return null;
+ }
+ const indexOfValue = stdout.indexOf(
+ ' ',
+ indexOfKey + keyPattern.length);
+ if (indexOfValue === -1) {
+ logger.error('could not find the start of the key value');
+ return null;
+ }
+ const value = stdout.substring(indexOfValue + 4, indexOfEol);
+ return value;
+ }
+ catch (err) {
+ logger.error(err);
+ return null;
+ }
+};
diff --git a/tgui/packages/tgui/assets.js b/tgui/packages/tgui/assets.js
new file mode 100644
index 0000000000..b0f71bb9ff
--- /dev/null
+++ b/tgui/packages/tgui/assets.js
@@ -0,0 +1,54 @@
+/**
+ * @file
+ * @copyright 2020 Aleksej Komarov
+ * @license MIT
+ */
+
+import { loadCSS as fgLoadCSS } from 'fg-loadcss';
+import { createLogger } from './logging';
+
+const logger = createLogger('assets');
+
+const EXCLUDED_PATTERNS = [
+ /v4shim/i,
+];
+
+const loadedStyles = [];
+const loadedMappings = {};
+
+export const loadCSS = url => {
+ if (loadedStyles.includes(url)) {
+ return;
+ }
+ loadedStyles.push(url);
+ logger.log(`loading stylesheet '${url}'`);
+ fgLoadCSS(url);
+};
+
+export const resolveAsset = name => (
+ loadedMappings[name] || name
+);
+
+export const assetMiddleware = store => next => action => {
+ const { type, payload } = action;
+ if (type === 'asset/stylesheet') {
+ loadCSS(payload);
+ return;
+ }
+ if (type === 'asset/mappings') {
+ for (let name of Object.keys(payload)) {
+ // Skip anything that matches excluded patterns
+ if (EXCLUDED_PATTERNS.some(regex => regex.test(name))) {
+ continue;
+ }
+ const url = payload[name];
+ const ext = name.split('.').pop();
+ loadedMappings[name] = url;
+ if (ext === 'css') {
+ loadCSS(url);
+ }
+ }
+ return;
+ }
+ next(action);
+};
diff --git a/tgui/packages/tgui/assets/bg-neutral.svg b/tgui/packages/tgui/assets/bg-neutral.svg
new file mode 100644
index 0000000000..1c397616e8
--- /dev/null
+++ b/tgui/packages/tgui/assets/bg-neutral.svg
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/tgui/packages/tgui/backend.js b/tgui/packages/tgui/backend.js
index 0c35483487..dab86ee918 100644
--- a/tgui/packages/tgui/backend.js
+++ b/tgui/packages/tgui/backend.js
@@ -5,10 +5,18 @@
* Sometimes backend can response without a "data" field, but our final
* state will still contain previous "data" because we are merging
* the response with already existing state.
+ *
+ * @file
+ * @copyright 2020 Aleksej Komarov
+ * @license MIT
*/
+import { perf } from 'common/perf';
import { UI_DISABLED, UI_INTERACTIVE } from './constants';
-import { callByond } from './byond';
+import { releaseHeldKeys } from './hotkeys';
+import { createLogger } from './logging';
+
+const logger = createLogger('backend');
export const backendUpdate = state => ({
type: 'backend/update',
@@ -20,7 +28,23 @@ export const backendSetSharedState = (key, nextState) => ({
payload: { key, nextState },
});
-export const backendReducer = (state, action) => {
+export const backendSuspendStart = () => ({
+ type: 'backend/suspendStart',
+});
+
+export const backendSuspendSuccess = () => ({
+ type: 'backend/suspendSuccess',
+ payload: {
+ timestamp: Date.now(),
+ },
+});
+
+const initialState = {
+ config: {},
+ data: {},
+};
+
+export const backendReducer = (state = initialState, action) => {
const { type, payload } = action;
if (type === 'backend/update') {
@@ -59,6 +83,7 @@ export const backendReducer = (state, action) => {
shared,
visible,
interactive,
+ suspended: false,
};
}
@@ -73,30 +98,175 @@ export const backendReducer = (state, action) => {
};
}
+ if (type === 'backend/suspendStart') {
+ return {
+ ...state,
+ suspending: true,
+ };
+ }
+
+ if (type === 'backend/suspendSuccess') {
+ const { timestamp } = payload;
+ return {
+ ...state,
+ data: {},
+ shared: {},
+ config: {
+ ...state.config,
+ title: '',
+ status: 1,
+ },
+ suspending: false,
+ suspended: timestamp,
+ };
+ }
+
return state;
};
+export const backendMiddleware = store => {
+ let fancyState;
+ let suspendInterval;
+
+ return next => action => {
+ const { config, suspended } = selectBackend(store.getState());
+ const { type, payload } = action;
+
+ if (type === 'backend/suspendStart' && !suspendInterval) {
+ logger.log(`suspending (${window.__windowId__})`);
+ // Keep sending suspend messages until it succeeds.
+ // It may fail multiple times due to topic rate limiting.
+ const suspendFn = () => sendMessage({
+ type: 'suspend',
+ });
+ suspendFn();
+ suspendInterval = setInterval(suspendFn, 2000);
+ }
+
+ if (type === 'backend/suspendSuccess') {
+ clearInterval(suspendInterval);
+ suspendInterval = undefined;
+ releaseHeldKeys();
+ Byond.winset(window.__windowId__, {
+ 'is-visible': false,
+ });
+ }
+
+ if (type === 'backend/update') {
+ const fancy = payload.config?.window?.fancy;
+ // Initialize fancy state
+ if (fancyState === undefined) {
+ fancyState = fancy;
+ }
+ // React to changes in fancy
+ else if (fancyState !== fancy) {
+ logger.log('changing fancy mode to', fancy);
+ fancyState = fancy;
+ Byond.winset(window.__windowId__, {
+ titlebar: !fancy,
+ 'can-resize': !fancy,
+ });
+ }
+ }
+
+ if (type === 'backend/update' && suspended) {
+ // We schedule this for the next tick here because resizing and unhiding
+ // during the same tick will flash with a white background.
+ setImmediate(() => {
+ perf.mark('resume/start');
+ // Doublecheck if we are not re-suspended.
+ const { suspended } = selectBackend(store.getState());
+ if (suspended) {
+ return;
+ }
+ Byond.winset(window.__windowId__, {
+ 'is-visible': true,
+ });
+ perf.mark('resume/finish');
+ if (process.env.NODE_ENV !== 'production') {
+ logger.log('visible in',
+ perf.measure('render/finish', 'resume/finish'));
+ }
+ });
+ }
+
+ return next(action);
+ };
+};
+
+/**
+ * Sends a message to /datum/tgui_window.
+ */
+export const sendMessage = (message = {}) => {
+ const { payload, ...rest } = message;
+ const data = {
+ // Message identifying header
+ tgui: 1,
+ window_id: window.__windowId__,
+ // Message body
+ ...rest,
+ };
+ // JSON-encode the payload
+ if (payload !== null && payload !== undefined) {
+ data.payload = JSON.stringify(payload);
+ }
+ Byond.topic(data);
+};
+
+/**
+ * Sends an action to `ui_act` on `src_object` that this tgui window
+ * is associated with.
+ */
+export const sendAct = (action, payload = {}) => {
+ // Validate that payload is an object
+ const isObject = typeof payload === 'object'
+ && payload !== null
+ && !Array.isArray(payload);
+ if (!isObject) {
+ logger.error(`Payload for act() must be an object, got this:`, payload);
+ return;
+ }
+ sendMessage({
+ type: 'act/' + action,
+ payload,
+ });
+};
+
/**
* @typedef BackendState
* @type {{
* config: {
* title: string,
* status: number,
- * screen: string,
- * style: string,
* interface: string,
- * fancy: number,
- * locked: number,
- * observer: number,
- * window: string,
- * ref: string,
+ * user: {
+ * name: string,
+ * ckey: string,
+ * observer: number,
+ * },
+ * window: {
+ * key: string,
+ * size: [number, number],
+ * fancy: boolean,
+ * locked: boolean,
+ * },
* },
* data: any,
+ * shared: any,
* visible: boolean,
* interactive: boolean,
+ * suspending: boolean,
+ * suspended: boolean,
* }}
*/
+/**
+ * Selects a backend-related slice of Redux state
+ *
+ * @return {BackendState}
+ */
+export const selectBackend = state => state.backend || {};
+
/**
* A React hook (sort of) for getting tgui state and related functions.
*
@@ -104,21 +274,16 @@ export const backendReducer = (state, action) => {
* be used in functional components.
*
* @return {BackendState & {
- * act: (action: string, params?: object) => void,
+ * act: sendAct,
* }}
*/
export const useBackend = context => {
const { store } = context;
- const state = store.getState();
- const ref = state.config.ref;
- const act = (action, params = {}) => {
- callByond('', {
- src: ref,
- action,
- ...params,
- });
+ const state = selectBackend(store.getState());
+ return {
+ ...state,
+ act: sendAct,
};
- return { ...state, act };
};
/**
@@ -136,7 +301,7 @@ export const useBackend = context => {
*/
export const useLocalState = (context, key, initialState) => {
const { store } = context;
- const state = store.getState();
+ const state = selectBackend(store.getState());
const sharedStates = state.shared ?? {};
const sharedState = (key in sharedStates)
? sharedStates[key]
@@ -144,7 +309,11 @@ export const useLocalState = (context, key, initialState) => {
return [
sharedState,
nextState => {
- store.dispatch(backendSetSharedState(key, nextState));
+ store.dispatch(backendSetSharedState(key, (
+ typeof nextState === 'function'
+ ? nextState(sharedState)
+ : nextState
+ )));
},
];
};
@@ -165,8 +334,7 @@ export const useLocalState = (context, key, initialState) => {
*/
export const useSharedState = (context, key, initialState) => {
const { store } = context;
- const state = store.getState();
- const ref = state.config.ref;
+ const state = selectBackend(store.getState());
const sharedStates = state.shared ?? {};
const sharedState = (key in sharedStates)
? sharedStates[key]
@@ -174,11 +342,14 @@ export const useSharedState = (context, key, initialState) => {
return [
sharedState,
nextState => {
- callByond('', {
- src: ref,
- action: 'tgui:setSharedState',
+ sendMessage({
+ type: 'setSharedState',
key,
- value: JSON.stringify(nextState) || '',
+ value: JSON.stringify(
+ typeof nextState === 'function'
+ ? nextState(sharedState)
+ : nextState
+ ) || '',
});
},
];
diff --git a/tgui/packages/tgui/byond.js b/tgui/packages/tgui/byond.js
index bd70056613..fa2b03e715 100644
--- a/tgui/packages/tgui/byond.js
+++ b/tgui/packages/tgui/byond.js
@@ -1,3 +1,9 @@
+/**
+ * @file
+ * @copyright 2020 Aleksej Komarov
+ * @license MIT
+ */
+
// Reference a global Byond object
const { Byond } = window;
diff --git a/tgui/packages/tgui/components/Box.js b/tgui/packages/tgui/components/Box.js
index cb86890a97..a85c692e9a 100644
--- a/tgui/packages/tgui/components/Box.js
+++ b/tgui/packages/tgui/components/Box.js
@@ -9,17 +9,22 @@ import { createVNode } from 'inferno';
import { ChildFlags, VNodeFlags } from 'inferno-vnode-flags';
import { CSS_COLORS } from '../constants';
-const UNIT_PX = 12;
-
/**
* Coverts our rem-like spacing unit into a CSS unit.
*/
export const unit = value => {
if (typeof value === 'string') {
+ // Transparently convert pixels into rem units
+ if (value.endsWith('px') && !Byond.IS_LTE_IE8) {
+ return parseFloat(value) / 12 + 'rem';
+ }
return value;
}
if (typeof value === 'number') {
- return (value * UNIT_PX) + 'px';
+ if (Byond.IS_LTE_IE8) {
+ return value * 12 + 'px';
+ }
+ return value + 'rem';
}
};
@@ -28,10 +33,10 @@ export const unit = value => {
*/
export const halfUnit = value => {
if (typeof value === 'string') {
- return value;
+ return unit(value);
}
if (typeof value === 'number') {
- return (value * UNIT_PX * 0.5) + 'px';
+ return unit(value * 0.5);
}
};
@@ -90,7 +95,13 @@ const styleMapperByPropName = {
maxHeight: mapUnitPropTo('max-height', unit),
fontSize: mapUnitPropTo('font-size', unit),
fontFamily: mapRawPropTo('font-family'),
- lineHeight: mapRawPropTo('line-height'),
+ lineHeight: (style, value) => {
+ if (!isFalsy(value)) {
+ style['line-height'] = typeof value === 'number'
+ ? value
+ : unit(value);
+ }
+ },
opacity: mapRawPropTo('opacity'),
textAlign: mapRawPropTo('text-align'),
verticalAlign: mapRawPropTo('vertical-align'),
diff --git a/tgui/packages/tgui/components/Button.js b/tgui/packages/tgui/components/Button.js
index 52adf286ad..bb8b4bcbf0 100644
--- a/tgui/packages/tgui/components/Button.js
+++ b/tgui/packages/tgui/components/Button.js
@@ -6,7 +6,6 @@
import { classes, pureComponentHooks } from 'common/react';
import { Component, createRef } from 'inferno';
-import { IS_IE8 } from '../byond';
import { KEY_ENTER, KEY_ESCAPE, KEY_SPACE } from '../hotkeys';
import { refocusLayout } from '../layouts';
import { createLogger } from '../logging';
@@ -61,7 +60,7 @@ export const Button = props => {
className,
])}
tabIndex={!disabled && '0'}
- unselectable={IS_IE8}
+ unselectable={Byond.IS_LTE_IE8}
onclick={e => {
refocusLayout();
if (!disabled && onClick) {
@@ -87,7 +86,10 @@ export const Button = props => {
}}
{...rest}>
{icon && (
-
+
)}
{content}
{children}
diff --git a/tgui/packages/tgui/components/ByondUi.js b/tgui/packages/tgui/components/ByondUi.js
index db9c5822ba..2369cc7993 100644
--- a/tgui/packages/tgui/components/ByondUi.js
+++ b/tgui/packages/tgui/components/ByondUi.js
@@ -7,7 +7,6 @@
import { shallowDiffers } from 'common/react';
import { debounce } from 'common/timer';
import { Component, createRef } from 'inferno';
-import { callByond, IS_IE8 } from '../byond';
import { createLogger } from '../logging';
import { computeBoxProps } from './Box';
@@ -28,16 +27,12 @@ const createByondUiElement = elementId => {
render: params => {
logger.log(`rendering '${id}'`);
byondUiStack[index] = id;
- callByond('winset', {
- ...params,
- id,
- });
+ Byond.winset(id, params);
},
unmount: () => {
logger.log(`unmounting '${id}'`);
byondUiStack[index] = null;
- callByond('winset', {
- id,
+ Byond.winset(id, {
parent: '',
});
},
@@ -51,8 +46,7 @@ window.addEventListener('beforeunload', () => {
if (typeof id === 'string') {
logger.log(`unmounting '${id}' (beforeunload)`);
byondUiStack[index] = null;
- callByond('winset', {
- id,
+ Byond.winset(id, {
parent: '',
});
}
@@ -83,7 +77,7 @@ export class ByondUi extends Component {
this.byondUiElement = createByondUiElement(props.params?.id);
this.handleResize = debounce(() => {
this.forceUpdate();
- }, 500);
+ }, 100);
}
shouldComponentUpdate(nextProps) {
@@ -101,16 +95,17 @@ export class ByondUi extends Component {
componentDidMount() {
// IE8: It probably works, but fuck you anyway.
- if (IS_IE8) {
+ if (Byond.IS_LTE_IE10) {
return;
}
window.addEventListener('resize', this.handleResize);
- return this.componentDidUpdate();
+ this.componentDidUpdate();
+ this.handleResize();
}
componentDidUpdate() {
// IE8: It probably works, but fuck you anyway.
- if (IS_IE8) {
+ if (Byond.IS_LTE_IE10) {
return;
}
const {
@@ -119,6 +114,7 @@ export class ByondUi extends Component {
const box = getBoundingBox(this.containerRef.current);
logger.log('bounding box', box);
this.byondUiElement.render({
+ parent: window.__windowId__,
...params,
pos: box.pos[0] + ',' + box.pos[1],
size: box.size[0] + 'x' + box.size[1],
@@ -127,7 +123,7 @@ export class ByondUi extends Component {
componentWillUnmount() {
// IE8: It probably works, but fuck you anyway.
- if (IS_IE8) {
+ if (Byond.IS_LTE_IE10) {
return;
}
window.removeEventListener('resize', this.handleResize);
@@ -135,26 +131,16 @@ export class ByondUi extends Component {
}
render() {
- const {
- parent,
- params,
- ...rest
- } = this.props;
+ const { params, ...rest } = this.props;
const type = params?.type;
const boxProps = computeBoxProps(rest);
return (
- {type === 'button' && }
+ {/* Filler */}
+
);
}
}
-
-const ButtonMock = () => (
-
-);
diff --git a/tgui/packages/tgui/components/Chart.js b/tgui/packages/tgui/components/Chart.js
index 16ed82c355..77913779db 100644
--- a/tgui/packages/tgui/components/Chart.js
+++ b/tgui/packages/tgui/components/Chart.js
@@ -7,7 +7,6 @@
import { map, zipWith } from 'common/collections';
import { pureComponentHooks } from 'common/react';
import { Component, createRef } from 'inferno';
-import { IS_IE8 } from '../byond';
import { Box } from './Box';
const normalizeData = (data, scale, rangeX, rangeY) => {
@@ -123,5 +122,5 @@ const Stub = props => null;
// IE8: No inline svg support
export const Chart = {
- Line: IS_IE8 ? Stub : LineChart,
+ Line: Byond.IS_LTE_IE8 ? Stub : LineChart,
};
diff --git a/tgui/packages/tgui/components/Flex.js b/tgui/packages/tgui/components/Flex.js
index 4ee69a1902..02d2fac314 100644
--- a/tgui/packages/tgui/components/Flex.js
+++ b/tgui/packages/tgui/components/Flex.js
@@ -5,7 +5,6 @@
*/
import { classes, pureComponentHooks } from 'common/react';
-import { IS_IE8 } from '../byond';
import { Box, unit } from './Box';
export const computeFlexProps = props => {
@@ -22,10 +21,10 @@ export const computeFlexProps = props => {
return {
className: classes([
'Flex',
- IS_IE8 && (
+ Byond.IS_LTE_IE10 && (
direction === 'column'
- ? 'Flex--ie8--column'
- : 'Flex--ie8'
+ ? 'Flex--iefix--column'
+ : 'Flex--iefix'
),
inline && 'Flex--inline',
spacing > 0 && 'Flex--spacing--' + spacing,
@@ -63,7 +62,7 @@ export const computeFlexItemProps = props => {
return {
className: classes([
'Flex__item',
- IS_IE8 && 'Flex__item--ie8',
+ Byond.IS_LTE_IE10 && 'Flex__item--iefix',
className,
]),
style: {
diff --git a/tgui/packages/tgui/components/Knob.js b/tgui/packages/tgui/components/Knob.js
index e72b15f195..4861e71bf3 100644
--- a/tgui/packages/tgui/components/Knob.js
+++ b/tgui/packages/tgui/components/Knob.js
@@ -6,7 +6,6 @@
import { keyOfMatchingRange, scale } from 'common/math';
import { classes } from 'common/react';
-import { IS_IE8 } from '../byond';
import { computeBoxClassName, computeBoxProps } from './Box';
import { DraggableControl } from './DraggableControl';
import { NumberInput } from './NumberInput';
@@ -14,7 +13,7 @@ import { NumberInput } from './NumberInput';
export const Knob = props => {
// IE8: I don't want to support a yet another component on IE8.
// IE8: It also can't handle SVG.
- if (IS_IE8) {
+ if (Byond.IS_LTE_IE8) {
return (
);
diff --git a/tgui/packages/tgui/components/NumberInput.js b/tgui/packages/tgui/components/NumberInput.js
index 806c81d542..cba6f5025e 100644
--- a/tgui/packages/tgui/components/NumberInput.js
+++ b/tgui/packages/tgui/components/NumberInput.js
@@ -7,7 +7,6 @@
import { clamp } from 'common/math';
import { classes, pureComponentHooks } from 'common/react';
import { Component, createRef } from 'inferno';
-import { IS_IE8 } from '../byond';
import { AnimatedNumber } from './AnimatedNumber';
import { Box } from './Box';
@@ -168,7 +167,7 @@ export class NumberInput extends Component {
const renderContentElement = value => (