diff --git a/code/__DEFINES/construction.dm b/code/__DEFINES/construction.dm
index fc5b6eb74b9..1ef94c8ba65 100644
--- a/code/__DEFINES/construction.dm
+++ b/code/__DEFINES/construction.dm
@@ -28,6 +28,12 @@
#define AIRLOCK_ASSEMBLY_NEEDS_ELECTRONICS 1
#define AIRLOCK_ASSEMBLY_NEEDS_SCREWDRIVER 2
+//used by airlocks and airlock wires.
+#define AICONTROLDISABLED_OFF 0 // Silicons can control the airlock normally.
+#define AICONTROLDISABLED_ON 1 // Silicons cannot control the airlock, but can hack the airlock.
+#define AICONTROLDISABLED_BYPASS 2 // Silicons can control the airlock because they succeeded on the hack
+#define AICONTROLDISABLED_PERMA 3 // Wire cutting an airlock on AICONTROLDISABLED_BYPASS toggles it between AICONTROLDISABLED_BYPASS and this.
+
//plastic flaps construction states
#define PLASTIC_FLAPS_NORMAL 0
#define PLASTIC_FLAPS_DETACHED 1
diff --git a/code/__DEFINES/instruments.dm b/code/__DEFINES/instruments.dm
new file mode 100644
index 00000000000..64c77abc9fa
--- /dev/null
+++ b/code/__DEFINES/instruments.dm
@@ -0,0 +1,29 @@
+#define INSTRUMENT_MIN_OCTAVE 1
+#define INSTRUMENT_MAX_OCTAVE 9
+#define INSTRUMENT_MIN_KEY 0
+#define INSTRUMENT_MAX_KEY 127
+
+/// Max number of playing notes per instrument.
+#define CHANNELS_PER_INSTRUMENT 128
+
+/// Distance multiplier that makes us not be impacted by 3d sound as much. This is a multiplier so lower it is the closer we will pretend to be to people.
+#define INSTRUMENT_DISTANCE_FALLOFF_BUFF 0.2
+/// How many tiles instruments have no falloff for
+#define INSTRUMENT_DISTANCE_NO_FALLOFF 3
+
+/// Maximum length a note should ever go for
+#define INSTRUMENT_MAX_TOTAL_SUSTAIN (5 SECONDS)
+
+/// These are per decisecond.
+#define INSTRUMENT_EXP_FALLOFF_MIN 1.025 //100/(1.025^50) calculated for [INSTRUMENT_MIN_SUSTAIN_DROPOFF] to be 30.
+#define INSTRUMENT_EXP_FALLOFF_MAX 10
+
+/// Minimum volume for when the sound is considered dead.
+#define INSTRUMENT_MIN_SUSTAIN_DROPOFF 0.1
+
+#define SUSTAIN_LINEAR 1
+#define SUSTAIN_EXPONENTIAL 2
+
+// /datum/instrument instrument_flags
+#define INSTRUMENT_LEGACY (1<<0) //Legacy instrument. Implies INSTRUMENT_DO_NOT_AUTOSAMPLE
+#define INSTRUMENT_DO_NOT_AUTOSAMPLE (1<<1) //Do not automatically sample
diff --git a/code/__DEFINES/machines.dm b/code/__DEFINES/machines.dm
index b56024bfab8..070aaaa2249 100644
--- a/code/__DEFINES/machines.dm
+++ b/code/__DEFINES/machines.dm
@@ -92,3 +92,8 @@
// Firelock states
#define FD_OPEN 1
#define FD_CLOSED 2
+
+// Computer login types
+#define LOGIN_TYPE_NORMAL 1
+#define LOGIN_TYPE_AI 2
+#define LOGIN_TYPE_ROBOT 3
diff --git a/code/__DEFINES/sound.dm b/code/__DEFINES/sound.dm
index c15dfd78b0f..3fcb9cc3410 100644
--- a/code/__DEFINES/sound.dm
+++ b/code/__DEFINES/sound.dm
@@ -12,6 +12,7 @@
#define CHANNEL_HIGHEST_AVAILABLE 1017
+#define MAX_INSTRUMENT_CHANNELS (128 * 6)
#define SOUND_MINIMUM_PRESSURE 10
#define FALLOFF_SOUNDS 0.5
diff --git a/code/__DEFINES/subsystems.dm b/code/__DEFINES/subsystems.dm
index fd7031409d8..6e3603f83a3 100644
--- a/code/__DEFINES/subsystems.dm
+++ b/code/__DEFINES/subsystems.dm
@@ -45,11 +45,13 @@
// Subsystems shutdown in the reverse of the order they initialize in
// The numbers just define the ordering, they are meaningless otherwise.
#define INIT_ORDER_TITLE 100 // This **MUST** load first or people will se blank lobby screens
-#define INIT_ORDER_GARBAGE 19
-#define INIT_ORDER_DBCORE 18
-#define INIT_ORDER_BLACKBOX 17
-#define INIT_ORDER_SERVER_MAINT 16
-#define INIT_ORDER_INPUT 15
+#define INIT_ORDER_GARBAGE 21
+#define INIT_ORDER_DBCORE 20
+#define INIT_ORDER_BLACKBOX 19
+#define INIT_ORDER_SERVER_MAINT 18
+#define INIT_ORDER_INPUT 17
+#define INIT_ORDER_SOUNDS 16
+#define INIT_ORDER_INSTRUMENTS 15
#define INIT_ORDER_RESEARCH 14
#define INIT_ORDER_EVENTS 13
#define INIT_ORDER_JOBS 12
diff --git a/code/__DEFINES/tgui.dm b/code/__DEFINES/tgui.dm
new file mode 100644
index 00000000000..0ef159f11c2
--- /dev/null
+++ b/code/__DEFINES/tgui.dm
@@ -0,0 +1,8 @@
+// TGUI defines
+#define TGUI_MODAL_INPUT_MAX_LENGTH 1024
+#define TGUI_MODAL_INPUT_MAX_LENGTH_NAME 64 // Names for generally anything don't go past 32, let alone 64.
+
+#define TGUI_MODAL_OPEN 1
+#define TGUI_MODAL_DELEGATE 2
+#define TGUI_MODAL_ANSWER 3
+#define TGUI_MODAL_CLOSE 4
diff --git a/code/_onclick/ai.dm b/code/_onclick/ai.dm
index 606773143fc..777a7625fe7 100644
--- a/code/_onclick/ai.dm
+++ b/code/_onclick/ai.dm
@@ -145,12 +145,10 @@
/mob/living/silicon/ai/MiddleClickOn(var/atom/A)
A.AIMiddleClick(src)
-/*
- The following criminally helpful code is just the previous code cleaned up;
- I have no idea why it was in atoms.dm instead of respective files.
-*/
-/atom/proc/AICtrlShiftClick(var/mob/user) // Examines
+// DEFAULT PROCS TO OVERRIDE
+
+/atom/proc/AICtrlShiftClick(mob/user) // Examines
if(user.client)
user.examinate(src)
return
@@ -158,70 +156,82 @@
/atom/proc/AIAltShiftClick()
return
-/obj/machinery/door/airlock/AIAltShiftClick() // Sets/Unsets Emergency Access Override
- if(density)
- Topic(src, list("src" = UID(), "command"="emergency", "activate" = "1"), 1) // 1 meaning no window (consistency!)
- else
- Topic(src, list("src" = UID(), "command"="emergency", "activate" = "0"), 1)
- return
-
-/atom/proc/AIShiftClick(var/mob/user)
+/atom/proc/AIShiftClick(mob/living/user) // borgs use this too
if(user.client)
user.examinate(src)
return
-/obj/machinery/door/airlock/AIShiftClick() // Opens and closes doors!
- if(density)
- Topic(src, list("src" = UID(), "command"="open", "activate" = "1"), 1) // 1 meaning no window (consistency!)
- else
- Topic(src, list("src" = UID(), "command"="open", "activate" = "0"), 1)
+/atom/proc/AICtrlClick(mob/living/silicon/ai/user)
return
-/atom/proc/AICtrlClick(var/mob/living/silicon/ai/user)
- return
-
-/obj/machinery/door/airlock/AICtrlClick() // Bolts doors
- if(locked)
- Topic(src, list("src" = UID(), "command"="bolts", "activate" = "0"), 1)// 1 meaning no window (consistency!)
- else
- Topic(src, list("src" = UID(), "command"="bolts", "activate" = "1"), 1)
-
-/obj/machinery/power/apc/AICtrlClick() // turns off/on APCs.
- Topic("breaker=1", list("breaker"="1"), 0) // 0 meaning no window (consistency! wait...)
-
-/obj/machinery/turretid/AICtrlClick() //turns off/on Turrets
- Topic(src, list("src" = UID(), "command"="enable", "value"="[!enabled]"), 1) // 1 meaning no window (consistency!)
-
-/atom/proc/AIAltClick(var/atom/A)
+/atom/proc/AIAltClick(atom/A)
AltClick(A)
-/obj/machinery/door/airlock/AIAltClick() // Electrifies doors.
- if(!electrified_until)
- // permanent shock
- Topic(src, list("src" = UID(), "command"="electrify_permanently", "activate" = "1"), 1) // 1 meaning no window (consistency!)
- else
- // disable/6 is not in Topic; disable/5 disables both temporary and permanent shock
- Topic(src, list("src" = UID(), "command"="electrify_permanently", "activate" = "0"), 1)
+/atom/proc/AIMiddleClick(mob/living/user)
return
+/mob/living/silicon/ai/TurfAdjacent(turf/T)
+ return (GLOB.cameranet && GLOB.cameranet.checkTurfVis(T))
+
+
+// APC
+
+/obj/machinery/power/apc/AICtrlClick(mob/living/user) // turns off/on APCs.
+ toggle_breaker(user)
+
+
+// TURRETCONTROL
+
+/obj/machinery/turretid/AICtrlClick(mob/living/silicon/ai/user) //turns off/on Turrets
+ enabled = !enabled
+ updateTurrets()
+
/obj/machinery/turretid/AIAltClick() //toggles lethal on turrets
- Topic(src, list("src" = UID(), "command"="lethal", "value"="[!lethal]"), 1) // 1 meaning no window (consistency!)
+ if(lethal_is_configurable)
+ lethal = !lethal
+ updateTurrets()
-/atom/proc/AIMiddleClick()
- return
+// AIRLOCKS
-/obj/machinery/door/airlock/AIMiddleClick() // Toggles door bolt lights.
- if(!src.lights)
- Topic(src, list("src" = UID(), "command"="lights", "activate" = "1"), 1) // 1 meaning no window (consistency!)
+/obj/machinery/door/airlock/AIAltShiftClick(mob/user) // Sets/Unsets Emergency Access Override
+ emergency = !emergency
+ update_icon()
+
+/obj/machinery/door/airlock/AIShiftClick(mob/user) // Opens and closes doors!
+ if(welded)
+ to_chat(user, "The airlock has been welded shut!")
+ if(locked)
+ locked = !locked
+ if(density)
+ open()
else
- Topic(src, list("src" = UID(), "command"="lights", "activate" = "0"), 1)
- return
+ close()
-/obj/machinery/ai_slipper/AICtrlClick() //Turns liquid dispenser on or off
- ToggleOn()
+/obj/machinery/door/airlock/AICtrlClick(mob/living/silicon/ai/user) // Bolts doors
+ locked = !locked
+ update_icon()
-/obj/machinery/ai_slipper/AIAltClick() //Dispenses liquid if on
- Activate()
+/obj/machinery/door/airlock/AIAltClick(mob/living/silicon/ai/user) // Electrifies doors.
+ if(wires.is_cut(WIRE_ELECTRIFY))
+ to_chat(user, "The electrification wire is cut - Cannot electrify the door.")
+ if(isElectrified())
+ electrify(0) // un-shock
+ else
+ electrify(-1) // permanent shock
+
+
+/obj/machinery/door/airlock/AIMiddleClick(mob/living/user) // Toggles door bolt lights.
+ if(wires.is_cut(WIRE_BOLT_LIGHT))
+ to_chat(user, "The bolt lights wire has been cut - The door bolt lights are permanently disabled.")
+ else if(lights)
+ lights = FALSE
+ to_chat(user, "The door bolt lights have been disabled.")
+ else if(!lights)
+ lights = TRUE
+ to_chat(user, "The door bolt lights have been enabled.")
+ update_icon()
+
+// FIRE ALARMS
/obj/machinery/firealarm/AICtrlClick()
if(enabled)
@@ -229,9 +239,10 @@
else
alarm()
-//
-// Override AdjacentQuick for AltClicking
-//
+// AI-CONTROLLED SLIP GENERATOR IN AI CORE
-/mob/living/silicon/ai/TurfAdjacent(var/turf/T)
- return (GLOB.cameranet && GLOB.cameranet.checkTurfVis(T))
+/obj/machinery/ai_slipper/AICtrlClick(mob/living/silicon/ai/user) //Turns liquid dispenser on or off
+ ToggleOn()
+
+/obj/machinery/ai_slipper/AIAltClick() //Dispenses liquid if on
+ Activate()
diff --git a/code/controllers/subsystem/processing/instruments.dm b/code/controllers/subsystem/processing/instruments.dm
new file mode 100644
index 00000000000..3d571d2a13d
--- /dev/null
+++ b/code/controllers/subsystem/processing/instruments.dm
@@ -0,0 +1,86 @@
+PROCESSING_SUBSYSTEM_DEF(instruments)
+ name = "Instruments"
+ init_order = INIT_ORDER_INSTRUMENTS
+ wait = 1
+ flags = SS_TICKER|SS_BACKGROUND|SS_KEEP_TIMING
+ offline_implications = "Instruments will no longer play. No immediate action is needed."
+
+ /// List of all instrument data, associative id = datum
+ var/list/datum/instrument/instrument_data
+ /// List of all song datums.
+ var/list/datum/song/songs
+ /// Max lines in songs
+ var/musician_maxlines = 600
+ /// Max characters per line in songs
+ var/musician_maxlinechars = 300
+ /// Deciseconds between hearchecks. Too high and instruments seem to lag when people are moving around in terms of who can hear it. Too low and the server lags from this.
+ var/musician_hearcheck_mindelay = 5
+ /// Maximum instrument channels total instruments are allowed to use. This is so you don't have instruments deadlocking all sound channels.
+ var/max_instrument_channels = MAX_INSTRUMENT_CHANNELS
+ /// Current number of channels allocated for instruments
+ var/current_instrument_channels = 0
+ /// Single cached list for synthesizer instrument ids, so you don't have to have a new list with every synthesizer.
+ var/list/synthesizer_instrument_ids
+
+/datum/controller/subsystem/processing/instruments/Initialize()
+ initialize_instrument_data()
+ synthesizer_instrument_ids = get_allowed_instrument_ids()
+ return ..()
+
+/**
+ * Initializes all instrument datums
+ */
+/datum/controller/subsystem/processing/instruments/proc/initialize_instrument_data()
+ instrument_data = list()
+ for(var/path in subtypesof(/datum/instrument))
+ var/datum/instrument/I = path
+ if(initial(I.abstract_type) == path)
+ continue
+ I = new path
+ I.Initialize()
+ if(!I.id)
+ qdel(I)
+ continue
+ else
+ instrument_data[I.id] = I
+ CHECK_TICK
+
+/**
+ * Reserves a sound channel for a given instrument datum
+ *
+ * Arguments:
+ * * I - The instrument datum
+ */
+/datum/controller/subsystem/processing/instruments/proc/reserve_instrument_channel(datum/instrument/I)
+ if(current_instrument_channels > max_instrument_channels)
+ return
+ . = SSsounds.reserve_sound_channel(I)
+ if(!isnull(.))
+ current_instrument_channels++
+
+/**
+ * Called when a datum/song is created
+ *
+ * Arguments:
+ * * S - The created datum/song
+ */
+/datum/controller/subsystem/processing/instruments/proc/on_song_new(datum/song/S)
+ LAZYADD(songs, S)
+
+/**
+ * Called when a datum/song is deleted
+ *
+ * Arguments:
+ * * S - The deleted datum/song
+ */
+/datum/controller/subsystem/processing/instruments/proc/on_song_del(datum/song/S)
+ LAZYREMOVE(songs, S)
+
+/**
+ * Returns the instrument datum at the given ID or path
+ *
+ * Arguments:
+ * * id_or_path - The ID or path of the instrument
+ */
+/datum/controller/subsystem/processing/instruments/proc/get_instrument(id_or_path)
+ return instrument_data["[id_or_path]"]
diff --git a/code/controllers/subsystem/sounds.dm b/code/controllers/subsystem/sounds.dm
new file mode 100644
index 00000000000..33d97fcfe04
--- /dev/null
+++ b/code/controllers/subsystem/sounds.dm
@@ -0,0 +1,165 @@
+#define DATUMLESS "NO_DATUM"
+
+SUBSYSTEM_DEF(sounds)
+ name = "Sounds"
+ init_order = INIT_ORDER_SOUNDS
+ flags = SS_NO_FIRE
+ offline_implications = "Sounds may not play correctly. Shuttle call recommended."
+
+ var/using_channels_max = CHANNEL_HIGHEST_AVAILABLE // BYOND max channels
+ /// Amount of channels to reserve for random usage rather than reservations being allowed to reserve all channels. Also a nice safeguard for when someone screws up.
+ var/random_channels_min = 50
+ // Hey uh these two needs to be initialized fast because the whole "things get deleted before init" thing.
+ /// Assoc list, "[channel]" = either the datum using it or TRUE for an unsafe-reserved (datumless reservation) channel
+ var/list/using_channels
+ /// Assoc list datum = list(channel1, channel2, ...) for what channels something reserved.
+ var/list/using_channels_by_datum
+ // Special datastructure for fast channel management
+ /// List of all channels as numbers
+ var/list/channel_list
+ /// Associative list of all reserved channels associated to their position. "[channel_number]" = index as number
+ var/list/reserved_channels
+ /// lower iteration position - Incremented and looped to get "random" sound channels for normal sounds. The channel at this index is returned when asking for a random channel.
+ var/channel_random_low
+ /// higher reserve position - decremented and incremented to reserve sound channels, anything above this is reserved. The channel at this index is the highest unreserved channel.
+ var/channel_reserve_high
+
+/datum/controller/subsystem/sounds/Initialize()
+ setup_available_channels()
+ return ..()
+
+/**
+ * Sets up all available sound channels
+ */
+/datum/controller/subsystem/sounds/proc/setup_available_channels()
+ channel_list = list()
+ reserved_channels = list()
+ using_channels = list()
+ using_channels_by_datum = list()
+ for(var/i in 1 to using_channels_max)
+ channel_list += i
+ channel_random_low = 1
+ channel_reserve_high = length(channel_list)
+
+/**
+ * Removes a channel from using list
+ *
+ * Arguments:
+ * * channel - The channel number
+ */
+/datum/controller/subsystem/sounds/proc/free_sound_channel(channel)
+ var/text_channel = num2text(channel)
+ var/using = using_channels[text_channel]
+ using_channels -= text_channel
+ if(!using) // datum channel
+ using_channels_by_datum[using] -= channel
+ if(!length(using_channels_by_datum[using]))
+ using_channels_by_datum -= using
+ free_channel(channel)
+
+/**
+ * Frees all the channels a datum is using
+ *
+ * Arguments:
+ * * D - The datum
+ */
+/datum/controller/subsystem/sounds/proc/free_datum_channels(datum/D)
+ var/list/L = using_channels_by_datum[D]
+ if(!L)
+ return
+ for(var/channel in L)
+ using_channels -= num2text(channel)
+ free_channel(channel)
+ using_channels_by_datum -= D
+
+/**
+ * Frees all datumless channels
+ */
+/datum/controller/subsystem/sounds/proc/free_datumless_channels()
+ free_datum_channels(DATUMLESS)
+
+/**
+ * NO AUTOMATIC CLEANUP - If you use this, you better manually free it later!
+ *
+ * Returns an integer for channel
+ */
+/datum/controller/subsystem/sounds/proc/reserve_sound_channel_datumless()
+ . = reserve_channel()
+ if(!.) // oh no..
+ return FALSE
+ var/text_channel = num2text(.)
+ using_channels[text_channel] = DATUMLESS
+ LAZYADD(using_channels_by_datum[DATUMLESS], .)
+
+/**
+ * Reserves a channel for a datum. Automatic cleanup only when the datum is deleted.
+ *
+ * Returns an integer for channel
+ * Arguments:
+ * * D - The datum
+ */
+/datum/controller/subsystem/sounds/proc/reserve_sound_channel(datum/D)
+ if(!D) // i don't like typechecks but someone will fuck it up
+ CRASH("Attempted to reserve sound channel without datum using the managed proc.")
+ . = reserve_channel()
+ if(!.)
+ return FALSE
+ var/text_channel = num2text(.)
+ using_channels[text_channel] = D
+ LAZYADD(using_channels_by_datum[D], .)
+
+/**
+ * Reserves a channel and updates the datastructure. Private proc.
+ */
+/datum/controller/subsystem/sounds/proc/reserve_channel()
+ PRIVATE_PROC(TRUE)
+ if(channel_reserve_high <= random_channels_min) // out of channels
+ return
+ var/channel = channel_list[channel_reserve_high]
+ reserved_channels[num2text(channel)] = channel_reserve_high--
+ return channel
+
+/**
+ * Frees a channel and updates the datastructure. Private proc.
+ */
+/datum/controller/subsystem/sounds/proc/free_channel(number)
+ PRIVATE_PROC(TRUE)
+ var/text_channel = num2text(number)
+ var/index = reserved_channels[text_channel]
+ if(!index)
+ CRASH("Attempted to (internally) free a channel that wasn't reserved.")
+ reserved_channels -= text_channel
+ // push reserve index up, which makes it now on a channel that is reserved
+ channel_reserve_high++
+ // swap the reserved channel with the unreserved channel so the reserve index is now on an unoccupied channel and the freed channel is next to be used.
+ channel_list.Swap(channel_reserve_high, index)
+ // now, an existing reserved channel will likely (exception: unreserving last reserved channel) be at index
+ // get it, and update position.
+ var/text_reserved = num2text(channel_list[index])
+ if(!reserved_channels[text_reserved]) // if it isn't already reserved make sure we don't accidently mistakenly put it on reserved list!
+ return
+ reserved_channels[text_reserved] = index
+
+/**
+ * Random available channel, returns text
+ */
+/datum/controller/subsystem/sounds/proc/random_available_channel_text()
+ if(channel_random_low > channel_reserve_high)
+ channel_random_low = 1
+ . = "[channel_list[channel_random_low++]]"
+
+/**
+ * Random available channel, returns number
+ */
+/datum/controller/subsystem/sounds/proc/random_available_channel()
+ if(channel_random_low > channel_reserve_high)
+ channel_random_low = 1
+ . = channel_list[channel_random_low++]
+
+/**
+ * How many channels we have left
+ */
+/datum/controller/subsystem/sounds/proc/available_channels_left()
+ return length(channel_list) - random_channels_min
+
+#undef DATUMLESS
diff --git a/code/datums/action.dm b/code/datums/action.dm
index c1802da47c1..11ab496d386 100644
--- a/code/datums/action.dm
+++ b/code/datums/action.dm
@@ -190,9 +190,6 @@
/datum/action/item_action/toggle_mister
name = "Toggle Mister"
-/datum/action/item_action/toggle_headphones
- name = "Toggle Headphones"
-
/datum/action/item_action/toggle_helmet_light
name = "Toggle Helmet Light"
@@ -232,19 +229,6 @@
button.name = name
..()
-/datum/action/item_action/synthswitch
- name = "Change Synthesizer Instrument"
- desc = "Change the type of instrument your synthesizer is playing as."
-
-/datum/action/item_action/synthswitch/Trigger()
- if(istype(target, /obj/item/instrument/piano_synth))
- var/obj/item/instrument/piano_synth/synth = target
- var/chosen = input("Choose the type of instrument you want to use", "Instrument Selection", "piano") as null|anything in synth.insTypes
- if(!synth.insTypes[chosen])
- return
- return synth.changeInstrument(chosen)
- return ..()
-
/datum/action/item_action/vortex_recall
name = "Vortex Recall"
desc = "Recall yourself, and anyone nearby, to an attuned hierophant beacon at any time.
If the beacon is still attached, will detach it."
@@ -257,6 +241,9 @@
return 0
return ..()
+/datum/action/item_action/change_headphones_song
+ name = "Change Headphones Song"
+
/datum/action/item_action/toggle
/datum/action/item_action/toggle/New(Target)
diff --git a/code/datums/components/spooky.dm b/code/datums/components/spooky.dm
new file mode 100644
index 00000000000..f5ee9c94666
--- /dev/null
+++ b/code/datums/components/spooky.dm
@@ -0,0 +1,58 @@
+/datum/component/spooky
+ var/too_spooky = TRUE //will it spawn a new instrument?
+
+/datum/component/spooky/Initialize()
+ RegisterSignal(parent, COMSIG_ITEM_ATTACK, .proc/spectral_attack)
+
+/datum/component/spooky/proc/spectral_attack(datum/source, mob/living/carbon/C, mob/user)
+ if(ishuman(user)) //this weapon wasn't meant for mortals.
+ var/mob/living/carbon/human/U = user
+ if(!istype(U.dna.species, /datum/species/skeleton))
+ U.adjustStaminaLoss(35) //Extra Damage
+ U.Jitter(35)
+ U.stuttering = 20
+ if(U.getStaminaLoss() > 95)
+ to_chat(U, "Your ears weren't meant for this spectral sound.")
+ spectral_change(U)
+ return
+
+ if(ishuman(C))
+ var/mob/living/carbon/human/H = C
+ if(istype(H.dna.species, /datum/species/skeleton))
+ return //undeads are unaffected by the spook-pocalypse.
+ C.Jitter(35)
+ C.stuttering = 20
+ if(!istype(H.dna.species, /datum/species/diona) && !istype(H.dna.species, /datum/species/machine) && !istype(H.dna.species, /datum/species/slime) && !istype(H.dna.species, /datum/species/golem) && !istype(H.dna.species, /datum/species/plasmaman))
+ C.adjustStaminaLoss(25) //boneless humanoids don't lose the will to live
+ to_chat(C, "DOOT")
+ spectral_change(H)
+
+ else //the sound will spook monkeys.
+ C.Jitter(15)
+ C.stuttering = 20
+
+/datum/component/spooky/proc/spectral_change(mob/living/carbon/human/H, mob/user)
+ if((H.getStaminaLoss() > 95) && (!istype(H.dna.species, /datum/species/diona) && !istype(H.dna.species, /datum/species/machine) && !istype(H.dna.species, /datum/species/slime) && !istype(H.dna.species, /datum/species/golem) && !istype(H.dna.species, /datum/species/plasmaman) && !istype(H.dna.species, /datum/species/skeleton)))
+ H.Stun(20)
+ H.set_species(/datum/species/skeleton)
+ H.visible_message("[H] has given up on life as a mortal.")
+ var/T = get_turf(H)
+ if(too_spooky)
+ if(prob(30))
+ new/obj/item/instrument/saxophone/spectral(T)
+ else if(prob(30))
+ new/obj/item/instrument/trumpet/spectral(T)
+ else if(prob(30))
+ new/obj/item/instrument/trombone/spectral(T)
+ else
+ to_chat(H, "The spooky gods forgot to ship your instrument. Better luck next unlife.")
+ to_chat(H, "You are the spooky skeleton!")
+ to_chat(H, "A new life and identity has begun. Help your fellow skeletons into bringing out the spooky-pocalypse. You haven't forgotten your past life, and are still beholden to past loyalties.")
+ change_name(H) //time for a new name!
+
+/datum/component/spooky/proc/change_name(mob/living/carbon/human/H)
+ var/t = stripped_input(H, "Enter your new skeleton name", H.real_name, null, MAX_NAME_LEN)
+ if(!t)
+ t = "spooky skeleton"
+ H.real_name = t
+ H.name = t
diff --git a/code/datums/looping_sounds/looping_sound.dm b/code/datums/looping_sounds/looping_sound.dm
index f44a87bdd7a..006e92c305c 100644
--- a/code/datums/looping_sounds/looping_sound.dm
+++ b/code/datums/looping_sounds/looping_sound.dm
@@ -71,7 +71,7 @@
var/list/atoms_cache = output_atoms
var/sound/S = sound(soundfile)
if(direct)
- S.channel = open_sound_channel()
+ S.channel = SSsounds.random_available_channel()
S.volume = volume
for(var/i in 1 to atoms_cache.len)
var/atom/thing = atoms_cache[i]
diff --git a/code/datums/uplink_item.dm b/code/datums/uplink_item.dm
index 8a172bb85b1..3562cfd9d80 100644
--- a/code/datums/uplink_item.dm
+++ b/code/datums/uplink_item.dm
@@ -736,6 +736,14 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item))
cost = 12 // normally 18
gamemodes = list(/datum/game_mode/nuclear)
+/datum/uplink_item/ammo/bulldog_XLmagsbag
+ name = "Bulldog - 12g XL Magazine Duffel Bag"
+ desc = "A duffel bag containing three 16 round drum magazines(Slug, Buckshot, Dragon's Breath)."
+ reference = "12XLDB"
+ item = /obj/item/storage/backpack/duffel/syndie/ammo/shotgunXLmags
+ cost = 12 // normally 18
+ gamemodes = list(/datum/game_mode/nuclear)
+
/datum/uplink_item/ammo/smg
name = "C-20r - .45 Magazine"
desc = "An additional 20-round .45 magazine for use in the C-20r submachine gun. These bullets pack a lot of punch that can knock most targets down, but do limited overall damage."
diff --git a/code/datums/wires/airlock.dm b/code/datums/wires/airlock.dm
index 6f7291a3a69..ff7a0169c15 100644
--- a/code/datums/wires/airlock.dm
+++ b/code/datums/wires/airlock.dm
@@ -33,7 +33,7 @@
. += "The door bolts [A.locked ? "have fallen!" : "look up."]"
. += "The door bolt lights are [(A.lights && haspower) ? "on." : "off!"]"
. += "The test light is [haspower ? "on." : "off!"]"
- . += "The 'AI control allowed' light is [(A.aiControlDisabled == 0 && !A.emagged && haspower) ? "on" : "off"]."
+ . += "The 'AI control allowed' light is [(A.aiControlDisabled == AICONTROLDISABLED_OFF && !A.emagged && haspower) ? "on" : "off"]."
. += "The 'Check Wiring' light is [(A.safe == 0 && haspower) ? "on" : "off"]."
. += "The 'Check Timing Mechanism' light is [(A.normalspeed == 0 && haspower) ? "on" : "off"]."
. += "The emergency lights are [(A.emergency && haspower) ? "on" : "off"]."
@@ -74,16 +74,16 @@
if(!mend)
//one wire for AI control. Cutting this prevents the AI from controlling the door unless it has hacked the door through the power connection (which takes about a minute). If both main and backup power are cut, as well as this wire, then the AI cannot operate or hack the door at all.
- //aiControlDisabled: If 1, AI control is disabled until the AI hacks back in and disables the lock. If 2, the AI has bypassed the lock. If -1, the control is enabled but the AI had bypassed it earlier, so if it is disabled again the AI would have no trouble getting back in.
- if(A.aiControlDisabled == 0)
- A.aiControlDisabled = 1
- else if(A.aiControlDisabled == -1)
- A.aiControlDisabled = 2
+ //aiControlDisabled: see explanation in code\__DEFINES\construction.dm#32
+ if(A.aiControlDisabled == AICONTROLDISABLED_OFF)
+ A.aiControlDisabled = AICONTROLDISABLED_ON
+ else if(A.aiControlDisabled == AICONTROLDISABLED_PERMA)
+ A.aiControlDisabled = AICONTROLDISABLED_BYPASS
else
- if(A.aiControlDisabled == 1)
- A.aiControlDisabled = 0
- else if(A.aiControlDisabled == 2)
- A.aiControlDisabled = -1
+ if(A.aiControlDisabled == AICONTROLDISABLED_ON)
+ A.aiControlDisabled = AICONTROLDISABLED_OFF
+ else if(A.aiControlDisabled == AICONTROLDISABLED_BYPASS)
+ A.aiControlDisabled = AICONTROLDISABLED_PERMA
if(WIRE_ELECTRIFY)
if(!mend)
@@ -136,10 +136,11 @@
A.loseBackupPower()
if(WIRE_AI_CONTROL)
- if(A.aiControlDisabled == 0)
- A.aiControlDisabled = 1
- else if(A.aiControlDisabled == -1)
- A.aiControlDisabled = 2
+ if(A.aiControlDisabled == AICONTROLDISABLED_OFF)
+ A.aiControlDisabled = AICONTROLDISABLED_ON
+ else if(A.aiControlDisabled == AICONTROLDISABLED_PERMA)
+ A.aiControlDisabled = AICONTROLDISABLED_BYPASS
+
addtimer(CALLBACK(A, /obj/machinery/door/airlock/.proc/ai_control_callback), 1 SECONDS)
if(WIRE_ELECTRIFY)
diff --git a/code/game/dna/dna_modifier.dm b/code/game/dna/dna_modifier.dm
index d887153c6d5..3158cf90303 100644
--- a/code/game/dna/dna_modifier.dm
+++ b/code/game/dna/dna_modifier.dm
@@ -7,6 +7,11 @@
#define NEGATE_MUTATION_THRESHOLD 30 // Occupants with over ## percent radiation threshold will not gain mutations
+#define PAGE_UI "ui"
+#define PAGE_SE "se"
+#define PAGE_BUFFER "buffer"
+#define PAGE_REJUVENATORS "rejuvenators"
+
//list("data" = null, "owner" = null, "label" = null, "type" = null, "ue" = 0),
/datum/dna2/record
var/datum/dna/dna = null
@@ -161,6 +166,7 @@
occupant = usr
icon_state = "scanner_occupied"
add_fingerprint(usr)
+ SStgui.update_uis(src)
/obj/machinery/dna_scannernew/MouseDrop_T(atom/movable/O, mob/user)
if(!istype(O))
@@ -214,6 +220,7 @@
return
beaker = I
+ SStgui.update_uis(src)
I.forceMove(src)
user.visible_message("[user] adds \a [I] to \the [src]!", "You add \a [I] to \the [src]!")
return
@@ -260,6 +267,7 @@
M.forceMove(src)
occupant = M
icon_state = "scanner_occupied"
+ SStgui.update_uis(src)
// search for ghosts, if the corpse is empty and the scanner is connected to a cloner
if(locate(/obj/machinery/computer/cloning, get_step(src, NORTH)) \
@@ -281,6 +289,7 @@
occupant.forceMove(loc)
occupant = null
icon_state = "scanner_open"
+ SStgui.update_uis(src)
/obj/machinery/dna_scannernew/force_eject_occupant()
go_out(null, TRUE)
@@ -296,6 +305,7 @@
occupant = null
updateUsrDialog()
update_icon()
+ SStgui.update_uis(src)
// Checks if occupants can be irradiated/mutated - prevents exploits where wearing full rad protection would still let you gain mutations
/obj/machinery/dna_scannernew/proc/radiation_check()
@@ -333,12 +343,11 @@
var/injector_ready = FALSE //Quick fix for issue 286 (screwdriver the screen twice to restore injector) -Pete
var/obj/machinery/dna_scannernew/connected = null
var/obj/item/disk/data/disk = null
- var/selected_menu_key = null
+ var/selected_menu_key = PAGE_UI
anchored = TRUE
use_power = IDLE_POWER_USE
idle_power_usage = 10
active_power_usage = 400
- var/waiting_for_user_input = 0 // Fix for #274 (Mash create block injector without answering dialog to make unlimited injectors) - N3X
/obj/machinery/computer/scan_consolenew/attackby(obj/item/I, mob/user, params)
if(istype(I, /obj/item/disk/data)) //INSERT SOME diskS
@@ -347,7 +356,7 @@
I.forceMove(src)
disk = I
to_chat(user, "You insert [I].")
- SSnanoui.update_uis(src) // update all UIs attached to src()
+ SStgui.update_uis(src)
return
else
return ..()
@@ -399,35 +408,18 @@
if(stat & (NOPOWER|BROKEN))
return
- ui_interact(user)
+ tgui_interact(user)
- /**
- * The ui_interact proc is used to open and update Nano UIs
- * If ui_interact is not used then the UI will not update correctly
- * ui_interact is currently defined for /atom/movable
- *
- * @param user /mob The mob who is interacting with this ui
- * @param ui_key string A string key to use for this ui. Allows for multiple unique uis on one obj/mob (defaut value "main")
- * @param ui /datum/nanoui This parameter is passed by the nanoui process() proc when updating an open ui
- *
- * @return nothing
- */
-/obj/machinery/computer/scan_consolenew/ui_interact(mob/user, ui_key = "main", datum/nanoui/ui = null, force_open = 1)
+/obj/machinery/computer/scan_consolenew/tgui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/tgui_state/state = GLOB.tgui_default_state)
if(user == connected.occupant)
return
- // update the ui if it exists, returns null if no ui is passed/found
- ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open)
+ ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
- // the ui does not exist, so we'll create a new() one
- // for a list of parameters and their descriptions see the code docs in \code\modules\nano\nanoui.dm
- ui = new(user, src, ui_key, "dna_modifier.tmpl", "DNA Modifier Console", 660, 700)
- // open the new ui window
+ ui = new(user, src, ui_key, "DNAModifier", name, 660, 700, master_ui, state)
ui.open()
- // auto update every Master Controller tick
- ui.set_auto_update(1)
-/obj/machinery/computer/scan_consolenew/ui_data(mob/user, datum/topic_state/state)
+/obj/machinery/computer/scan_consolenew/tgui_data(mob/user)
var/data[0]
data["selectedMenuKey"] = selected_menu_key
data["locked"] = connected.locked
@@ -501,9 +493,12 @@
for(var/datum/reagent/R in connected.beaker.reagents.reagent_list)
data["beakerVolume"] += R.volume
+ // Transfer modal information if there is one
+ data["modal"] = tgui_modal_data(src)
+
return data
-/obj/machinery/computer/scan_consolenew/Topic(href, href_list)
+/obj/machinery/computer/scan_consolenew/tgui_act(action, params)
if(..())
return FALSE // don't update uis
if(!istype(usr.loc, /turf))
@@ -512,405 +507,350 @@
return FALSE // don't update uis
if(irradiating) // Make sure that it isn't already irradiating someone...
return FALSE // don't update uis
+ if(stat & (NOPOWER|BROKEN))
+ return
add_fingerprint(usr)
- if(href_list["selectMenuKey"])
- selected_menu_key = href_list["selectMenuKey"]
- return TRUE // return 1 forces an update to all Nano uis attached to src
-
- if(href_list["toggleLock"])
- if((connected && connected.occupant))
- connected.locked = !(connected.locked)
- return TRUE // return 1 forces an update to all Nano uis attached to src
-
- if(href_list["pulseRadiation"])
- irradiating = radiation_duration
- var/lock_state = connected.locked
- connected.locked = TRUE //lock it
- SSnanoui.update_uis(src) // update all UIs attached to src
-
- sleep(10 * radiation_duration) // sleep for radiation_duration seconds
-
- irradiating = 0
- connected.locked = lock_state
-
- if(!connected.occupant)
- return TRUE // return 1 forces an update to all Nano uis attached to src
-
- var/radiation = (((radiation_intensity * 3) + radiation_duration * 3) / connected.damage_coeff)
- connected.occupant.apply_effect(radiation, IRRADIATE, 0)
- if(connected.radiation_check())
- return TRUE
-
- if(prob(95))
- if(prob(75))
- randmutb(connected.occupant)
- else
- randmuti(connected.occupant)
- else
- if(prob(95))
- randmutg(connected.occupant)
- else
- randmuti(connected.occupant)
-
- return TRUE // return 1 forces an update to all Nano uis attached to src
-
- if(href_list["radiationDuration"])
- if(text2num(href_list["radiationDuration"]) > 0)
- if(radiation_duration < 20)
- radiation_duration += 2
- else
- if(radiation_duration > 2)
- radiation_duration -= 2
- return TRUE // return 1 forces an update to all Nano uis attached to src
-
- if(href_list["radiationIntensity"])
- if(text2num(href_list["radiationIntensity"]) > 0)
- if(radiation_intensity < 10)
- radiation_intensity++
- else
- if(radiation_intensity > 1)
- radiation_intensity--
- return TRUE // return 1 forces an update to all Nano uis attached to src
-
- ////////////////////////////////////////////////////////
-
- if(href_list["changeUITarget"] && text2num(href_list["changeUITarget"]) > 0)
- if(selected_ui_target < 15)
- selected_ui_target++
- selected_ui_target_hex = selected_ui_target
- switch(selected_ui_target)
- if(10)
- selected_ui_target_hex = "A"
- if(11)
- selected_ui_target_hex = "B"
- if(12)
- selected_ui_target_hex = "C"
- if(13)
- selected_ui_target_hex = "D"
- if(14)
- selected_ui_target_hex = "E"
- if(15)
- selected_ui_target_hex = "F"
- else
- selected_ui_target = 0
- selected_ui_target_hex = 0
- return TRUE // return 1 forces an update to all Nano uis attached to src
-
- if(href_list["changeUITarget"] && text2num(href_list["changeUITarget"]) < 1)
- if(selected_ui_target > 0)
- selected_ui_target--
- selected_ui_target_hex = selected_ui_target
- switch(selected_ui_target)
- if(10)
- selected_ui_target_hex = "A"
- if(11)
- selected_ui_target_hex = "B"
- if(12)
- selected_ui_target_hex = "C"
- if(13)
- selected_ui_target_hex = "D"
- if(14)
- selected_ui_target_hex = "E"
- else
- selected_ui_target = 15
- selected_ui_target_hex = "F"
- return TRUE // return 1 forces an update to all Nano uis attached to src
-
- if(href_list["selectUIBlock"] && href_list["selectUISubblock"]) // This chunk of code updates selected block / sub-block based on click
- var/select_block = text2num(href_list["selectUIBlock"])
- var/select_subblock = text2num(href_list["selectUISubblock"])
- if((select_block <= DNA_UI_LENGTH) && (select_block >= 1))
- selected_ui_block = select_block
- if((select_subblock <= DNA_BLOCK_SIZE) && (select_subblock >= 1))
- selected_ui_subblock = select_subblock
- return TRUE // return 1 forces an update to all Nano uis attached to src
-
- if(href_list["pulseUIRadiation"])
- var/block = connected.occupant.dna.GetUISubBlock(selected_ui_block, selected_ui_subblock)
-
- irradiating = radiation_duration
- var/lock_state = connected.locked
- connected.locked = TRUE //lock it
- SSnanoui.update_uis(src) // update all UIs attached to src
-
- sleep(10 * radiation_duration) // sleep for radiation_duration seconds
-
- irradiating = 0
- connected.locked = lock_state
-
- if(!connected.occupant)
- return TRUE
-
- if(prob((80 + (radiation_duration / 2))))
- var/radiation = (radiation_intensity + radiation_duration)
- connected.occupant.apply_effect(radiation,IRRADIATE,0)
-
- if(connected.radiation_check())
- return TRUE
-
- block = miniscrambletarget(num2text(selected_ui_target), radiation_intensity, radiation_duration)
- connected.occupant.dna.SetUISubBlock(selected_ui_block, selected_ui_subblock, block)
- connected.occupant.UpdateAppearance()
- else
- var/radiation = ((radiation_intensity * 2) + radiation_duration)
- connected.occupant.apply_effect(radiation, IRRADIATE, 0)
- if(connected.radiation_check())
- return TRUE
-
- if(prob(20 + radiation_intensity))
- randmutb(connected.occupant)
- domutcheck(connected.occupant, connected)
- else
- randmuti(connected.occupant)
- connected.occupant.UpdateAppearance()
- return TRUE // return 1 forces an update to all Nano uis attached to src
-
- ////////////////////////////////////////////////////////
-
- if(href_list["injectRejuvenators"])
- if(!connected.occupant)
- return FALSE
- var/inject_amount = round(text2num(href_list["injectRejuvenators"]), 5) // round to nearest 5
- if(inject_amount < 0) // Since the user can actually type the commands himself, some sanity checking
- inject_amount = 0
- if(inject_amount > 50)
- inject_amount = 50
- connected.beaker.reagents.trans_to(connected.occupant, inject_amount)
- connected.beaker.reagents.reaction(connected.occupant)
- return TRUE // return 1 forces an update to all Nano uis attached to src
-
- ////////////////////////////////////////////////////////
-
- if(href_list["selectSEBlock"] && href_list["selectSESubblock"]) // This chunk of code updates selected block / sub-block based on click (se stands for strutural enzymes)
- var/select_block = text2num(href_list["selectSEBlock"])
- var/select_subblock = text2num(href_list["selectSESubblock"])
- if((select_block <= DNA_SE_LENGTH) && (select_block >= 1))
- selected_se_block = select_block
- if((select_subblock <= DNA_BLOCK_SIZE) && (select_subblock >= 1))
- selected_se_subblock = select_subblock
- //testing("User selected block [selected_se_block] (sent [select_block]), subblock [selected_se_subblock] (sent [select_block]).")
- return TRUE // return 1 forces an update to all Nano uis attached to src
-
- if(href_list["pulseSERadiation"])
- var/block = connected.occupant.dna.GetSESubBlock(selected_se_block, selected_se_subblock)
- //var/original_block=block
- //testing("Irradiating SE block [selected_se_block]:[selected_se_subblock] ([block])...")
-
- irradiating = radiation_duration
- var/lock_state = connected.locked
- connected.locked = TRUE //lock it
- SSnanoui.update_uis(src) // update all UIs attached to src
-
- sleep(10 * radiation_duration) // sleep for radiation_duration seconds
-
- irradiating = 0
- connected.locked = lock_state
-
- if(connected.occupant)
- if(prob((80 + ((radiation_duration / 2) + (connected.precision_coeff ** 3)))))
- var/radiation = ((radiation_intensity + radiation_duration) / connected.damage_coeff)
- connected.occupant.apply_effect(radiation, IRRADIATE, 0)
-
- if(connected.radiation_check())
- return 1
-
- var/real_SE_block=selected_se_block
- block = miniscramble(block, radiation_intensity, radiation_duration)
- if(prob(20))
- if(selected_se_block > 1 && selected_se_block < DNA_SE_LENGTH/2)
- real_SE_block++
- else if(selected_se_block > DNA_SE_LENGTH/2 && selected_se_block < DNA_SE_LENGTH)
- real_SE_block--
-
- //testing("Irradiated SE block [real_SE_block]:[selected_se_subblock] ([original_block] now [block]) [(real_SE_block!=selected_se_block) ? "(SHIFTED)":""]!")
- connected.occupant.dna.SetSESubBlock(real_SE_block, selected_se_subblock, block)
- domutcheck(connected.occupant, connected)
- else
- var/radiation = (((radiation_intensity * 2) + radiation_duration) / connected.damage_coeff)
- connected.occupant.apply_effect(radiation, IRRADIATE, 0)
-
- if(connected.radiation_check())
- return 1
-
- if(prob(80 - radiation_duration))
- //testing("Random bad mut!")
- randmutb(connected.occupant)
- domutcheck(connected.occupant, connected)
- else
- randmuti(connected.occupant)
- //testing("Random identity mut!")
- connected.occupant.UpdateAppearance()
- return TRUE // return 1 forces an update to all Nano uis attached to src
-
- if(href_list["ejectBeaker"])
- if(connected.beaker)
- var/obj/item/reagent_containers/glass/B = connected.beaker
- B.forceMove(connected.loc)
- connected.beaker = null
+ if(tgui_act_modal(action, params))
return TRUE
- if(href_list["ejectOccupant"])
- connected.eject_occupant(usr)
- return TRUE
-
- // Transfer Buffer Management
- if(href_list["bufferOption"])
- var/bufferOption = href_list["bufferOption"]
-
- // These bufferOptions do not require a bufferId
- if(bufferOption == "wipeDisk")
- if((isnull(disk)) || (disk.read_only))
- //temphtml = "Invalid disk. Please try again."
- return FALSE
-
- disk.buf = null
- //temphtml = "Data saved."
- return TRUE
-
- if(bufferOption == "ejectDisk")
- if(!disk)
+ . = TRUE
+ switch(action)
+ if("selectMenuKey")
+ var/key = params["key"]
+ if(!(key in list(PAGE_UI, PAGE_SE, PAGE_BUFFER, PAGE_REJUVENATORS)))
return
- disk.forceMove(get_turf(src))
- disk = null
- return TRUE
-
- // All bufferOptions from here on require a bufferId
- if(!href_list["bufferId"])
- return FALSE
-
- var/bufferId = text2num(href_list["bufferId"])
-
- if(bufferId < 1 || bufferId > 3)
- return FALSE // Not a valid buffer id
-
- if(bufferOption == "saveUI")
- if(connected.occupant && connected.occupant.dna)
- var/datum/dna2/record/databuf = new
- databuf.types = DNA2_BUF_UI // DNA2_BUF_UE
- databuf.dna = connected.occupant.dna.Clone()
- if(ishuman(connected.occupant))
- databuf.dna.real_name=connected.occupant.name
- databuf.name = "Unique Identifier"
- buffers[bufferId] = databuf
- return TRUE
-
- if(bufferOption == "saveUIAndUE")
- if(connected.occupant && connected.occupant.dna)
- var/datum/dna2/record/databuf = new
- databuf.types = DNA2_BUF_UI|DNA2_BUF_UE
- databuf.dna = connected.occupant.dna.Clone()
- if(ishuman(connected.occupant))
- databuf.dna.real_name=connected.occupant.dna.real_name
- databuf.name = "Unique Identifier + Unique Enzymes"
- buffers[bufferId] = databuf
- return TRUE
-
- if(bufferOption == "saveSE")
- if(connected.occupant && connected.occupant.dna)
- var/datum/dna2/record/databuf = new
- databuf.types = DNA2_BUF_SE
- databuf.dna = connected.occupant.dna.Clone()
- if(ishuman(connected.occupant))
- databuf.dna.real_name = connected.occupant.dna.real_name
- databuf.name = "Structural Enzymes"
- buffers[bufferId] = databuf
- return TRUE
-
- if(bufferOption == "clear")
- buffers[bufferId] = new /datum/dna2/record()
- return TRUE
-
- if(bufferOption == "changeLabel")
- var/datum/dna2/record/buf = buffers[bufferId]
- var/text = sanitize(input(usr, "New Label:", "Edit Label", buf.name) as text|null)
- buf.name = text
- buffers[bufferId] = buf
- return TRUE
-
- if(bufferOption == "transfer")
- if(!connected.occupant || (NOCLONE in connected.occupant.mutations && connected.scan_level < 3) || !connected.occupant.dna)
- return TRUE
-
- irradiating = 2
+ selected_menu_key = key
+ if("toggleLock")
+ if(connected && connected.occupant)
+ connected.locked = !(connected.locked)
+ if("pulseRadiation")
+ irradiating = radiation_duration
var/lock_state = connected.locked
connected.locked = TRUE //lock it
- SSnanoui.update_uis(src) // update all UIs attached to src
- sleep(2 SECONDS)
+ SStgui.update_uis(src)
+ sleep(10 * radiation_duration) // sleep for radiation_duration seconds
irradiating = 0
connected.locked = lock_state
- var/radiation = (rand(20,50) / connected.damage_coeff)
+ if(!connected.occupant)
+ return
+
+ var/radiation = (((radiation_intensity * 3) + radiation_duration * 3) / connected.damage_coeff)
connected.occupant.apply_effect(radiation, IRRADIATE, 0)
-
if(connected.radiation_check())
- return TRUE
+ return
- var/datum/dna2/record/buf = buffers[bufferId]
-
- if((buf.types & DNA2_BUF_UI))
- if((buf.types & DNA2_BUF_UE))
- connected.occupant.real_name = buf.dna.real_name
- connected.occupant.name = buf.dna.real_name
- connected.occupant.UpdateAppearance(buf.dna.UI.Copy())
- else if(buf.types & DNA2_BUF_SE)
- connected.occupant.dna.SE = buf.dna.SE.Copy()
- connected.occupant.dna.UpdateSE()
- domutcheck(connected.occupant, connected)
- return TRUE
-
- if(bufferOption == "createInjector")
- if(injector_ready && !waiting_for_user_input)
-
- var/success = 1
- var/obj/item/dnainjector/I = new /obj/item/dnainjector
- var/datum/dna2/record/buf = buffers[bufferId]
- buf = buf.copy()
- if(href_list["createBlockInjector"])
- waiting_for_user_input=1
- var/list/selectedbuf
- if(buf.types & DNA2_BUF_SE)
- selectedbuf=buf.dna.SE
- else
- selectedbuf=buf.dna.UI
- var/blk = input(usr,"Select Block","Block") as null|anything in all_dna_blocks(selectedbuf)
- success = setInjectorBlock(I,blk,buf)
+ if(prob(95))
+ if(prob(75))
+ randmutb(connected.occupant)
else
- I.buf = buf
- waiting_for_user_input = 0
- if(success)
- I.forceMove(loc)
- I.name += " ([buf.name])"
- if(connected)
- I.damage_coeff = connected.damage_coeff
- injector_ready = FALSE
- spawn(300)
- injector_ready = TRUE
- return TRUE
+ randmuti(connected.occupant)
+ else
+ if(prob(95))
+ randmutg(connected.occupant)
+ else
+ randmuti(connected.occupant)
+ if("radiationDuration")
+ radiation_duration = clamp(text2num(params["value"]), 1, 20)
+ if("radiationIntensity")
+ radiation_intensity = clamp(text2num(params["value"]), 1, 10)
+ ////////////////////////////////////////////////////////
+ if("changeUITarget")
+ selected_ui_target = clamp(text2num(params["value"]), 1, 15)
+ selected_ui_target_hex = num2text(selected_ui_target, 1, 16)
+ if("selectUIBlock") // This chunk of code updates selected block / sub-block based on click
+ var/select_block = text2num(params["block"])
+ var/select_subblock = text2num(params["subblock"])
+ if(!select_block || !select_subblock)
+ return
- if(bufferOption == "loadDisk")
- if((isnull(disk)) || (!disk.buf))
- //temphtml = "Invalid disk. Please try again."
- return FALSE
+ selected_ui_block = clamp(select_block, 1, DNA_UI_LENGTH)
+ selected_ui_subblock = clamp(select_subblock, 1, DNA_BLOCK_SIZE)
+ if("pulseUIRadiation")
+ var/block = connected.occupant.dna.GetUISubBlock(selected_ui_block, selected_ui_subblock)
- buffers[bufferId] = disk.buf.copy()
- //temphtml = "Data loaded."
- return TRUE
+ irradiating = radiation_duration
+ var/lock_state = connected.locked
+ connected.locked = TRUE //lock it
- if(bufferOption == "saveDisk")
- if((isnull(disk)) || (disk.read_only))
- //temphtml = "Invalid disk. Please try again."
- return FALSE
+ SStgui.update_uis(src)
+ sleep(10 * radiation_duration) // sleep for radiation_duration seconds
- var/datum/dna2/record/buf = buffers[bufferId]
+ irradiating = 0
+ connected.locked = lock_state
- disk.buf = buf.copy()
- disk.name = "data disk - '[buf.dna.real_name]'"
- //temphtml = "Data saved."
- return TRUE
+ if(!connected.occupant)
+ return
+ if(prob((80 + (radiation_duration / 2))))
+ var/radiation = (radiation_intensity + radiation_duration)
+ connected.occupant.apply_effect(radiation,IRRADIATE,0)
+
+ if(connected.radiation_check())
+ return
+
+ block = miniscrambletarget(num2text(selected_ui_target), radiation_intensity, radiation_duration)
+ connected.occupant.dna.SetUISubBlock(selected_ui_block, selected_ui_subblock, block)
+ connected.occupant.UpdateAppearance()
+ else
+ var/radiation = ((radiation_intensity * 2) + radiation_duration)
+ connected.occupant.apply_effect(radiation, IRRADIATE, 0)
+ if(connected.radiation_check())
+ return
+
+ if(prob(20 + radiation_intensity))
+ randmutb(connected.occupant)
+ domutcheck(connected.occupant, connected)
+ else
+ randmuti(connected.occupant)
+ connected.occupant.UpdateAppearance()
+ ////////////////////////////////////////////////////////
+ if("injectRejuvenators")
+ if(!connected.occupant || !connected.beaker)
+ return
+ var/inject_amount = clamp(round(text2num(params["amount"]), 5), 0, 50) // round to nearest 5 and clamp to 0-50
+ if(!inject_amount)
+ return
+ connected.beaker.reagents.trans_to(connected.occupant, inject_amount)
+ connected.beaker.reagents.reaction(connected.occupant)
+ ////////////////////////////////////////////////////////
+ if("selectSEBlock") // This chunk of code updates selected block / sub-block based on click (se stands for strutural enzymes)
+ var/select_block = text2num(params["block"])
+ var/select_subblock = text2num(params["subblock"])
+ if(!select_block || !select_subblock)
+ return
+
+ selected_se_block = clamp(select_block, 1, DNA_SE_LENGTH)
+ selected_se_subblock = clamp(select_subblock, 1, DNA_BLOCK_SIZE)
+ if("pulseSERadiation")
+ var/block = connected.occupant.dna.GetSESubBlock(selected_se_block, selected_se_subblock)
+ //var/original_block=block
+ //testing("Irradiating SE block [selected_se_block]:[selected_se_subblock] ([block])...")
+
+ irradiating = radiation_duration
+ var/lock_state = connected.locked
+ connected.locked = TRUE //lock it
+
+ SStgui.update_uis(src)
+ sleep(10 * radiation_duration) // sleep for radiation_duration seconds
+
+ irradiating = 0
+ connected.locked = lock_state
+
+ if(connected.occupant)
+ if(prob((80 + ((radiation_duration / 2) + (connected.precision_coeff ** 3)))))
+ var/radiation = ((radiation_intensity + radiation_duration) / connected.damage_coeff)
+ connected.occupant.apply_effect(radiation, IRRADIATE, 0)
+
+ if(connected.radiation_check())
+ return 1
+
+ var/real_SE_block=selected_se_block
+ block = miniscramble(block, radiation_intensity, radiation_duration)
+ if(prob(20))
+ if(selected_se_block > 1 && selected_se_block < DNA_SE_LENGTH/2)
+ real_SE_block++
+ else if(selected_se_block > DNA_SE_LENGTH/2 && selected_se_block < DNA_SE_LENGTH)
+ real_SE_block--
+
+ //testing("Irradiated SE block [real_SE_block]:[selected_se_subblock] ([original_block] now [block]) [(real_SE_block!=selected_se_block) ? "(SHIFTED)":""]!")
+ connected.occupant.dna.SetSESubBlock(real_SE_block, selected_se_subblock, block)
+ domutcheck(connected.occupant, connected)
+ else
+ var/radiation = (((radiation_intensity * 2) + radiation_duration) / connected.damage_coeff)
+ connected.occupant.apply_effect(radiation, IRRADIATE, 0)
+
+ if(connected.radiation_check())
+ return
+
+ if(prob(80 - radiation_duration))
+ //testing("Random bad mut!")
+ randmutb(connected.occupant)
+ domutcheck(connected.occupant, connected)
+ else
+ randmuti(connected.occupant)
+ //testing("Random identity mut!")
+ connected.occupant.UpdateAppearance()
+ if("ejectBeaker")
+ if(connected.beaker)
+ var/obj/item/reagent_containers/glass/B = connected.beaker
+ B.forceMove(connected.loc)
+ connected.beaker = null
+ if("ejectOccupant")
+ connected.eject_occupant()
+ // Transfer Buffer Management
+ if("bufferOption")
+ var/bufferOption = params["option"]
+ var/bufferId = text2num(params["id"])
+ if(bufferId < 1 || bufferId > 3) // Not a valid buffer id
+ return
+
+ var/datum/dna2/record/buffer = buffers[bufferId]
+ switch(bufferOption)
+ if("saveUI")
+ if(connected.occupant && connected.occupant.dna)
+ var/datum/dna2/record/databuf = new
+ databuf.types = DNA2_BUF_UI // DNA2_BUF_UE
+ databuf.dna = connected.occupant.dna.Clone()
+ if(ishuman(connected.occupant))
+ databuf.dna.real_name=connected.occupant.name
+ databuf.name = "Unique Identifier"
+ buffers[bufferId] = databuf
+ if("saveUIAndUE")
+ if(connected.occupant && connected.occupant.dna)
+ var/datum/dna2/record/databuf = new
+ databuf.types = DNA2_BUF_UI|DNA2_BUF_UE
+ databuf.dna = connected.occupant.dna.Clone()
+ if(ishuman(connected.occupant))
+ databuf.dna.real_name=connected.occupant.dna.real_name
+ databuf.name = "Unique Identifier + Unique Enzymes"
+ buffers[bufferId] = databuf
+ if("saveSE")
+ if(connected.occupant && connected.occupant.dna)
+ var/datum/dna2/record/databuf = new
+ databuf.types = DNA2_BUF_SE
+ databuf.dna = connected.occupant.dna.Clone()
+ if(ishuman(connected.occupant))
+ databuf.dna.real_name = connected.occupant.dna.real_name
+ databuf.name = "Structural Enzymes"
+ buffers[bufferId] = databuf
+ if("clear")
+ buffers[bufferId] = new /datum/dna2/record()
+ if("changeLabel")
+ tgui_modal_input(src, "changeBufferLabel", "Please enter the new buffer label:", null, list("id" = bufferId), buffer.name, TGUI_MODAL_INPUT_MAX_LENGTH_NAME)
+ if("transfer")
+ if(!connected.occupant || (NOCLONE in connected.occupant.mutations && connected.scan_level < 3) || !connected.occupant.dna)
+ return
+
+ irradiating = 2
+ var/lock_state = connected.locked
+ connected.locked = TRUE //lock it
+
+ SStgui.update_uis(src)
+ sleep(2 SECONDS)
+
+ irradiating = 0
+ connected.locked = lock_state
+
+ var/radiation = (rand(20,50) / connected.damage_coeff)
+ connected.occupant.apply_effect(radiation, IRRADIATE, 0)
+
+ if(connected.radiation_check())
+ return
+
+ var/datum/dna2/record/buf = buffers[bufferId]
+
+ if((buf.types & DNA2_BUF_UI))
+ if((buf.types & DNA2_BUF_UE))
+ connected.occupant.real_name = buf.dna.real_name
+ connected.occupant.name = buf.dna.real_name
+ connected.occupant.UpdateAppearance(buf.dna.UI.Copy())
+ else if(buf.types & DNA2_BUF_SE)
+ connected.occupant.dna.SE = buf.dna.SE.Copy()
+ connected.occupant.dna.UpdateSE()
+ domutcheck(connected.occupant, connected)
+ if("createInjector")
+ if(!injector_ready)
+ return
+ if(text2num(params["block"]) > 0)
+ var/list/choices = all_dna_blocks((buffer.types & DNA2_BUF_SE) ? buffer.dna.SE : buffer.dna.UI)
+ tgui_modal_choice(src, "createInjectorBlock", "Please select the block to create an injector from:", null, list("id" = bufferId), null, choices)
+ else
+ create_injector(bufferId, TRUE)
+ if("loadDisk")
+ if(isnull(disk) || disk.read_only)
+ return
+ buffers[bufferId] = disk.buf.copy()
+ if("saveDisk")
+ if(isnull(disk) || disk.read_only)
+ return
+ var/datum/dna2/record/buf = buffers[bufferId]
+ disk.buf = buf.copy()
+ disk.name = "data disk - '[buf.dna.real_name]'"
+ if("wipeDisk")
+ if(isnull(disk) || disk.read_only)
+ return
+ disk.buf = null
+ if("ejectDisk")
+ if(!disk)
+ return
+ disk.forceMove(get_turf(src))
+ disk = null
+
+/**
+ * Creates a blank injector with the name of the buffer at the given buffer_id
+ *
+ * Arguments:
+ * * buffer_id - The ID of the buffer
+ * * copy_buffer - Whether the injector should copy the buffer contents
+ */
+/obj/machinery/computer/scan_consolenew/proc/create_injector(buffer_id, copy_buffer = FALSE)
+ if(buffer_id < 1 || buffer_id > length(buffers))
+ return
+
+ // Cooldown
+ injector_ready = FALSE
+ addtimer(CALLBACK(src, .proc/injector_cooldown_finish), 30 SECONDS)
+
+ // Create it
+ var/datum/dna2/record/buf = buffers[buffer_id]
+ var/obj/item/dnainjector/I = new()
+ I.forceMove(loc)
+ I.name += " ([buf.name])"
+ if(copy_buffer)
+ I.buf = buf.copy()
+ if(connected)
+ I.damage_coeff = connected.damage_coeff
+ return I
+
+/**
+ * Called when the injector creation cooldown finishes
+ */
+/obj/machinery/computer/scan_consolenew/proc/injector_cooldown_finish()
+ injector_ready = TRUE
+
+/**
+ * Called in tgui_act() to process modal actions
+ *
+ * Arguments:
+ * * action - The action passed by tgui
+ * * params - The params passed by tgui
+ */
+/obj/machinery/computer/scan_consolenew/proc/tgui_act_modal(action, params)
+ . = TRUE
+ var/id = params["id"] // The modal's ID
+ var/list/arguments = istext(params["arguments"]) ? json_decode(params["arguments"]) : params["arguments"]
+ switch(tgui_modal_act(src, action, params))
+ if(TGUI_MODAL_ANSWER)
+ var/answer = params["answer"]
+ switch(id)
+ if("createInjectorBlock")
+ var/buffer_id = text2num(arguments["id"])
+ if(buffer_id < 1 || buffer_id > length(buffers))
+ return
+ var/datum/dna2/record/buf = buffers[buffer_id]
+ var/obj/item/dnainjector/I = create_injector(buffer_id)
+ setInjectorBlock(I, answer, buf.copy())
+ if("changeBufferLabel")
+ var/buffer_id = text2num(arguments["id"])
+ if(buffer_id < 1 || buffer_id > length(buffers))
+ return
+ var/datum/dna2/record/buf = buffers[buffer_id]
+ buf.name = answer
+ buffers[buffer_id] = buf
+ else
+ return FALSE
+ else
+ return FALSE
+
+
+#undef PAGE_UI
+#undef PAGE_SE
+#undef PAGE_BUFFER
+#undef PAGE_REJUVENATORS
/////////////////////////// DNA MACHINES
diff --git a/code/game/jobs/access.dm b/code/game/jobs/access.dm
index d5a59a36872..48ac91c72fa 100644
--- a/code/game/jobs/access.dm
+++ b/code/game/jobs/access.dm
@@ -510,3 +510,26 @@ proc/get_all_job_icons() //For all existing HUD icons
return rankName
return "Unknown" //Return unknown if none of the above apply
+
+proc/get_accesslist_static_data(num_min_region = REGION_GENERAL, num_max_region = REGION_COMMAND)
+ var/list/retval
+ for(var/i in num_min_region to num_max_region)
+ var/list/accesses = list()
+ var/list/available_accesses
+ if(i == REGION_CENTCOMM) // Override necessary, because get_region_accesses(REGION_CENTCOM) returns BOTH CC and crew accesses.
+ available_accesses = get_all_centcom_access()
+ else
+ available_accesses = get_region_accesses(i)
+ for(var/access in available_accesses)
+ var/access_desc = (i == REGION_CENTCOMM) ? get_centcom_access_desc(access) : get_access_desc(access)
+ if (access_desc)
+ accesses += list(list(
+ "desc" = replacetext(access_desc, " ", " "),
+ "ref" = access,
+ ))
+ retval += list(list(
+ "name" = get_region_accesses_name(i),
+ "regid" = i,
+ "accesses" = accesses
+ ))
+ return retval
diff --git a/code/game/machinery/Sleeper.dm b/code/game/machinery/Sleeper.dm
index 7c5a03e29e6..d33518be857 100644
--- a/code/game/machinery/Sleeper.dm
+++ b/code/game/machinery/Sleeper.dm
@@ -125,7 +125,7 @@
return attack_hand(user)
/obj/machinery/sleeper/attack_ghost(mob/user)
- return attack_hand(user)
+ tgui_interact(user)
/obj/machinery/sleeper/attack_hand(mob/user)
if(stat & (NOPOWER|BROKEN))
@@ -135,17 +135,17 @@
to_chat(user, "Close the maintenance panel first.")
return
- ui_interact(user)
+ tgui_interact(user)
-/obj/machinery/sleeper/ui_interact(mob/user, ui_key = "main", datum/nanoui/ui = null, force_open = 1)
- ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open)
+/obj/machinery/sleeper/tgui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/tgui_state/state = GLOB.tgui_default_state)
+ ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
- ui = new(user, src, ui_key, "sleeper.tmpl", "Sleeper", 550, 770)
+ ui = new(user, src, ui_key, "Sleeper", "Sleeper", 550, 775)
ui.open()
- ui.set_auto_update(1)
-/obj/machinery/sleeper/ui_data(mob/user, datum/topic_state/state)
+/obj/machinery/sleeper/tgui_data(mob/user)
var/data[0]
+ data["amounts"] = amounts
data["hasOccupant"] = occupant ? 1 : 0
var/occupantData[0]
var/crisis = 0
@@ -210,9 +210,13 @@
if(beaker)
data["isBeakerLoaded"] = 1
if(beaker.reagents)
+ data["beakerMaxSpace"] = beaker.reagents.maximum_volume
data["beakerFreeSpace"] = round(beaker.reagents.maximum_volume - beaker.reagents.total_volume)
else
+ data["beakerMaxSpace"] = 0
data["beakerFreeSpace"] = 0
+ else
+ data["isBeakerLoaded"] = FALSE
var/chemicals[0]
for(var/re in possible_chems)
@@ -234,51 +238,52 @@
if(temp.id in occupant.reagents.overdose_list())
overdosing = 1
- // Because I don't know how to do this on the nano side
pretty_amount = round(reagent_amount, 0.05)
chemicals.Add(list(list("title" = temp.name, "id" = temp.id, "commands" = list("chemical" = temp.id), "occ_amount" = reagent_amount, "pretty_amount" = pretty_amount, "injectable" = injectable, "overdosing" = overdosing, "od_warning" = caution)))
data["chemicals"] = chemicals
return data
-/obj/machinery/sleeper/Topic(href, href_list)
- if(!controls_inside && usr == occupant)
- return 0
-
+/obj/machinery/sleeper/tgui_act(action, params)
if(..())
- return 1
-
+ return
+ if(!controls_inside && usr == occupant)
+ return
if(panel_open)
to_chat(usr, "Close the maintenance panel first.")
- return 0
+ return
+ if(stat & (NOPOWER|BROKEN))
+ return
- if((usr.contents.Find(src) || ((get_dist(src, usr) <= 1) && istype(loc, /turf))) || (istype(usr, /mob/living/silicon/ai)))
- if(href_list["chemical"])
- if(occupant)
- if(occupant.stat == DEAD)
- to_chat(usr, "This person has no life for to preserve anymore. Take [occupant.p_them()] to a department capable of reanimating them.")
- else if(occupant.health > min_health || (href_list["chemical"] in emergency_chems))
- inject_chemical(usr,href_list["chemical"],text2num(href_list["amount"]))
- else
- to_chat(usr, "This person is not in good enough condition for sleepers to be effective! Use another means of treatment, such as cryogenics!")
-
- if(href_list["removebeaker"])
+ . = TRUE
+ switch(action)
+ if("chemical")
+ if(!occupant)
+ return
+ if(occupant.stat == DEAD)
+ to_chat(usr, "This person has no life to preserve anymore. Take [occupant.p_them()] to a department capable of reanimating them.")
+ return
+ var/chemical = params["chemid"]
+ var/amount = text2num(params["amount"])
+ if(!length(chemical) || amount <= 0)
+ return
+ if(occupant.health > min_health || (chemical in emergency_chems))
+ inject_chemical(usr, chemical, amount)
+ else
+ to_chat(usr, "This person is not in good enough condition for sleepers to be effective! Use another means of treatment, such as cryogenics!")
+ if("removebeaker")
remove_beaker()
-
- if(href_list["togglefilter"])
+ if("togglefilter")
toggle_filter()
-
- if(href_list["ejectify"])
+ if("ejectify")
eject()
-
- if(href_list["auto_eject_dead_on"])
+ if("auto_eject_dead_on")
auto_eject_dead = TRUE
-
- if(href_list["auto_eject_dead_off"])
+ if("auto_eject_dead_off")
auto_eject_dead = FALSE
-
- add_fingerprint(usr)
- return 1
+ else
+ return FALSE
+ add_fingerprint(usr)
/obj/machinery/sleeper/attackby(obj/item/I, mob/user, params)
if(istype(I, /obj/item/reagent_containers/glass))
@@ -290,6 +295,7 @@
beaker = I
I.forceMove(src)
user.visible_message("[user] adds \a [I] to [src]!", "You add \a [I] to [src]!")
+ SStgui.update_uis(src)
return
else
@@ -328,6 +334,7 @@
to_chat(M, "You feel cool air surround you. You go numb as your senses turn inward.")
add_fingerprint(user)
qdel(G)
+ SStgui.update_uis(src)
return
return ..()
@@ -374,9 +381,11 @@
occupant = null
updateUsrDialog()
update_icon()
+ SStgui.update_uis(src)
if(A == beaker)
beaker = null
updateUsrDialog()
+ SStgui.update_uis(src)
/obj/machinery/sleeper/emp_act(severity)
if(filtering)
@@ -394,7 +403,7 @@
qdel(src)
/obj/machinery/sleeper/proc/toggle_filter()
- if(filtering)
+ if(filtering || !beaker)
filtering = 0
else
filtering = 1
@@ -410,29 +419,28 @@
// eject trash the occupant dropped
for(var/atom/movable/A in contents - component_parts - list(beaker))
A.forceMove(loc)
+ SStgui.update_uis(src)
/obj/machinery/sleeper/force_eject_occupant()
go_out()
-/obj/machinery/sleeper/proc/inject_chemical(mob/living/user as mob, chemical, amount)
+/obj/machinery/sleeper/proc/inject_chemical(mob/living/user, chemical, amount)
if(!(chemical in possible_chems))
to_chat(user, "The sleeper does not offer that chemical!")
return
+ if(!(amount in amounts))
+ return
if(occupant)
if(occupant.reagents)
if(occupant.reagents.get_reagent_amount(chemical) + amount <= max_chem)
occupant.reagents.add_reagent(chemical, amount)
- return
else
to_chat(user, "You can not inject any more of this chemical.")
- return
else
to_chat(user, "The patient rejects the chemicals!")
- return
else
to_chat(user, "There's no occupant in the sleeper!")
- return
/obj/machinery/sleeper/verb/eject()
set name = "Eject Sleeper"
@@ -459,6 +467,7 @@
filtering = 0
beaker.forceMove(usr.loc)
beaker = null
+ SStgui.update_uis(src)
add_fingerprint(usr)
return
@@ -511,6 +520,7 @@
add_fingerprint(user)
if(user.pulling == L)
user.stop_pulling()
+ SStgui.update_uis(src)
return
return
@@ -547,6 +557,7 @@
for(var/obj/O in src)
qdel(O)
add_fingerprint(usr)
+ SStgui.update_uis(src)
return
return
diff --git a/code/game/machinery/adv_med.dm b/code/game/machinery/adv_med.dm
index 3fa78dd02e6..97ab80fc1d7 100644
--- a/code/game/machinery/adv_med.dm
+++ b/code/game/machinery/adv_med.dm
@@ -65,6 +65,7 @@
icon_state = "body_scanner_1"
add_fingerprint(user)
qdel(TYPECAST_YOUR_SHIT)
+ SStgui.update_uis(src)
return
return ..()
@@ -127,12 +128,13 @@
occupant = H
icon_state = "bodyscanner"
add_fingerprint(user)
+ SStgui.update_uis(src)
/obj/machinery/bodyscanner/attack_ai(user)
return attack_hand(user)
/obj/machinery/bodyscanner/attack_ghost(user)
- return attack_hand(user)
+ tgui_interact(user)
/obj/machinery/bodyscanner/attack_hand(user)
if(stat & (NOPOWER|BROKEN))
@@ -145,7 +147,7 @@
to_chat(user, "Close the maintenance panel first.")
return
- ui_interact(user)
+ tgui_interact(user)
/obj/machinery/bodyscanner/relaymove(mob/user)
if(user.incapacitated())
@@ -171,6 +173,7 @@
// eject trash the occupant dropped
for(var/atom/movable/A in contents - component_parts)
A.forceMove(loc)
+ SStgui.update_uis(src)
/obj/machinery/bodyscanner/force_eject_occupant()
go_out()
@@ -192,17 +195,16 @@
new /obj/effect/gibspawner/generic(get_turf(loc)) //I REPLACE YOUR TECHNOLOGY WITH FLESH!
qdel(src)
-/obj/machinery/bodyscanner/ui_interact(mob/user, ui_key = "main", datum/nanoui/ui = null, force_open = 1)
- ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open)
+/obj/machinery/bodyscanner/tgui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/tgui_state/state = GLOB.tgui_default_state)
+ ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
- ui = new(user, src, ui_key, "adv_med.tmpl", "Body Scanner", 690, 600)
+ ui = new(user, src, ui_key, "BodyScanner", "Body Scanner", 690, 600)
ui.open()
- ui.set_auto_update(1)
-/obj/machinery/bodyscanner/ui_data(mob/user, datum/topic_state/state)
+/obj/machinery/bodyscanner/tgui_data(mob/user)
var/data[0]
- data["occupied"] = occupant ? 1 : 0
+ data["occupied"] = occupant ? TRUE : FALSE
var/occupantData[0]
if(occupant)
@@ -236,9 +238,9 @@
occupantData["hasBorer"] = occupant.has_brain_worms()
var/bloodData[0]
- bloodData["hasBlood"] = 0
+ bloodData["hasBlood"] = FALSE
if(!(NO_BLOOD in occupant.dna.species.species_traits))
- bloodData["hasBlood"] = 1
+ bloodData["hasBlood"] = TRUE
bloodData["volume"] = occupant.blood_volume
bloodData["percent"] = round(((occupant.blood_volume / BLOOD_VOLUME_NORMAL)*100))
bloodData["pulse"] = occupant.get_pulse(GETPULSE_TOOL)
@@ -282,19 +284,19 @@
if(E.status & ORGAN_BROKEN)
organStatus["broken"] = E.broken_description
if(E.is_robotic())
- organStatus["robotic"] = 1
+ organStatus["robotic"] = TRUE
if(E.status & ORGAN_SPLINTED)
- organStatus["splinted"] = 1
+ organStatus["splinted"] = TRUE
if(E.status & ORGAN_DEAD)
- organStatus["dead"] = 1
+ organStatus["dead"] = TRUE
organData["status"] = organStatus
if(istype(E, /obj/item/organ/external/chest) && occupant.is_lung_ruptured())
- organData["lungRuptured"] = 1
+ organData["lungRuptured"] = TRUE
if(E.internal_bleeding)
- organData["internalBleeding"] = 1
+ organData["internalBleeding"] = TRUE
extOrganData.Add(list(organData))
@@ -319,27 +321,33 @@
occupantData["blind"] = (BLINDNESS in occupant.mutations)
occupantData["colourblind"] = (COLOURBLIND in occupant.mutations)
- occupantData["nearsighted"] = (NEARSIGHTED in occupant.mutations)
+ occupantData["nearsighted"] = (NEARSIGHTED in occupant.mutations)
data["occupant"] = occupantData
return data
-/obj/machinery/bodyscanner/Topic(href, href_list)
+/obj/machinery/bodyscanner/tgui_act(action, params)
if(..())
- return 1
+ return
+ if(stat & (NOPOWER|BROKEN))
+ return
- if(href_list["ejectify"])
- eject()
-
- if(href_list["print_p"])
- visible_message("[src] rattles and prints out a sheet of paper.")
- var/obj/item/paper/P = new /obj/item/paper(loc)
- playsound(loc, 'sound/goonstation/machines/printer_dotmatrix.ogg', 50, 1)
- P.info = "
![]() | "
- if(j == 5)
- dat += "
![]() | "
- if(j == 5)
- dat += "
| Brute Damage: | -Brain Damage: | -
| {{:helper.displayBar(data.occupant.bruteLoss, 0, data.occupant.maxHealth+100, (data.occupant.bruteLoss < 15) ? 'notgood' : (data.occupant.bruteLoss < 40) ? 'average' : 'bad', data.occupant.bruteLoss) }} | -{{:helper.displayBar(data.occupant.brainLoss, 0, data.occupant.maxHealth+100, (data.occupant.brainLoss < 15) ? 'notgood' : (data.occupant.brainLoss < 40) ? 'average' : 'bad', data.occupant.brainLoss)}} | -
| Resp. Damage: | -Radiation Level: | -
| {{:helper.displayBar(data.occupant.oxyLoss, 0, data.occupant.maxHealth+100, (data.occupant.oxyLoss < 15) ? 'notgood' : (data.occupant.oxyLoss < 40) ? 'average' : 'bad', data.occupant.oxyLoss)}} | -{{:helper.displayBar(data.occupant.radLoss, 0, data.occupant.maxHealth+100, (data.occupant.radLoss < 15) ? 'notgood' : (data.occupant.radLoss < 40) ? 'average' : 'bad', data.occupant.radLoss)}} | -
| Toxin Damage: | -Genetic Damage: | -
| {{:helper.displayBar(data.occupant.toxLoss, 0, data.occupant.maxHealth+100, (data.occupant.toxLoss < 15) ? 'notgood' : (data.occupant.toxLoss < 40) ? 'average' : 'bad', data.occupant.toxLoss)}} | -{{:helper.displayBar(data.occupant.cloneLoss, 0, data.occupant.maxHealth+100, (data.occupant.cloneLoss < 15) ? 'notgood' : (data.occupant.cloneLoss < 40) ? 'average' : 'bad', data.occupant.cloneLoss)}} | -
| Burn Severity: | -Paralysis %: ({{:helper.smoothRound(data.occupant.paralysisSeconds)}} seconds left) | -
| {{:helper.displayBar(data.occupant.fireLoss, 0, data.occupant.maxHealth+100, (data.occupant.fireLoss < 15) ? 'notgood' : (data.occupant.fireLoss < 40) ? 'average' : 'bad', data.occupant.fireLoss)}} | -{{:helper.displayBar(data.occupant.paralysis, 0, 100, (data.occupant.paralysis < 25) ? 'notgood' : (data.occupant.paralysis < 50) ? 'average' : 'bad', data.occupant.paralysis)}} | -
| {{:value.name}} |
| Name | -Total damage | -Brute damage | -Burn damage | -Injuries | -
|---|---|---|---|---|
| {{:value.name}} | -{{:helper.displayBar(value.totalLoss, 0, value.maxHealth, (value.totalLoss < value.bruised) ? 'notgood' : (value.totalLoss < value.broken) ? 'average' : 'bad', value.totalLoss) }} | -{{if value.bruteLoss> 0}} {{else}} {{/if}} {{:helper.smoothRound(value.bruteLoss)}} | -{{if value.fireLoss> 0}} {{else}} {{/if}} {{:helper.smoothRound(value.fireLoss)}} | -
-
- {{if value.internalBleeding}} internal bleeding {{/if}} - {{if value.lungRuptured}} lung ruptured {{/if}} - {{if value.status.broken}} {{:value.status.broken}} {{/if}} - {{if value.germ_level > 100}} - {{if value.germ_level < 300}} - mild infection - {{else value.germ_level < 400}} - mild infection+ - {{else value.germ_level < 500}} - mild infection++ - {{else value.germ_level < 700}} - acute infection - {{else value.germ_level < 800}} - acute infection+ - {{else value.germ_level < 900}} - acute infection++ - {{else value.germ_level >= 900}} - septic - {{/if}} - {{/if}} - {{if value.open}} open incision {{/if}} - - {{if value.status.splinted}} splinted {{/if}} - {{if value.status.robotic}} robotic {{/if}} - {{if value.status.dead}} DEAD - {{/for}} - {{/if}} - |
-
| Name | -Brute damage | -Injuries | -
|---|---|---|
| {{:value.name}} |
- {{:helper.displayBar(value.damage, 0, value.maxHealth, (value.damage < value.bruised) ? 'notgood' : (value.damage < value.broken) ? 'average' : 'bad', value.damage) }} | -
-
- {{if value.germ_level > 100}}
- {{if value.germ_level < 300}}
- mild infection - {{else value.germ_level < 400}} - mild infection+ - {{else value.germ_level < 500}} - mild infection++ - {{else value.germ_level < 700}} - acute Infection - {{else value.germ_level < 800}} - acute Infection+ - {{else value.germ_level < 900}} - acute Infection++ - {{else value.germ_level >= 900}} - septic - {{/if}} - {{/if}} - - {{if value.robotic == 1}} robotic {{/if}} - {{if value.robotic == 2}} assisted {{/if}} - {{if value.dead}} DEAD {{/if}} - |
-
| - {{:helper.link('Create pill (100u max)', 'arrow-right', {'createpill': 1})}} - | -- {{:helper.link('Multiple', 'arrow-right', {'createpill_multiple': 1})}} - | -- {{:helper.link('Style', 'pencil', {'change_pill': 1})}} - | -
- |
-
| - {{:helper.link('Create patch (30u max)', 'arrow-right', {'createpatch': 1})}} - | -- {{:helper.link('Multiple', 'arrow-right', {'createpatch_multiple': 1})}} - | -||
| - {{:helper.link('Create bottle (50u max)', 'arrow-right', {'createbottle': 1})}} - | -- {{:helper.link('Style', 'pencil', {'change_bottle': 1})}} - | -
- |
-
| - {{:helper.link('Pill Bottle (' + data.loaded_pill_bottle_contents_len + '/' + data.loaded_pill_bottle_storage_slots + ')', 'eject', {'ejectp': 1})}} - | -- {{:helper.link('Change Wrapper', 'pencil', {'change_pillbottle': 1})}} - | -
| - {{:helper.link('No Pill Bottle', 'eject', {}, 'disabled')}} - | -
| {{:value.name}}: | -{{:helper.link(value.enabled, null, {'command' : value.command, 'activate' : value.act ? 1 : 0}, value.active ? 'selected' : null)}} | -{{:helper.link(value.disabled, null, {'command' : value.command, 'activate' : value.act ? 0 : 1}, !value.active ? (value.danger ? 'redButton' : 'selected') : null)}} | -
|
- {{for data.general.fields}}
-
-
- {{/for}}
- {{:value.field}}
- {{:helper.link(value.value, value.edit ? 'pencil' : 'user-times', {'field' : value.edit}, value.edit ? null : 'disabled')}}
- |
-
- {{if data.general.has_photos}}
- {{for data.general.photos}}
- {{if value.photo}}
- |
-
- Lines are a series of chords, separated by commas (,), each with notes seperated by hyphens (-).
-
- Every note in a chord will play together, with the chord timed by the tempo as defined above.
-
- Notes are played by the names of the note, and optionally, the accidental, and/or the octave number.
-
- By default, every note is natural and in octave 3. Defining a different state for either is remembered for each note.
-
- Chords can be played simply by seperating each note with a hyphon: A-C#,Cn-E,E-G#,Gn-B.
- A pause may be denoted by an empty chord: C,E,,C,G.
-
- To make a chord be a different time, end it with /x, where the chord length will be length defined by tempo / x, eg: C,G/2,E/4.
-
- Combined, an example line is: E-E4/4,F#/2,G#/8,B/8,E3-E4/4. -
+ Lines are a series of chords, separated by commas
+
+ Every note in a chord will play together,
+ with the chord timed by the
+
+ Notes are played by the
+
+ By default, every note is
+
+
+ A
+ To make a chord be a different time, end it
+ with /x, where the chord length will be length defined by
+
+ Combined, an example line is: E-E4/4,F#/2,G#/8,B/8,E3-E4/4. +
+ Lines are a series of chords, separated by commas
+
+ Every note in a chord will play together,
+ with the chord timed by the
+
+ Notes are played by the
+
+ By default, every note is
+
+
+ A
+ To make a chord be a different time, end it
+ with /x, where the chord length will be length defined by
+
+ Combined, an example line is: E-E4/4,F#/2,G#/8,B/8,E3-E4/4. +