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 = "
Body Scan - [href_list["name"]]

" - P.info += "Time of scan: [station_time_timestamp()]

" - P.info += "[generate_printing_text()]" - P.info += "

Notes:
" - P.name = "Body Scan - [href_list["name"]]" + . = TRUE + switch(action) + if("ejectify") + eject() + if("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, TRUE) + var/name = occupant ? occupant.name : "Unknown" + P.info = "
Body Scan - [name]

" + P.info += "Time of scan: [station_time_timestamp()]

" + P.info += "[generate_printing_text()]" + P.info += "

Notes:
" + P.name = "Body Scan - [name]" + else + return FALSE /obj/machinery/bodyscanner/proc/generate_printing_text() var/dat = "" diff --git a/code/game/machinery/atmoalter/canister.dm b/code/game/machinery/atmoalter/canister.dm index e927415c759..3c09da548f5 100644 --- a/code/game/machinery/atmoalter/canister.dm +++ b/code/game/machinery/atmoalter/canister.dm @@ -11,6 +11,7 @@ list("name" = "\[SPECIAL\]", "icon" = "whiters") ) possibleseccolor = list( // no point in having the N2O and "whiters" ones in these lists + list("name" = "\[None\]", "icon" = "none"), list("name" = "\[N2\]", "icon" = "red-c"), list("name" = "\[O2\]", "icon" = "blue-c"), list("name" = "\[Toxin (Bio)\]", "icon" = "orange-c"), @@ -19,6 +20,7 @@ list("name" = "\[CAUTION\]", "icon" = "yellow-c") ) possibletertcolor = list( + list("name" = "\[None\]", "icon" = "none"), list("name" = "\[N2\]", "icon" = "red-c-1"), list("name" = "\[O2\]", "icon" = "blue-c-1"), list("name" = "\[Toxin (Bio)\]", "icon" = "orange-c-1"), @@ -27,6 +29,7 @@ list("name" = "\[CAUTION\]", "icon" = "yellow-c-1") ) possiblequartcolor = list( + list("name" = "\[None\]", "icon" = "none"), list("name" = "\[N2\]", "icon" = "red-c-2"), list("name" = "\[O2\]", "icon" = "blue-c-2"), list("name" = "\[Toxin (Bio)\]", "icon" = "orange-c-2"), @@ -34,12 +37,6 @@ list("name" = "\[Air\]", "icon" = "grey-c-2"), list("name" = "\[CAUTION\]", "icon" = "yellow-c-2") ) - - possibledecals = list( //var that stores all possible decals, used by ui - list("name" = "Low temperature canister", "icon" = "cold"), - list("name" = "High temperature canister", "icon" = "hot"), - list("name" = "Plasma containing canister", "icon" = "plasma") - ) GLOBAL_DATUM_INIT(canister_icon_container, /datum/canister_icons, new()) /obj/machinery/portable_atmospherics/canister @@ -52,22 +49,17 @@ GLOBAL_DATUM_INIT(canister_icon_container, /datum/canister_icons, new()) max_integrity = 250 integrity_failure = 100 - var/menu = 0 - //used by nanoui: 0 = main menu, 1 = relabel - var/valve_open = 0 var/release_pressure = ONE_ATMOSPHERE var/list/canister_color //variable that stores colours - var/list/decals //list that stores the decals + var/list/color_index // list which stores tgui color indexes for the recoloring options, to enable previously-set colors to show up right //lists for check_change() var/list/oldcolor - var/list/olddecals //passed to the ui to render the color lists var/list/colorcontainer - var/list/possibledecals var/can_label = 1 var/filled = 0.5 @@ -82,18 +74,12 @@ GLOBAL_DATUM_INIT(canister_icon_container, /datum/canister_icons, new()) /obj/machinery/portable_atmospherics/canister/New() ..() canister_color = list( - "prim" = "yellow", - "sec" = "none", - "ter" = "none", - "quart" = "none") + "prim" = "yellow", + "sec" = "none", + "ter" = "none", + "quart" = "none" + ) oldcolor = new /list() - decals = list("cold" = 0, "hot" = 0, "plasma" = 0) - colorcontainer = list() - possibledecals = list() - update_icon() - -/obj/machinery/portable_atmospherics/canister/proc/init_data_vars() - //passed to the ui to render the color lists colorcontainer = list( "prim" = list( "options" = GLOB.canister_icon_container.possiblemaincolor, @@ -112,26 +98,8 @@ GLOBAL_DATUM_INIT(canister_icon_container, /datum/canister_icons, new()) "name" = "Quaternary color", ) ) - - //var/anycolor used by the nanoUI, 0: no color applied. 1: color applied - for(var/C in colorcontainer) - if(C == "prim") continue - var/list/L = colorcontainer[C] - if(!(canister_color[C]) || (canister_color[C] == "none")) - L.Add(list("anycolor" = 0)) - else - L.Add(list("anycolor" = 1)) - colorcontainer[C] = L - - possibledecals = list() - - var/i - var/list/L = GLOB.canister_icon_container.possibledecals - for(i=1;i<=L.len;i++) - var/list/LL = L[i] - LL = LL.Copy() //make sure we don't edit the datum list - LL.Add(list("active" = decals[LL["icon"]])) //"active" used by nanoUI - possibledecals.Add(LL) + color_index = list() + update_icon() /obj/machinery/portable_atmospherics/canister/proc/check_change() var/old_flag = update_flag @@ -155,10 +123,6 @@ GLOBAL_DATUM_INIT(canister_icon_container, /datum/canister_icons, new()) update_flag |= 64 oldcolor = canister_color.Copy() - if(list2params(olddecals) != list2params(decals)) - update_flag |= 128 - olddecals = decals.Copy() - if(update_flag == old_flag) return 1 else @@ -174,17 +138,16 @@ update_flag 16 = tank_pressure < 15*ONE_ATMOS 32 = tank_pressure go boom. 64 = colors -128 = decals -(note: colors and decals has to be applied every icon update) +(note: colors has to be applied every icon update) */ - if(src.destroyed) - src.overlays = 0 - src.icon_state = text("[]-1", src.canister_color["prim"])//yes, I KNOW the colours don't reflect when the can's borked, whatever. + if(destroyed) + overlays = 0 + icon_state = text("[]-1", canister_color["prim"])//yes, I KNOW the colours don't reflect when the can's borked, whatever. return - if(icon_state != src.canister_color["prim"]) - icon_state = src.canister_color["prim"] + if(icon_state != canister_color["prim"]) + icon_state = canister_color["prim"] if(check_change()) //Returns 1 if no change needed to icons. return @@ -192,14 +155,12 @@ update_flag overlays.Cut() for(var/C in canister_color) - if(C == "prim") continue - if(canister_color[C] == "none") continue + if(C == "prim") + continue + if(canister_color[C] == "none") + continue overlays.Add(canister_color[C]) - for(var/D in decals) - if(decals[D]) - overlays.Add("decal-" + D) - if(update_flag & 1) overlays += "can-open" if(update_flag & 2) @@ -213,35 +174,9 @@ update_flag else if(update_flag & 32) overlays += "can-o3" - update_flag &= ~196 //the flags 128 and 64 represent change, not states. As such, we have to reset them to be able to detect a change on the next go. + update_flag &= ~68 //the flag 64 represents change, not states. As such, we have to reset them to be able to detect a change on the next go. return -//template modification exploit prevention, used in Topic() -/obj/machinery/portable_atmospherics/canister/proc/is_a_color(var/inputVar, var/checkColor = "all") - if(checkColor == "prim" || checkColor == "all") - for(var/list/L in GLOB.canister_icon_container.possiblemaincolor) - if(L["icon"] == inputVar) - return 1 - if(checkColor == "sec" || checkColor == "all") - for(var/list/L in GLOB.canister_icon_container.possibleseccolor) - if(L["icon"] == inputVar) - return 1 - if(checkColor == "ter" || checkColor == "all") - for(var/list/L in GLOB.canister_icon_container.possibletertcolor) - if(L["icon"] == inputVar) - return 1 - if(checkColor == "quart" || checkColor == "all") - for(var/list/L in GLOB.canister_icon_container.possiblequartcolor) - if(L["icon"] == inputVar) - return 1 - return 0 - -/obj/machinery/portable_atmospherics/canister/proc/is_a_decal(var/inputVar) - for(var/list/L in GLOB.canister_icon_container.possibledecals) - if(L["icon"] == inputVar) - return 1 - return 0 - /obj/machinery/portable_atmospherics/canister/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume) ..() if(exposed_temperature > temperature_resistance) @@ -271,7 +206,7 @@ update_flag stat |= BROKEN density = FALSE - playsound(src.loc, 'sound/effects/spray.ogg', 10, TRUE, -3) + playsound(loc, 'sound/effects/spray.ogg', 10, TRUE, -3) update_icon() if(holding) @@ -297,7 +232,7 @@ update_flag var/transfer_moles = 0 if((air_contents.temperature > 0) && (pressure_delta > 0)) - transfer_moles = pressure_delta*environment.volume/(air_contents.temperature * R_IDEAL_GAS_EQUATION) + transfer_moles = pressure_delta * environment.volume / (air_contents.temperature * R_IDEAL_GAS_EQUATION) //Actually transfer the gas var/datum/gas_mixture/removed = air_contents.remove(transfer_moles) @@ -307,7 +242,7 @@ update_flag else loc.assume_air(removed) air_update_turf() - src.update_icon() + update_icon() if(air_contents.return_pressure() < 1) @@ -315,20 +250,20 @@ update_flag else can_label = 0 - src.updateDialog() + updateDialog() return /obj/machinery/portable_atmospherics/canister/return_air() return air_contents /obj/machinery/portable_atmospherics/canister/proc/return_temperature() - var/datum/gas_mixture/GM = src.return_air() + var/datum/gas_mixture/GM = return_air() if(GM && GM.volume>0) return GM.temperature return 0 /obj/machinery/portable_atmospherics/canister/proc/return_pressure() - var/datum/gas_mixture/GM = src.return_air() + var/datum/gas_mixture/GM = return_air() if(GM && GM.volume>0) return GM.return_pressure() return 0 @@ -343,141 +278,113 @@ update_flag else if(valve_open && holding) investigate_log("[key_name(user)] started a transfer into [holding].
", "atmos") -/obj/machinery/portable_atmospherics/canister/attack_ai(var/mob/user as mob) - src.add_hiddenprint(user) - return src.attack_hand(user) +/obj/machinery/portable_atmospherics/canister/attack_ai(var/mob/user) + add_hiddenprint(user) + return attack_hand(user) -/obj/machinery/portable_atmospherics/canister/attack_ghost(var/mob/user as mob) - return src.ui_interact(user) +/obj/machinery/portable_atmospherics/canister/attack_ghost(var/mob/user) + return tgui_interact(user) -/obj/machinery/portable_atmospherics/canister/attack_hand(var/mob/user as mob) - return src.ui_interact(user) +/obj/machinery/portable_atmospherics/canister/attack_hand(var/mob/user) + return tgui_interact(user) -/obj/machinery/portable_atmospherics/canister/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1, var/datum/topic_state/state = GLOB.physical_state) - if(src.destroyed) - 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) +/obj/machinery/portable_atmospherics/canister/tgui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = TRUE, datum/tgui/master_ui = null, datum/tgui_state/state = GLOB.tgui_physical_state) + 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, "canister.tmpl", "Canister", 480, 400, state = state) - // open the new ui window + ui = new(user, src, ui_key, "Canister", name, 600, 350, master_ui, state) ui.open() - // auto update every Master Controller tick - ui.set_auto_update(1) - -/obj/machinery/portable_atmospherics/canister/ui_data(mob/user, datum/topic_state/state) - init_data_vars() //set up var/colorcontainer and var/possibledecals - - // this is the data which will be sent to the ui - var/data[0] - data["name"] = name - data["menu"] = menu ? 1 : 0 - data["canLabel"] = can_label ? 1 : 0 - data["canister_color"] = canister_color - data["colorContainer"] = colorcontainer.Copy() - colorcontainer.Cut() - data["possibleDecals"] = possibledecals.Copy() - possibledecals.Cut() +/obj/machinery/portable_atmospherics/canister/tgui_data() + var/data = list() data["portConnected"] = connected_port ? 1 : 0 data["tankPressure"] = round(air_contents.return_pressure() ? air_contents.return_pressure() : 0) data["releasePressure"] = round(release_pressure ? release_pressure : 0) - data["minReleasePressure"] = round(ONE_ATMOSPHERE/10) - data["maxReleasePressure"] = round(10*ONE_ATMOSPHERE) + data["defaultReleasePressure"] = ONE_ATMOSPHERE + data["minReleasePressure"] = round(ONE_ATMOSPHERE / 10) + data["maxReleasePressure"] = round(ONE_ATMOSPHERE * 10) data["valveOpen"] = valve_open ? 1 : 0 - + data["name"] = name + data["canLabel"] = can_label ? 1 : 0 + data["colorContainer"] = colorcontainer.Copy() + data["color_index"] = color_index data["hasHoldingTank"] = holding ? 1 : 0 if(holding) data["holdingTank"] = list("name" = holding.name, "tankPressure" = round(holding.air_contents.return_pressure())) - return data -/obj/machinery/portable_atmospherics/canister/Topic(href, href_list) +/obj/machinery/portable_atmospherics/canister/tgui_act(action, params) if(..()) - return 1 - - if(href_list["choice"] == "menu") - menu = text2num(href_list["mode_target"]) - - if(href_list["toggle"]) - var/logmsg - if(valve_open) - if(holding) - logmsg = "Valve was closed by [key_name(usr)], stopping the transfer into the [holding]
" - else - logmsg = "Valve was closed by [key_name(usr)], stopping the transfer into the air
" - else - if(holding) - logmsg = "Valve was opened by [key_name(usr)], starting the transfer into the [holding]
" - else - logmsg = "Valve was opened by [key_name(usr)], starting the transfer into the air
" - if(air_contents.toxins > 0) - message_admins("[key_name_admin(usr)] opened a canister that contains plasma in [get_area(src)]! (JMP)") - log_admin("[key_name(usr)] opened a canister that contains plasma at [get_area(src)]: [x], [y], [z]") - if(air_contents.sleeping_agent > 0) - message_admins("[key_name_admin(usr)] opened a canister that contains N2O in [get_area(src)]! (JMP)") - log_admin("[key_name(usr)] opened a canister that contains N2O at [get_area(src)]: [x], [y], [z]") - investigate_log(logmsg, "atmos") - release_log += logmsg - valve_open = !valve_open - - if(href_list["remove_tank"]) - if(holding) - if(valve_open) - valve_open = 0 - release_log += "Valve was closed by [key_name(usr)], stopping the transfer into the [holding]
" - holding.loc = loc - holding = null - - if(href_list["pressure_adj"]) - var/diff = text2num(href_list["pressure_adj"]) - if(diff > 0) - release_pressure = min(10*ONE_ATMOSPHERE, release_pressure+diff) - else - release_pressure = max(ONE_ATMOSPHERE/10, release_pressure+diff) - - if(href_list["rename"]) - if(can_label) - var/T = sanitize(copytext(input("Choose canister label", "Name", name) as text|null,1,MAX_NAME_LEN)) - if(can_label) //Exploit prevention - if(T) - name = T + return + var/can_min_release_pressure = round(ONE_ATMOSPHERE / 10) + var/can_max_release_pressure = round(ONE_ATMOSPHERE * 10) + . = TRUE + switch(action) + if("relabel") + if(can_label) + var/T = sanitize(copytext(input("Choose canister label", "Name", name) as text|null, 1, MAX_NAME_LEN)) + if(can_label) //Exploit prevention + if(T) + name = T + else + name = "canister" else - name = "canister" + to_chat(usr, "As you attempted to rename it the pressure rose!") + . = FALSE + if("pressure") + var/pressure = params["pressure"] + if(pressure == "reset") + pressure = ONE_ATMOSPHERE + else if(pressure == "min") + pressure = can_min_release_pressure + else if(pressure == "max") + pressure = can_max_release_pressure + else if(pressure == "input") + pressure = input("New release pressure ([can_min_release_pressure]-[can_max_release_pressure] kPa):", name, release_pressure) as num|null + if(isnull(pressure)) + . = FALSE + else if(text2num(pressure) != null) + pressure = text2num(pressure) + if(.) + release_pressure = clamp(round(pressure), can_min_release_pressure, can_max_release_pressure) + investigate_log("was set to [release_pressure] kPa by [key_name(usr)].", "atmos") + if("valve") + var/logmsg + valve_open = !valve_open + if(valve_open) + logmsg = "Valve was opened by [key_name(usr)], starting a transfer into the [holding || "air"].
" + if(!holding) + logmsg = "Valve was opened by [key_name(usr)], starting a transfer into the [holding || "air"].
" + if(air_contents.toxins > 0) + message_admins("[key_name_admin(usr)] opened a canister that contains plasma in [get_area(src)]! (JMP)") + log_admin("[key_name(usr)] opened a canister that contains plasma at [get_area(src)]: [x], [y], [z]") + if(air_contents.sleeping_agent > 0) + message_admins("[key_name_admin(usr)] opened a canister that contains N2O in [get_area(src)]! (JMP)") + log_admin("[key_name(usr)] opened a canister that contains N2O at [get_area(src)]: [x], [y], [z]") else - to_chat(usr, "As you attempted to rename it the pressure rose!") - - if(href_list["choice"] == "Primary color") - if(is_a_color(href_list["icon"],"prim")) - canister_color["prim"] = href_list["icon"] - if(href_list["choice"] == "Secondary color") - if(href_list["icon"] == "none") - canister_color["sec"] = "none" - else if(is_a_color(href_list["icon"],"sec")) - canister_color["sec"] = href_list["icon"] - if(href_list["choice"] == "Tertiary color") - if(href_list["icon"] == "none") - canister_color["ter"] = "none" - else if(is_a_color(href_list["icon"],"ter")) - canister_color["ter"] = href_list["icon"] - if(href_list["choice"] == "Quaternary color") - if(href_list["icon"] == "none") - canister_color["quart"] = "none" - else if(is_a_color(href_list["icon"],"quart")) - canister_color["quart"] = href_list["icon"] - - if(href_list["choice"] == "decals") - if(is_a_decal(href_list["icon"])) - decals[href_list["icon"]] = (decals[href_list["icon"]] == 0) - - src.add_fingerprint(usr) + logmsg = "Valve was closed by [key_name(usr)], stopping the transfer into the [holding || "air"].
" + investigate_log(logmsg, "atmos") + release_log += logmsg + if("eject") + if(holding) + if(valve_open) + release_log += "[key_name(usr)] removed the [holding], leaving the valve open and transferring into the air
" + message_admins("[ADMIN_LOOKUPFLW(usr)] removed [holding] from [src] with valve still open at [ADMIN_VERBOSEJMP(src)] releasing contents into the air.") + investigate_log("[key_name(usr)] removed the [holding], leaving the valve open and transferring into the air.", "atmos") + replace_tank(usr, FALSE) + if("recolor") + if(can_label) + var/ctype = params["ctype"] + var/cnum = text2num(params["nc"]) + if(isnull(colorcontainer[ctype])) + message_admins("[key_name_admin(usr)] passed an invalid ctype var to a canister.") + return + var/newcolor = sanitize_integer(cnum, 0, length(colorcontainer[ctype]["options"])) + color_index[ctype] = newcolor + newcolor++ // javascript starts arrays at 0, byond (for some reason) starts them at 1, this converts JS values to byond values + canister_color[ctype] = colorcontainer[ctype]["options"][newcolor]["icon"] + add_fingerprint(usr) update_icon() - return 1 - /obj/machinery/portable_atmospherics/canister/toxins name = "Canister \[Toxin (Plasma)\]" @@ -513,19 +420,18 @@ update_flag ..() canister_color["prim"] = "orange" - decals["plasma"] = 1 - src.air_contents.toxins = (src.maximum_pressure*filled)*air_contents.volume/(R_IDEAL_GAS_EQUATION*air_contents.temperature) + air_contents.toxins = (maximum_pressure * filled) * air_contents.volume / (R_IDEAL_GAS_EQUATION * air_contents.temperature) - src.update_icon() + update_icon() return 1 /obj/machinery/portable_atmospherics/canister/oxygen/New() ..() canister_color["prim"] = "blue" - src.air_contents.oxygen = (src.maximum_pressure*filled)*air_contents.volume/(R_IDEAL_GAS_EQUATION*air_contents.temperature) + air_contents.oxygen = (maximum_pressure * filled) * air_contents.volume / (R_IDEAL_GAS_EQUATION * air_contents.temperature) - src.update_icon() + update_icon() return 1 /obj/machinery/portable_atmospherics/canister/sleeping_agent/New() @@ -534,25 +440,25 @@ update_flag canister_color["prim"] = "redws" air_contents.sleeping_agent = (maximum_pressure * filled) * air_contents.volume / (R_IDEAL_GAS_EQUATION * air_contents.temperature) - src.update_icon() + update_icon() return 1 /obj/machinery/portable_atmospherics/canister/nitrogen/New() ..() canister_color["prim"] = "red" - src.air_contents.nitrogen = (src.maximum_pressure*filled)*air_contents.volume/(R_IDEAL_GAS_EQUATION*air_contents.temperature) + air_contents.nitrogen = (maximum_pressure * filled) * air_contents.volume / (R_IDEAL_GAS_EQUATION * air_contents.temperature) - src.update_icon() + update_icon() return 1 /obj/machinery/portable_atmospherics/canister/carbon_dioxide/New() ..() canister_color["prim"] = "black" - src.air_contents.carbon_dioxide = (src.maximum_pressure*filled)*air_contents.volume/(R_IDEAL_GAS_EQUATION*air_contents.temperature) + air_contents.carbon_dioxide = (maximum_pressure * filled) * air_contents.volume / (R_IDEAL_GAS_EQUATION * air_contents.temperature) - src.update_icon() + update_icon() return 1 @@ -560,17 +466,17 @@ update_flag ..() canister_color["prim"] = "grey" - src.air_contents.oxygen = (O2STANDARD*src.maximum_pressure*filled)*air_contents.volume/(R_IDEAL_GAS_EQUATION*air_contents.temperature) - src.air_contents.nitrogen = (N2STANDARD*src.maximum_pressure*filled)*air_contents.volume/(R_IDEAL_GAS_EQUATION*air_contents.temperature) + air_contents.oxygen = (O2STANDARD * maximum_pressure * filled) * air_contents.volume / (R_IDEAL_GAS_EQUATION * air_contents.temperature) + air_contents.nitrogen = (N2STANDARD * maximum_pressure * filled) * air_contents.volume / (R_IDEAL_GAS_EQUATION * air_contents.temperature) - src.update_icon() + update_icon() return 1 /obj/machinery/portable_atmospherics/canister/custom_mix/New() ..() canister_color["prim"] = "whiters" - src.update_icon() // Otherwise new canisters do not have their icon updated with the pressure light, likely want to add this to the canister class constructor, avoiding at current time to refrain from screwing up code for other canisters. --DZD + update_icon() // Otherwise new canisters do not have their icon updated with the pressure light, likely want to add this to the canister class constructor, avoiding at current time to refrain from screwing up code for other canisters. --DZD return 1 /obj/machinery/portable_atmospherics/canister/welder_act(mob/user, obj/item/I) diff --git a/code/game/machinery/computer/Operating.dm b/code/game/machinery/computer/Operating.dm index 21fddce64b7..1b083bd9c87 100644 --- a/code/game/machinery/computer/Operating.dm +++ b/code/game/machinery/computer/Operating.dm @@ -40,8 +40,7 @@ add_fingerprint(user) if(stat & (BROKEN|NOPOWER)) return - ui_interact(user) - + tgui_interact(user) /obj/machinery/computer/operating/attack_hand(mob/user) if(..(user)) @@ -52,16 +51,15 @@ add_fingerprint(user) - ui_interact(user) + tgui_interact(user) -/obj/machinery/computer/operating/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)//ui is mostly copy pasta from the sleeper ui - ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) +/obj/machinery/computer/operating/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, "op_computer.tmpl", "Patient Monitor", 650, 455) + ui = new(user, src, ui_key, "OperatingComputer", "Patient Monitor", 650, 455, master_ui, state) ui.open() - ui.set_auto_update(1) -/obj/machinery/computer/operating/ui_data(mob/user, ui_key = "main", datum/topic_state/state = GLOB.default_state) +/obj/machinery/computer/operating/tgui_data(mob/user) var/data[0] var/mob/living/carbon/human/occupant if(table) @@ -136,38 +134,43 @@ return data -/obj/machinery/computer/operating/Topic(href, href_list) +/obj/machinery/computer/operating/tgui_act(action, params) if(..()) - return 1 + return + if(stat & (NOPOWER|BROKEN)) + return + if((usr.contents.Find(src) || (in_range(src, usr) && istype(src.loc, /turf))) || (istype(usr, /mob/living/silicon))) usr.set_machine(src) - if(href_list["verboseOn"]) - verbose=1 - if(href_list["verboseOff"]) - verbose=0 - if(href_list["healthOn"]) - healthAnnounce=1 - if(href_list["healthOff"]) - healthAnnounce=0 - if(href_list["critOn"]) - crit=1 - if(href_list["critOff"]) - crit=0 - if(href_list["oxyOn"]) - oxy=1 - if(href_list["oxyOff"]) - oxy=0 - if(href_list["oxy_adj"]!=0) - oxyAlarm=oxyAlarm+text2num(href_list["oxy_adj"]) - if(href_list["choiceOn"]) - choice=1 - if(href_list["choiceOff"]) - choice=0 - if(href_list["health_adj"]!=0) - healthAlarm=healthAlarm+text2num(href_list["health_adj"]) - return - + . = TRUE + switch(action) + if("verboseOn") + verbose = TRUE + if("verboseOff") + verbose = FALSE + if("healthOn") + healthAnnounce = TRUE + if("healthOff") + healthAnnounce = FALSE + if("critOn") + crit = TRUE + if("critOff") + crit = FALSE + if("oxyOn") + oxy = TRUE + if("oxyOff") + oxy = FALSE + if("oxy_adj") + oxyAlarm = clamp(text2num(params["new"]), -100, 100) + if("choiceOn") + choice = TRUE + if("choiceOff") + choice = FALSE + if("health_adj") + healthAlarm = clamp(text2num(params["new"]), -100, 100) + else + return FALSE /obj/machinery/computer/operating/process() @@ -178,6 +181,7 @@ atom_say("New patient detected, loading stats") victim = table.victim atom_say("[victim.real_name], [victim.dna.blood_type] blood, [victim.stat ? "Non-Responsive" : "Awake"]") + SStgui.update_uis(src) if(nextTick < world.time) nextTick=world.time + OP_COMPUTER_COOLDOWN if(crit && victim.health <= -50 ) diff --git a/code/game/machinery/computer/buildandrepair.dm b/code/game/machinery/computer/buildandrepair.dm index a07f3c04348..e91e631cc62 100644 --- a/code/game/machinery/computer/buildandrepair.dm +++ b/code/game/machinery/computer/buildandrepair.dm @@ -233,7 +233,10 @@ /obj/item/circuitboard/brigcells name = "Circuit board (Brig Cell Control)" build_path = /obj/machinery/computer/brigcells - +/obj/item/circuitboard/sm_monitor + name = "Circuit board (Supermatter Monitoring Console)" + build_path = /obj/machinery/computer/sm_monitor + origin_tech = "programming=2;powerstorage=2" // RD console circuits, so that {de,re}constructing one of the special consoles doesn't ruin everything forever /obj/item/circuitboard/rdconsole diff --git a/code/game/machinery/computer/cloning.dm b/code/game/machinery/computer/cloning.dm index 5198a97a88c..04a4f7e0db4 100644 --- a/code/game/machinery/computer/cloning.dm +++ b/code/game/machinery/computer/cloning.dm @@ -1,3 +1,6 @@ +#define MENU_MAIN 1 +#define MENU_RECORDS 2 + /obj/machinery/computer/cloning name = "cloning console" icon = 'icons/obj/computer.dmi' @@ -6,11 +9,11 @@ circuit = /obj/item/circuitboard/cloning req_access = list(ACCESS_HEADS) //Only used for record deletion right now. var/obj/machinery/dna_scannernew/scanner = null //Linked scanner. For scanning. - var/list/pods = list() //Linked cloning pods. - var/temp = "" - var/scantemp = "Scanner ready." - var/menu = 1 //Which menu screen to display - var/list/records = list() + var/list/pods = null //Linked cloning pods. + var/list/temp = null + var/list/scantemp = null + var/menu = MENU_MAIN //Which menu screen to display + var/list/records = null var/datum/dna2/record/active_record = null var/obj/item/disk/data/diskette = null //Mostly so the geneticist can steal everything. var/loading = 0 // Nice loading text @@ -24,6 +27,9 @@ /obj/machinery/computer/cloning/Initialize() ..() + pods = list() + records = list() + set_scan_temp("Scanner ready.", "good") updatemodules() /obj/machinery/computer/cloning/Destroy() @@ -91,7 +97,7 @@ W.loc = src src.diskette = W to_chat(user, "You insert [W].") - SSnanoui.update_uis(src) + SStgui.update_uis(src) return else if(istype(W, /obj/item/multitool)) var/obj/item/multitool/M = W @@ -117,19 +123,21 @@ return updatemodules() - ui_interact(user) + tgui_interact(user) -/obj/machinery/computer/cloning/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) +/obj/machinery/computer/cloning/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(stat & (NOPOWER|BROKEN)) return - // Set up the Nano UI - ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) + var/datum/asset/cloning/assets = get_asset_datum(/datum/asset/cloning) + assets.send(user) + + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "cloning_console.tmpl", "Cloning Console UI", 640, 520) + ui = new(user, src, ui_key, "CloningConsole", "Cloning Console", 640, 520) ui.open() -/obj/machinery/computer/cloning/ui_data(mob/user, ui_key = "main", datum/topic_state/state = GLOB.default_state) +/obj/machinery/computer/cloning/tgui_data(mob/user) var/data[0] data["menu"] = src.menu data["scanner"] = sanitize("[src.scanner]") @@ -143,7 +151,18 @@ if(pod.efficiency > 5) canpodautoprocess = 1 - tempods.Add(list(list("pod" = "\ref[pod]", "name" = sanitize(capitalize(pod.name)), "biomass" = pod.biomass))) + var/status = "idle" + if(pod.mess) + status = "mess" + else if(pod.occupant && !(pod.stat & NOPOWER)) + status = "cloning" + tempods.Add(list(list( + "pod" = "\ref[pod]", + "name" = sanitize(capitalize(pod.name)), + "biomass" = pod.biomass, + "status" = status, + "progress" = (pod.occupant && pod.occupant.stat != DEAD) ? pod.get_completion() : 0 + ))) data["pods"] = tempods data["loading"] = loading @@ -168,188 +187,190 @@ temprecords.Add(list(list("record" = "\ref[R]", "realname" = sanitize(tempRealName)))) data["records"] = temprecords - if(src.menu == 3) - if(src.active_record) - data["activerecord"] = "\ref[src.active_record]" - var/obj/item/implant/health/H = null - if(src.active_record.implant) - H = locate(src.active_record.implant) + if(selected_pod && (selected_pod in pods) && selected_pod.biomass >= CLONE_BIOMASS) + data["podready"] = 1 + else + data["podready"] = 0 - if((H) && (istype(H))) - data["health"] = H.sensehealth() - data["realname"] = sanitize(src.active_record.dna.real_name) - data["unidentity"] = src.active_record.dna.uni_identity - data["strucenzymes"] = src.active_record.dna.struc_enzymes - if(selected_pod && (selected_pod in pods) && selected_pod.biomass >= CLONE_BIOMASS) - data["podready"] = 1 - else - data["podready"] = 0 + data["modal"] = tgui_modal_data(src) return data -/obj/machinery/computer/cloning/Topic(href, href_list) +/obj/machinery/computer/cloning/tgui_act(action, params) if(..()) - return 1 - - if(loading) + return + if(stat & (NOPOWER|BROKEN)) return - if(href_list["scan"] && scanner && scanner.occupant) - scantemp = "Scanner ready." - - loading = 1 - - spawn(20) - if(can_brainscan() && scan_mode) - scan_mob(scanner.occupant, scan_brain = 1) - else - scan_mob(scanner.occupant) - - loading = 0 - SSnanoui.update_uis(src) - - if(href_list["task"]) - switch(href_list["task"]) - if("autoprocess") - autoprocess = 1 - SSnanoui.update_uis(src) - if("stopautoprocess") - autoprocess = 0 - SSnanoui.update_uis(src) - - //No locking an open scanner. - else if((href_list["lock"]) && (!isnull(src.scanner))) - if((!src.scanner.locked) && (src.scanner.occupant)) - src.scanner.locked = 1 - else - src.scanner.locked = 0 - - else if(href_list["view_rec"]) - src.active_record = locate(href_list["view_rec"]) - if(istype(src.active_record,/datum/dna2/record)) - if((isnull(src.active_record.ckey))) - qdel(src.active_record) - src.temp = "Error: Record corrupt." - else - src.menu = 3 - else - src.active_record = null - src.temp = "Error: Record missing." - - else if(href_list["del_rec"]) - if((!src.active_record) || (src.menu < 3)) - return - if(src.menu == 3) //If we are viewing a record, confirm deletion - src.temp = "Please confirm that you want to delete the record?" - src.menu = 4 - - else if(src.menu == 4) - var/obj/item/card/id/C = usr.get_active_hand() - if(istype(C)||istype(C, /obj/item/pda)) - if(src.check_access(C)) - src.records.Remove(src.active_record) - qdel(src.active_record) - src.temp = "Record deleted." - src.menu = 2 + . = TRUE + switch(tgui_modal_act(src, action, params)) + if(TGUI_MODAL_ANSWER) + if(params["id"] == "del_rec" && active_record) + var/obj/item/card/id/C = usr.get_active_hand() + if(!istype(C) && !istype(C, /obj/item/pda)) + set_temp("ID not in hand.", "danger") + return + if(check_access(C)) + records.Remove(active_record) + qdel(active_record) + set_temp("Record deleted.", "success") + menu = MENU_RECORDS else - src.temp = "Error: Access denied." - - else if(href_list["disk"]) //Load or eject. - switch(href_list["disk"]) - if("load") - if((isnull(src.diskette)) || isnull(src.diskette.buf)) - src.temp = "Error: The disk's data could not be read." - SSnanoui.update_uis(src) - return - if(isnull(src.active_record)) - src.temp = "Error: No active record was found." - src.menu = 1 - SSnanoui.update_uis(src) - return - - src.active_record = src.diskette.buf.copy() - - src.temp = "Load successful." - - if("eject") - if(!isnull(src.diskette)) - src.diskette.loc = src.loc - src.diskette = null - - else if(href_list["save_disk"]) //Save to disk! - if((isnull(src.diskette)) || (src.diskette.read_only) || (isnull(src.active_record))) - src.temp = "Error: The data could not be saved." - SSnanoui.update_uis(src) + set_temp("Access denied.", "danger") return - // DNA2 makes things a little simpler. - src.diskette.buf=src.active_record.copy() - src.diskette.buf.types=0 - switch(href_list["save_disk"]) //Save as Ui/Ui+Ue/Se - if("ui") - src.diskette.buf.types=DNA2_BUF_UI - if("ue") - src.diskette.buf.types=DNA2_BUF_UI|DNA2_BUF_UE - if("se") - src.diskette.buf.types=DNA2_BUF_SE - src.diskette.name = "data disk - '[src.active_record.dna.real_name]'" - src.temp = "Save \[[href_list["save_disk"]]\] successful." + switch(action) + if("scan") + if(!scanner || !scanner.occupant || loading) + return + set_scan_temp("Scanner ready.", "good") + loading = TRUE - else if(href_list["refresh"]) - SSnanoui.update_uis(src) - - else if(href_list["selectpod"]) - var/obj/machinery/clonepod/selected = locate(href_list["selectpod"]) - if(istype(selected) && (selected in pods)) - selected_pod = selected - - else if(href_list["clone"]) - var/datum/dna2/record/C = locate(href_list["clone"]) - //Look for that player! They better be dead! - if(istype(C)) - //Can't clone without someone to clone. Or a pod. Or if the pod is busy. Or full of gibs. - if(!pods.len) - temp = "Error: No cloning pod detected." - else - var/obj/machinery/clonepod/pod = selected_pod - var/cloneresult - if(!selected_pod) - temp = "Error: No cloning pod selected." - else if(pod.occupant) - temp = "Error: The cloning pod is currently occupied." - else if(pod.biomass < CLONE_BIOMASS) - temp = "Error: Not enough biomass." - else if(pod.mess) - temp = "Error: The cloning pod is malfunctioning." - else if(!config.revival_cloning) - temp = "Error: Unable to initiate cloning cycle." + spawn(20) + if(can_brainscan() && scan_mode) + scan_mob(scanner.occupant, scan_brain = TRUE) else - cloneresult = pod.growclone(C) - if(cloneresult) - if(cloneresult > 0) - temp = "Initiating cloning cycle..." - records.Remove(C) - qdel(C) - menu = 1 + scan_mob(scanner.occupant) + loading = FALSE + SStgui.update_uis(src) + if("autoprocess") + autoprocess = text2num(params["on"]) > 0 + if("lock") + if(isnull(scanner) || !scanner.occupant) //No locking an open scanner. + return + scanner.locked = !scanner.locked + if("view_rec") + var/ref = params["ref"] + if(!length(ref)) + return + active_record = locate(ref) + if(istype(active_record)) + if(isnull(active_record.ckey)) + qdel(active_record) + set_temp("Error: Record corrupt.", "danger") + else + var/obj/item/implant/health/H = null + if(active_record.implant) + H = locate(active_record.implant) + var/list/payload = list( + activerecord = "\ref[active_record]", + health = (H && istype(H)) ? H.sensehealth() : "", + realname = sanitize(active_record.dna.real_name), + unidentity = active_record.dna.uni_identity, + strucenzymes = active_record.dna.struc_enzymes, + ) + tgui_modal_message(src, action, "", null, payload) + else + active_record = null + set_temp("Error: Record missing.", "danger") + if("del_rec") + if(!active_record) + return + tgui_modal_boolean(src, action, "Please confirm that you want to delete the record by holding your ID and pressing Delete:", yes_text = "Delete", no_text = "Cancel") + if("disk") // Disk management. + if(!length(params["option"])) + return + switch(params["option"]) + if("load") + if(isnull(diskette) || isnull(diskette.buf)) + set_temp("Error: The disk's data could not be read.", "danger") + return + else if(isnull(active_record)) + set_temp("Error: No active record was found.", "danger") + menu = MENU_MAIN + return + + active_record = diskette.buf.copy() + set_temp("Successfully loaded from disk.", "success") + if("save") + if(isnull(diskette) || diskette.read_only || isnull(active_record)) + set_temp("Error: The data could not be saved.", "danger") + return + + // DNA2 makes things a little simpler. + var/types + switch(params["savetype"]) // Save as Ui/Ui+Ue/Se + if("ui") + types = DNA2_BUF_UI + if("ue") + types = DNA2_BUF_UI|DNA2_BUF_UE + if("se") + types = DNA2_BUF_SE + else + set_temp("Error: Invalid save format.", "danger") + return + diskette.buf = active_record.copy() + diskette.buf.types = types + diskette.name = "data disk - '[active_record.dna.real_name]'" + set_temp("Successfully saved to disk.", "success") + if("eject") + if(!isnull(diskette)) + diskette.loc = loc + diskette = null + if("refresh") + SStgui.update_uis(src) + if("selectpod") + var/ref = params["ref"] + if(!length(ref)) + return + var/obj/machinery/clonepod/selected = locate(ref) + if(istype(selected) && (selected in pods)) + selected_pod = selected + if("clone") + var/ref = params["ref"] + if(!length(ref)) + return + var/datum/dna2/record/C = locate(ref) + //Look for that player! They better be dead! + if(istype(C)) + tgui_modal_clear(src) + //Can't clone without someone to clone. Or a pod. Or if the pod is busy. Or full of gibs. + if(!length(pods)) + set_temp("Error: No cloning pod detected.", "danger") + else + var/obj/machinery/clonepod/pod = selected_pod + var/cloneresult + if(!selected_pod) + set_temp("Error: No cloning pod selected.", "danger") + else if(pod.occupant) + set_temp("Error: The cloning pod is currently occupied.", "danger") + else if(pod.biomass < CLONE_BIOMASS) + set_temp("Error: Not enough biomass.", "danger") + else if(pod.mess) + set_temp("Error: The cloning pod is malfunctioning.", "danger") + else if(!config.revival_cloning) + set_temp("Error: Unable to initiate cloning cycle.", "danger") else - temp = "[C.name] => Initialisation failure." - + cloneresult = pod.growclone(C) + if(cloneresult) + set_temp("Initiating cloning cycle...", "success") + records.Remove(C) + qdel(C) + menu = MENU_MAIN + else + set_temp("Error: Initialisation failure.", "danger") + else + set_temp("Error: Data corruption.", "danger") + if("menu") + menu = clamp(text2num(params["num"]), MENU_MAIN, MENU_RECORDS) + if("toggle_mode") + if(loading) + return + if(can_brainscan()) + scan_mode = !scan_mode + else + scan_mode = FALSE + if("eject") + if(usr.incapacitated() || !scanner || loading) + return + scanner.eject_occupant(usr) + scanner.add_fingerprint(usr) + if("cleartemp") + temp = null else - temp = "Error: Data corruption." - - else if(href_list["menu"]) - src.menu = text2num(href_list["menu"]) - temp = "" - scantemp = "Scanner ready." - else if(href_list["toggle_mode"]) - if(can_brainscan()) - scan_mode = !scan_mode - else - scan_mode = 0 + return FALSE src.add_fingerprint(usr) - SSnanoui.update_uis(src) - return /obj/machinery/computer/cloning/proc/scan_mob(mob/living/carbon/human/subject as mob, var/scan_brain = 0) if(stat & NOPOWER) @@ -360,46 +381,46 @@ return if(isnull(subject) || (!(ishuman(subject))) || (!subject.dna)) if(isalien(subject)) - scantemp = "Error: Xenomorphs are not scannable." - SSnanoui.update_uis(src) + set_scan_temp("Xenomorphs are not scannable.", "bad") + SStgui.update_uis(src) return // can add more conditions for specific non-human messages here else - scantemp = "Error: Subject species is not scannable." - SSnanoui.update_uis(src) + set_scan_temp("Subject species is not scannable.", "bad") + SStgui.update_uis(src) return if(subject.get_int_organ(/obj/item/organ/internal/brain)) var/obj/item/organ/internal/brain/Brn = subject.get_int_organ(/obj/item/organ/internal/brain) if(istype(Brn)) if(NO_SCAN in Brn.dna.species.species_traits) - scantemp = "Error: [Brn.dna.species.name_plural] are not scannable." - SSnanoui.update_uis(src) + set_scan_temp("[Brn.dna.species.name_plural] are not scannable.", "bad") + SStgui.update_uis(src) return if(!subject.get_int_organ(/obj/item/organ/internal/brain)) - scantemp = "Error: No brain detected in subject." - SSnanoui.update_uis(src) + set_scan_temp("No brain detected in subject.", "bad") + SStgui.update_uis(src) return if(subject.suiciding) - scantemp = "Error: Subject has committed suicide and is not scannable." - SSnanoui.update_uis(src) + set_scan_temp("Subject has committed suicide and is not scannable.", "bad") + SStgui.update_uis(src) return if((!subject.ckey) || (!subject.client)) - scantemp = "Error: Subject's brain is not responding. Further attempts after a short delay may succeed." - SSnanoui.update_uis(src) + set_scan_temp("Subject's brain is not responding. Further attempts after a short delay may succeed.", "bad") + SStgui.update_uis(src) return if((NOCLONE in subject.mutations) && src.scanner.scan_level < 2) - scantemp = "Error: Subject has incompatible genetic mutations." - SSnanoui.update_uis(src) + set_scan_temp("Subject has incompatible genetic mutations.", "bad") + SStgui.update_uis(src) return if(!isnull(find_record(subject.ckey))) - scantemp = "Subject already in database." - SSnanoui.update_uis(src) + set_scan_temp("Subject already in database.") + SStgui.update_uis(src) return for(var/obj/machinery/clonepod/pod in pods) if(pod.occupant && pod.clonemind == subject.mind) - scantemp = "Subject already getting cloned." - SSnanoui.update_uis(src) + set_scan_temp("Subject already getting cloned.") + SStgui.update_uis(src) return subject.dna.check_integrity() @@ -434,8 +455,8 @@ R.mind = "\ref[subject.mind]" src.records += R - scantemp = "Subject successfully scanned. " + extra_info - SSnanoui.update_uis(src) + set_scan_temp("Subject successfully scanned. [extra_info]", "good") + SStgui.update_uis(src) //Find a specific record by key. /obj/machinery/computer/cloning/proc/find_record(var/find_key) @@ -451,3 +472,30 @@ /obj/machinery/computer/cloning/proc/can_brainscan() return (scanner && scanner.scan_level > 3) + +/** + * Sets a temporary message to display to the user + * + * Arguments: + * * text - Text to display, null/empty to clear the message from the UI + * * style - The style of the message: (color name), info, success, warning, danger + */ +/obj/machinery/computer/cloning/proc/set_temp(text = "", style = "info", update_now = FALSE) + temp = list(text = text, style = style) + if(update_now) + SStgui.update_uis(src) + +/** + * Sets a temporary scan message to display to the user + * + * Arguments: + * * text - Text to display, null/empty to clear the message from the UI + * * color - The color of the message: (color name) + */ +/obj/machinery/computer/cloning/proc/set_scan_temp(text = "", color = "", update_now = FALSE) + scantemp = list(text = text, color = color) + if(update_now) + SStgui.update_uis(src) + +#undef MENU_MAIN +#undef MENU_RECORDS diff --git a/code/game/machinery/computer/medical.dm b/code/game/machinery/computer/medical.dm index 3674358f07f..c733a7c92c1 100644 --- a/code/game/machinery/computer/medical.dm +++ b/code/game/machinery/computer/medical.dm @@ -1,10 +1,12 @@ -#define MED_DATA_MAIN 1 // Main menu #define MED_DATA_R_LIST 2 // Record list #define MED_DATA_MAINT 3 // Records maintenance #define MED_DATA_RECORD 4 // Record #define MED_DATA_V_DATA 5 // Virus database #define MED_DATA_MEDBOT 6 // Medbot monitor +#define FIELD(N, V, E) list(field = N, value = V, edit = E) +#define MED_FIELD(N, V, E, LB) list(field = N, value = V, edit = E, line_break = LB) + /obj/machinery/computer/med_data //TODO:SANITY name = "medical records console" desc = "This can be used to check medical records." @@ -18,11 +20,45 @@ var/screen = null var/datum/data/record/active1 = null var/datum/data/record/active2 = null - var/temp = null + var/list/temp = null var/printing = null + // The below are used to make modal generation more convenient + var/static/list/field_edit_questions + var/static/list/field_edit_choices light_color = LIGHT_COLOR_DARKBLUE +/obj/machinery/computer/med_data/Initialize() + ..() + field_edit_questions = list( + // General + "sex" = "Please select new sex:", + "age" = "Please input new age:", + "fingerprint" = "Please input new fingerprint hash:", + "p_stat" = "Please select new physical status:", + "m_stat" = "Please select new mental status:", + // Medical + "blood_type" = "Please select new blood type:", + "b_dna" = "Please input new DNA:", + "mi_dis" = "Please input new minor disabilities:", + "mi_dis_d" = "Please summarize minor disabilities:", + "ma_dis" = "Please input new major disabilities:", + "ma_dis_d" = "Please summarize major disabilities:", + "alg" = "Please input new allergies:", + "alg_d" = "Please summarize allergies:", + "cdi" = "Please input new current diseases:", + "cdi_d" = "Please summarize current diseases:", + "notes" = "Please input new important notes:", + ) + field_edit_choices = list( + // General + "sex" = list("Male", "Female"), + "p_stat" = list("*Deceased*", "*SSD*", "Active", "Physically Unfit", "Disabled"), + "m_stat" = list("*Insane*", "*Unstable*", "*Watch*", "Stable"), + // Medical + "blood_type" = list("A+", "A-", "B+", "B-", "AB+", "AB-", "O+", "O-"), + ) + /obj/machinery/computer/med_data/Destroy() active1 = null active2 = null @@ -33,7 +69,7 @@ usr.drop_item() O.forceMove(src) scan = O - ui_interact(user) + tgui_interact(user) return return ..() @@ -44,20 +80,25 @@ to_chat(user, "Unable to establish a connection: You're too far away from the station!") return add_fingerprint(user) - ui_interact(user) + tgui_interact(user) -/obj/machinery/computer/med_data/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) - ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) +/obj/machinery/computer/med_data/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, "med_data.tmpl", name, 800, 380) + ui = new(user, src, ui_key, "MedicalRecords", "Medical Records", 800, 380, master_ui, state) ui.open() + ui.set_autoupdate(FALSE) -/obj/machinery/computer/med_data/ui_data(mob/user, ui_key = "main", datum/topic_state/state = GLOB.default_state) +/obj/machinery/computer/med_data/tgui_data(mob/user) var/data[0] data["temp"] = temp data["scan"] = scan ? scan.name : null data["authenticated"] = authenticated + data["rank"] = rank data["screen"] = screen + data["printing"] = printing + data["isAI"] = isAI(user) + data["isRobot"] = isrobot(user) if(authenticated) switch(screen) if(MED_DATA_R_LIST) @@ -72,17 +113,17 @@ if(istype(active1, /datum/data/record) && GLOB.data_core.general.Find(active1)) var/list/fields = list() general["fields"] = fields - fields[++fields.len] = list("field" = "Name:", "value" = active1.fields["name"], "edit" = null) - fields[++fields.len] = list("field" = "ID:", "value" = active1.fields["id"], "edit" = null) - fields[++fields.len] = list("field" = "Sex:", "value" = active1.fields["sex"], "edit" = "sex") - fields[++fields.len] = list("field" = "Age:", "value" = active1.fields["age"], "edit" = "age") - fields[++fields.len] = list("field" = "Fingerprint:", "value" = active1.fields["fingerprint"], "edit" = "fingerprint") - fields[++fields.len] = list("field" = "Physical Status:", "value" = active1.fields["p_stat"], "edit" = "p_stat") - fields[++fields.len] = list("field" = "Mental Status:", "value" = active1.fields["m_stat"], "edit" = "m_stat") + fields[++fields.len] = FIELD("Name", active1.fields["name"], null) + fields[++fields.len] = FIELD("ID", active1.fields["id"], null) + fields[++fields.len] = FIELD("Sex", active1.fields["sex"], "sex") + fields[++fields.len] = FIELD("Age", active1.fields["age"], "age") + fields[++fields.len] = FIELD("Fingerprint", active1.fields["fingerprint"], "fingerprint") + fields[++fields.len] = FIELD("Physical Status", active1.fields["p_stat"], "p_stat") + fields[++fields.len] = FIELD("Mental Status", active1.fields["m_stat"], "m_stat") var/list/photos = list() general["photos"] = photos - photos[++photos.len] = list("photo" = active1.fields["photo-south"]) - photos[++photos.len] = list("photo" = active1.fields["photo-west"]) + photos[++photos.len] = active1.fields["photo-south"] + photos[++photos.len] = active1.fields["photo-west"] general["has_photos"] = (active1.fields["photo-south"] || active1.fields["photo-west"] ? 1 : 0) general["empty"] = 0 else @@ -93,17 +134,17 @@ if(istype(active2, /datum/data/record) && GLOB.data_core.medical.Find(active2)) var/list/fields = list() medical["fields"] = fields - fields[++fields.len] = list("field" = "Blood Type:", "value" = active2.fields["blood_type"], "edit" = "blood_type", "line_break" = 0) - fields[++fields.len] = list("field" = "DNA:", "value" = active2.fields["b_dna"], "edit" = "b_dna", "line_break" = 1) - fields[++fields.len] = list("field" = "Minor Disabilities:", "value" = active2.fields["mi_dis"], "edit" = "mi_dis", "line_break" = 0) - fields[++fields.len] = list("field" = "Details:", "value" = active2.fields["mi_dis_d"], "edit" = "mi_dis_d", "line_break" = 1) - fields[++fields.len] = list("field" = "Major Disabilities:", "value" = active2.fields["ma_dis"], "edit" = "ma_dis", "line_break" = 0) - fields[++fields.len] = list("field" = "Details:", "value" = active2.fields["ma_dis_d"], "edit" = "ma_dis_d", "line_break" = 1) - fields[++fields.len] = list("field" = "Allergies:", "value" = active2.fields["alg"], "edit" = "alg", "line_break" = 0) - fields[++fields.len] = list("field" = "Details:", "value" = active2.fields["alg_d"], "edit" = "alg_d", "line_break" = 1) - fields[++fields.len] = list("field" = "Current Diseases:", "value" = active2.fields["cdi"], "edit" = "cdi", "line_break" = 0) - fields[++fields.len] = list("field" = "Details:", "value" = active2.fields["cdi_d"], "edit" = "cdi_d", "line_break" = 1) - fields[++fields.len] = list("field" = "Important Notes:", "value" = active2.fields["notes"], "edit" = "notes", "line_break" = 0) + fields[++fields.len] = MED_FIELD("Blood Type", active2.fields["blood_type"], "blood_type", FALSE) + fields[++fields.len] = MED_FIELD("DNA", active2.fields["b_dna"], "b_dna", TRUE) + fields[++fields.len] = MED_FIELD("Minor Disabilities", active2.fields["mi_dis"], "mi_dis", FALSE) + fields[++fields.len] = MED_FIELD("Details", active2.fields["mi_dis_d"], "mi_dis_d", TRUE) + fields[++fields.len] = MED_FIELD("Major Disabilities", active2.fields["ma_dis"], "ma_dis", FALSE) + fields[++fields.len] = MED_FIELD("Details", active2.fields["ma_dis_d"], "ma_dis_d", TRUE) + fields[++fields.len] = MED_FIELD("Allergies", active2.fields["alg"], "alg", FALSE) + fields[++fields.len] = MED_FIELD("Details", active2.fields["alg_d"], "alg_d", TRUE) + fields[++fields.len] = MED_FIELD("Current Diseases", active2.fields["cdi"], "cdi", FALSE) + fields[++fields.len] = MED_FIELD("Details", active2.fields["cdi_d"], "cdi_d", TRUE) + fields[++fields.len] = MED_FIELD("Important Notes", active2.fields["notes"], "notes", TRUE) if(!active2.fields["comments"] || !islist(active2.fields["comments"])) active2.fields["comments"] = list() medical["comments"] = active2.fields["comments"] @@ -127,7 +168,9 @@ var/turf/T = get_turf(M) if(T) var/medbot = list() + var/area/A = get_area(T) medbot["name"] = M.name + medbot["area"] = A.name medbot["x"] = T.x medbot["y"] = T.y medbot["on"] = M.on @@ -138,395 +181,290 @@ else medbot["use_beaker"] = 0 data["medbots"] += list(medbot) + + data["modal"] = tgui_modal_data(src) return data -/obj/machinery/computer/med_data/Topic(href, href_list) +/obj/machinery/computer/med_data/tgui_act(action, params) if(..()) - return 1 + return + if(stat & (NOPOWER|BROKEN)) + return if(!GLOB.data_core.general.Find(active1)) active1 = null if(!GLOB.data_core.medical.Find(active2)) active2 = null - if(href_list["temp"]) - temp = null + . = TRUE + if(tgui_act_modal(action, params)) + return - if(href_list["temp_action"]) - if(href_list["temp_action"]) - var/temp_href = splittext(href_list["temp_action"], "=") - switch(temp_href[1]) - if("del_all2") - for(var/datum/data/record/R in GLOB.data_core.medical) - qdel(R) - setTemp("

All records deleted.

") - if("p_stat") - if(active1) - switch(temp_href[2]) - if("deceased") - active1.fields["p_stat"] = "*Deceased*" - if("ssd") - active1.fields["p_stat"] = "*SSD*" - if("active") - active1.fields["p_stat"] = "Active" - if("unfit") - active1.fields["p_stat"] = "Physically Unfit" - if("disabled") - active1.fields["p_stat"] = "Disabled" - if("m_stat") - if(active1) - switch(temp_href[2]) - if("insane") - active1.fields["m_stat"] = "*Insane*" - if("unstable") - active1.fields["m_stat"] = "*Unstable*" - if("watch") - active1.fields["m_stat"] = "*Watch*" - if("stable") - active1.fields["m_stat"] = "Stable" - if("blood_type") - if(active2) - switch(temp_href[2]) - if("an") - active2.fields["blood_type"] = "A-" - if("bn") - active2.fields["blood_type"] = "B-" - if("abn") - active2.fields["blood_type"] = "AB-" - if("on") - active2.fields["blood_type"] = "O-" - if("ap") - active2.fields["blood_type"] = "A+" - if("bp") - active2.fields["blood_type"] = "B+" - if("abp") - active2.fields["blood_type"] = "AB+" - if("op") - active2.fields["blood_type"] = "O+" - if("del_r2") - QDEL_NULL(active2) - - if(href_list["scan"]) - if(scan) - scan.forceMove(loc) - if(ishuman(usr) && !usr.get_active_hand()) - usr.put_in_hands(scan) - scan = null + switch(action) + if("cleartemp") + temp = null + if("scan") + if(scan) + scan.forceMove(loc) + if(ishuman(usr) && !usr.get_active_hand()) + usr.put_in_hands(scan) + scan = null + else + var/obj/item/I = usr.get_active_hand() + if(istype(I, /obj/item/card/id)) + usr.drop_item() + I.forceMove(src) + scan = I + if("login") + var/login_type = text2num(params["login_type"]) + if(login_type == LOGIN_TYPE_NORMAL && istype(scan)) + if(check_access(scan)) + authenticated = scan.registered_name + rank = scan.assignment + else if(login_type == LOGIN_TYPE_AI && isAI(usr)) + authenticated = usr.name + rank = "AI" + else if(login_type == LOGIN_TYPE_ROBOT && isrobot(usr)) + authenticated = usr.name + var/mob/living/silicon/robot/R = usr + rank = "[R.modtype] [R.braintype]" + if(authenticated) + active1 = null + active2 = null + screen = MED_DATA_R_LIST else - var/obj/item/I = usr.get_active_hand() - if(istype(I, /obj/item/card/id)) - usr.drop_item() - I.forceMove(src) - scan = I + . = FALSE - if(href_list["login"]) - if(isAI(usr)) - authenticated = usr.name - rank = "AI" - else if(isrobot(usr)) - authenticated = usr.name - var/mob/living/silicon/robot/R = usr - rank = "[R.modtype] [R.braintype]" - else if(istype(scan, /obj/item/card/id)) - if(check_access(scan)) - authenticated = scan.registered_name - rank = scan.assignment - - if(authenticated) - active1 = null - active2 = null - screen = MED_DATA_MAIN + if(.) + return if(authenticated) - if(href_list["logout"]) - authenticated = null - screen = null - active1 = null - active2 = null + . = TRUE + switch(action) + if("logout") + if(scan) + scan.forceMove(loc) + if(ishuman(usr) && !usr.get_active_hand()) + usr.put_in_hands(scan) + scan = null + authenticated = null + screen = null + active1 = null + active2 = null + if("screen") + screen = clamp(text2num(params["screen"]) || 0, MED_DATA_R_LIST, MED_DATA_MEDBOT) + active1 = null + active2 = null + if("vir") + var/type = text2path(params["vir"] || "") + if(!ispath(type, /datum/disease)) + return - if(href_list["screen"]) - screen = text2num(href_list["screen"]) - if(screen < 1) - screen = MED_DATA_MAIN + var/datum/disease/D = new type(0) + var/list/payload = list( + name = D.name, + max_stages = D.max_stages, + spread_text = D.spread_text, + cure = D.cure_text || "None", + desc = D.desc, + severity = D.severity + ); + tgui_modal_message(src, "virus", "", null, payload) + qdel(D) + if("del_all") + for(var/datum/data/record/R in GLOB.data_core.medical) + qdel(R) + set_temp("All medical records deleted.") + if("del_r") + if(active2) + set_temp("Medical record deleted.") + qdel(active2) + if("d_rec") + var/datum/data/record/general_record = locate(params["d_rec"] || "") + if(!GLOB.data_core.general.Find(general_record)) + set_temp("Record not found.", "danger") + return - active1 = null - active2 = null + var/datum/data/record/medical_record + for(var/datum/data/record/M in GLOB.data_core.medical) + if(M.fields["name"] == general_record.fields["name"] && M.fields["id"] == general_record.fields["id"]) + medical_record = M + break - if(href_list["vir"]) - var/type = href_list["vir"] - var/datum/disease/D = new type(0) - var/afs = "" - for(var/mob/M in D.viable_mobtypes) - afs += "[initial(M.name)];" - var/severity = D.severity - switch(severity) - if("Harmful", "Minor") - severity = "[severity]" - if("Medium") - severity = "[severity]" - if("Dangerous!") - severity = "[severity]" - if("BIOHAZARD THREAT!") - severity = "

[severity]

" - setTemp({"Name: [D.name] -
Number of stages: [D.max_stages] -
Spread: [D.spread_text] Transmission -
Possible Cure: [(D.cure_text||"none")] -
Affected Lifeforms:[afs]
-
Notes: [D.desc]
-
Severity: [severity]"}) - qdel(D) - - if(href_list["del_all"]) - var/list/buttons = list() - buttons[++buttons.len] = list("name" = "Yes", "icon" = "check", "href" = "del_all2=1") - buttons[++buttons.len] = list("name" = "No", "icon" = "times", "href" = null) - setTemp("

Are you sure you wish to delete all records?

", buttons) - - if(href_list["field"]) - if(..()) - return 1 - var/a1 = active1 - var/a2 = active2 - switch(href_list["field"]) - if("fingerprint") - if(istype(active1, /datum/data/record)) - var/t1 = copytext(trim(sanitize(input("Please input fingerprint hash:", "Med. records", active1.fields["fingerprint"], null) as text)), 1, MAX_MESSAGE_LEN) - if(!t1 || ..() || active1 != a1) - return 1 - active1.fields["fingerprint"] = t1 - if("sex") - if(istype(active1, /datum/data/record)) - if(active1.fields["sex"] == "Male") - active1.fields["sex"] = "Female" - else - active1.fields["sex"] = "Male" - if("age") - if(istype(active1, /datum/data/record)) - var/t1 = input("Please input age:", "Med. records", active1.fields["age"], null) as num - if(!t1 || ..() || active1 != a1) - return 1 - active1.fields["age"] = t1 - if("mi_dis") - if(istype(active2, /datum/data/record)) - var/t1 = copytext(trim(sanitize(input("Please input minor disabilities list:", "Med. records", active2.fields["mi_dis"], null) as text)), 1, MAX_MESSAGE_LEN) - if(!t1 || ..() || active2 != a2) - return 1 - active2.fields["mi_dis"] = t1 - if("mi_dis_d") - if(istype(active2, /datum/data/record)) - var/t1 = copytext(trim(sanitize(input("Please summarize minor dis.:", "Med. records", active2.fields["mi_dis_d"], null) as message)), 1, MAX_MESSAGE_LEN) - if(!t1 || ..() || active2 != a2) - return 1 - active2.fields["mi_dis_d"] = t1 - if("ma_dis") - if(istype(active2, /datum/data/record)) - var/t1 = copytext(trim(sanitize(input("Please input major diabilities list:", "Med. records", active2.fields["ma_dis"], null) as text)), 1, MAX_MESSAGE_LEN) - if(!t1 || ..() || active2 != a2) - return 1 - active2.fields["ma_dis"] = t1 - if("ma_dis_d") - if(istype(active2, /datum/data/record)) - var/t1 = copytext(trim(sanitize(input("Please summarize major dis.:", "Med. records", active2.fields["ma_dis_d"], null) as message)), 1, MAX_MESSAGE_LEN) - if(!t1 || ..() || active2 != a2) - return 1 - active2.fields["ma_dis_d"] = t1 - if("alg") - if(istype(active2, /datum/data/record)) - var/t1 = copytext(trim(sanitize(input("Please state allergies:", "Med. records", active2.fields["alg"], null) as text)), 1, MAX_MESSAGE_LEN) - if(!t1 || ..() || active2 != a2) - return 1 - active2.fields["alg"] = t1 - if("alg_d") - if(istype(active2, /datum/data/record)) - var/t1 = copytext(trim(sanitize(input("Please summarize allergies:", "Med. records", active2.fields["alg_d"], null) as message)), 1, MAX_MESSAGE_LEN) - if(!t1 || ..() || active2 != a2) - return 1 - active2.fields["alg_d"] = t1 - if("cdi") - if(istype(active2, /datum/data/record)) - var/t1 = copytext(trim(sanitize(input("Please state diseases:", "Med. records", active2.fields["cdi"], null) as text)), 1, MAX_MESSAGE_LEN) - if(!t1 || ..() || active2 != a2) - return 1 - active2.fields["cdi"] = t1 - if("cdi_d") - if(istype(active2, /datum/data/record)) - var/t1 = copytext(trim(sanitize(input("Please summarize diseases:", "Med. records", active2.fields["cdi_d"], null) as message)), 1, MAX_MESSAGE_LEN) - if(!t1 || ..() || active2 != a2) - return 1 - active2.fields["cdi_d"] = t1 - if("notes") - if(istype(active2, /datum/data/record)) - var/t1 = copytext(html_encode(trim(input("Please summarize notes:", "Med. records", html_decode(active2.fields["notes"]), null) as message)), 1, MAX_MESSAGE_LEN) - if(!t1 || ..() || active2 != a2) - return 1 - active2.fields["notes"] = t1 - if("p_stat") - if(istype(active1, /datum/data/record)) - var/list/buttons = list() - buttons[++buttons.len] = list("name" = "*Deceased*", "icon" = "stethoscope", "href" = "p_stat=deceased", "status" = (active1.fields["p_stat"] == "*Deceased*" ? "selected" : null)) - buttons[++buttons.len] = list("name" = "*SSD*", "icon" = "stethoscope", "href" = "p_stat=ssd", "status" = (active1.fields["p_stat"] == "*SSD*" ? "selected" : null)) - buttons[++buttons.len] = list("name" = "Active", "icon" = "stethoscope", "href" = "p_stat=active", "status" = (active1.fields["p_stat"] == "Active" ? "selected" : null)) - buttons[++buttons.len] = list("name" = "Physically Unfit", "icon" = "stethoscope", "href" = "p_stat=unfit", "status" = (active1.fields["p_stat"] == "Physically Unfit" ? "selected" : null)) - buttons[++buttons.len] = list("name" = "Disabled", "icon" = "stethoscope", "href" = "p_stat=disabled", "status" = (active1.fields["p_stat"] == "Disabled" ? "selected" : null)) - setTemp("

Physical Condition

", buttons) - if("m_stat") - if(istype(active1, /datum/data/record)) - var/list/buttons = list() - buttons[++buttons.len] = list("name" = "*Insane*", "icon" = "stethoscope", "href" = "m_stat=insane", "status" = (active1.fields["m_stat"] == "*Insane*" ? "selected" : null)) - buttons[++buttons.len] = list("name" = "*Unstable*", "icon" = "stethoscope", "href" = "m_stat=unstable", "status" = (active1.fields["m_stat"] == "*Unstable*" ? "selected" : null)) - buttons[++buttons.len] = list("name" = "*Watch*", "icon" = "stethoscope", "href" = "m_stat=watch", "status" = (active1.fields["m_stat"] == "*Watch*" ? "selected" : null)) - buttons[++buttons.len] = list("name" = "Stable", "icon" = "stethoscope", "href" = "m_stat=stable", "status" = (active1.fields["m_stat"] == "Stable" ? "selected" : null)) - setTemp("

Mental Condition

", buttons) - if("blood_type") - if(istype(active2, /datum/data/record)) - var/list/buttons = list() - buttons[++buttons.len] = list("name" = "A-", "icon" = "tint", "href" = "blood_type=an", "status" = (active2.fields["blood_type"] == "A-" ? "selected" : null)) - buttons[++buttons.len] = list("name" = "A+", "icon" = "tint", "href" = "blood_type=ap", "status" = (active2.fields["blood_type"] == "A+" ? "selected" : null)) - buttons[++buttons.len] = list("name" = "B-", "icon" = "tint", "href" = "blood_type=bn", "status" = (active2.fields["blood_type"] == "B-" ? "selected" : null)) - buttons[++buttons.len] = list("name" = "B+", "icon" = "tint", "href" = "blood_type=bp", "status" = (active2.fields["blood_type"] == "B+" ? "selected" : null)) - buttons[++buttons.len] = list("name" = "AB-", "icon" = "tint", "href" = "blood_type=abn", "status" = (active2.fields["blood_type"] == "AB-" ? "selected" : null)) - buttons[++buttons.len] = list("name" = "AB+", "icon" = "tint", "href" = "blood_type=abp", "status" = (active2.fields["blood_type"] == "AB+" ? "selected" : null)) - buttons[++buttons.len] = list("name" = "O-", "icon" = "tint", "href" = "blood_type=on", "status" = (active2.fields["blood_type"] == "O-" ? "selected" : null)) - buttons[++buttons.len] = list("name" = "O+", "icon" = "tint", "href" = "blood_type=op", "status" = (active2.fields["blood_type"] == "O+" ? "selected" : null)) - setTemp("

Blood Type

", buttons) - if("b_dna") - if(istype(active2, /datum/data/record)) - var/t1 = copytext(trim(sanitize(input("Please input DNA hash:", "Med. records", active2.fields["b_dna"], null) as text)), 1, MAX_MESSAGE_LEN) - if(!t1 || ..() || active2 != a2) - return 1 - active2.fields["b_dna"] = t1 - if("vir_name") - var/datum/data/record/v = locate(href_list["edit_vir"]) - if(v) - var/t1 = copytext(trim(sanitize(input("Please input pathogen name:", "VirusDB", v.fields["name"], null) as text)), 1, MAX_MESSAGE_LEN) - if(!t1 || ..() || active1 != a1) - return 1 - v.fields["name"] = t1 - if("vir_desc") - var/datum/data/record/v = locate(href_list["edit_vir"]) - if(v) - var/t1 = copytext(trim(sanitize(input("Please input information about pathogen:", "VirusDB", v.fields["description"], null) as message)), 1, MAX_MESSAGE_LEN) - if(!t1 || ..() || active1 != a1) - return 1 - v.fields["description"] = t1 - - if(href_list["del_r"]) - if(active2) - var/list/buttons = list() - buttons[++buttons.len] = list("name" = "Yes", "icon" = "check", "href" = "del_r2=1", "status" = null) - buttons[++buttons.len] = list("name" = "No", "icon" = "times", "href" = null, "status" = null) - setTemp("

Are you sure you wish to delete the record (Medical Portion Only)?

", buttons) - - if(href_list["d_rec"]) - var/datum/data/record/R = locate(href_list["d_rec"]) - var/datum/data/record/M = locate(href_list["d_rec"]) - if(!GLOB.data_core.general.Find(R)) - setTemp("

Record not found!

") - return 1 - for(var/datum/data/record/E in GLOB.data_core.medical) - if(E.fields["name"] == R.fields["name"] && E.fields["id"] == R.fields["id"]) - M = E - active1 = R - active2 = M - screen = MED_DATA_RECORD - - if(href_list["new"]) - if(istype(active1, /datum/data/record) && !istype(active2, /datum/data/record)) - var/datum/data/record/R = new /datum/data/record() - R.fields["name"] = active1.fields["name"] - R.fields["id"] = active1.fields["id"] - R.name = "Medical Record #[R.fields["id"]]" - R.fields["blood_type"] = "Unknown" - R.fields["b_dna"] = "Unknown" - R.fields["mi_dis"] = "None" - R.fields["mi_dis_d"] = "No minor disabilities have been declared." - R.fields["ma_dis"] = "None" - R.fields["ma_dis_d"] = "No major disabilities have been diagnosed." - R.fields["alg"] = "None" - R.fields["alg_d"] = "No allergies have been detected in this patient." - R.fields["cdi"] = "None" - R.fields["cdi_d"] = "No diseases have been diagnosed at the moment." - R.fields["notes"] = "No notes." - GLOB.data_core.medical += R - active2 = R + active1 = general_record + active2 = medical_record screen = MED_DATA_RECORD - - if(href_list["add_c"]) - if(!istype(active2, /datum/data/record)) - return 1 - var/a2 = active2 - var/t1 = copytext(trim(sanitize(input("Add Comment:", "Med. records", null, null) as message)), 1, MAX_MESSAGE_LEN) - if(!t1 || ..() || active2 != a2) - return 1 - active2.fields["comments"] += "Made by [authenticated] ([rank]) on [GLOB.current_date_string] [station_time_timestamp()]
[t1]" - - if(href_list["del_c"]) - var/index = min(max(text2num(href_list["del_c"]) + 1, 1), length(active2.fields["comments"])) - if(istype(active2, /datum/data/record) && active2.fields["comments"][index]) - active2.fields["comments"] -= active2.fields["comments"][index] - - if(href_list["search"]) - var/t1 = clean_input("Search String: (Name, DNA, or ID)", "Med. records", null, null) - if(!t1 || ..()) - return 1 - active1 = null - active2 = null - t1 = lowertext(t1) - for(var/datum/data/record/R in GLOB.data_core.medical) - if(t1 == lowertext(R.fields["name"]) || t1 == lowertext(R.fields["id"]) || t1 == lowertext(R.fields["b_dna"])) + if("new") + if(istype(active1, /datum/data/record) && !istype(active2, /datum/data/record)) + var/datum/data/record/R = new /datum/data/record() + R.fields["name"] = active1.fields["name"] + R.fields["id"] = active1.fields["id"] + R.name = "Medical Record #[R.fields["id"]]" + R.fields["blood_type"] = "Unknown" + R.fields["b_dna"] = "Unknown" + R.fields["mi_dis"] = "None" + R.fields["mi_dis_d"] = "No minor disabilities have been declared." + R.fields["ma_dis"] = "None" + R.fields["ma_dis_d"] = "No major disabilities have been diagnosed." + R.fields["alg"] = "None" + R.fields["alg_d"] = "No allergies have been detected in this patient." + R.fields["cdi"] = "None" + R.fields["cdi_d"] = "No diseases have been diagnosed at the moment." + R.fields["notes"] = "No notes." + GLOB.data_core.medical += R active2 = R - if(!active2) - setTemp("

Could not locate record [t1].

") - else + screen = MED_DATA_RECORD + set_temp("Medical record created.", "success") + if("del_c") + var/index = text2num(params["del_c"] || "") + if(!index || !istype(active2, /datum/data/record)) + return + + var/list/comments = active2.fields["comments"] + index = clamp(index, 1, length(comments)) + if(comments[index]) + comments.Cut(index, index + 1) + if("search") + active1 = null + active2 = null + var/t1 = lowertext(params["t1"] || "") + if(!length(t1)) + return + + for(var/datum/data/record/R in GLOB.data_core.medical) + if(t1 == lowertext(R.fields["name"]) || t1 == lowertext(R.fields["id"]) || t1 == lowertext(R.fields["b_dna"])) + active2 = R + break + if(!active2) + set_temp("Medical record not found. You must enter the person's exact name, ID or DNA.", "danger") + return for(var/datum/data/record/E in GLOB.data_core.general) if(E.fields["name"] == active2.fields["name"] && E.fields["id"] == active2.fields["id"]) active1 = E + break screen = MED_DATA_RECORD + if("print_p") + if(!printing) + printing = TRUE + playsound(loc, 'sound/goonstation/machines/printer_dotmatrix.ogg', 50, TRUE) + SStgui.update_uis(src) + addtimer(CALLBACK(src, .proc/print_finish), 5 SECONDS) + else + return FALSE - if(href_list["print_p"]) - if(!printing) - printing = 1 - playsound(loc, 'sound/goonstation/machines/printer_dotmatrix.ogg', 50, 1) - sleep(50) - var/obj/item/paper/P = new /obj/item/paper(loc) - P.info = "
Medical Record

" - if(istype(active1, /datum/data/record) && GLOB.data_core.general.Find(active1)) - P.info += {"Name: [active1.fields["name"]] ID: [active1.fields["id"]] -
\nSex: [active1.fields["sex"]] -
\nAge: [active1.fields["age"]] -
\nFingerprint: [active1.fields["fingerprint"]] -
\nPhysical Status: [active1.fields["p_stat"]] -
\nMental Status: [active1.fields["m_stat"]]
"} +/** + * Called in tgui_act() to process modal actions + * + * Arguments: + * * action - The action passed by tgui + * * params - The params passed by tgui + */ +/obj/machinery/computer/med_data/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_OPEN) + switch(id) + if("edit") + var/field = arguments["field"] + if(!length(field) || !field_edit_questions[field]) + return + var/question = field_edit_questions[field] + var/choices = field_edit_choices[field] + if(length(choices)) + tgui_modal_choice(src, id, question, arguments = arguments, value = arguments["value"], choices = choices) + else + tgui_modal_input(src, id, question, arguments = arguments, value = arguments["value"]) + if("add_c") + tgui_modal_input(src, id, "Please enter your message:") else - P.info += "General Record Lost!
" - if(istype(active2, /datum/data/record) && GLOB.data_core.medical.Find(active2)) - P.info += {"
\n
Medical Data
-
\nBlood Type: [active2.fields["blood_type"]] -
\nDNA: [active2.fields["b_dna"]]
\n -
\nMinor Disabilities: [active2.fields["mi_dis"]] -
\nDetails: [active2.fields["mi_dis_d"]]
\n -
\nMajor Disabilities: [active2.fields["ma_dis"]] -
\nDetails: [active2.fields["ma_dis_d"]]
\n -
\nAllergies: [active2.fields["alg"]] -
\nDetails: [active2.fields["alg_d"]]
\n -
\nCurrent Diseases: [active2.fields["cdi"]] (per disease info placed in log/comment section) -
\nDetails: [active2.fields["cdi_d"]]
\n -
\nImportant Notes: -
\n\t[active2.fields["notes"]]
\n -
\n -
Comments/Log

"} - for(var/c in active2.fields["comments"]) - P.info += "[c]
" - else - P.info += "Medical Record Lost!
" - P.info += "" - P.name = "paper- 'Medical Record: [active1.fields["name"]]'" - printing = 0 - return 1 + return FALSE + if(TGUI_MODAL_ANSWER) + var/answer = params["answer"] + switch(id) + if("edit") + var/field = arguments["field"] + if(!length(field) || !field_edit_questions[field]) + return + var/list/choices = field_edit_choices[field] + if(length(choices) && !(answer in choices)) + return -/obj/machinery/computer/med_data/proc/setTemp(text, list/buttons = list()) - temp = list("text" = text, "buttons" = buttons, "has_buttons" = buttons.len > 0) + if(field == "age") + var/new_age = text2num(answer) + if(new_age < AGE_MIN || new_age > AGE_MAX) + set_temp("Invalid age. It must be between [AGE_MIN] and [AGE_MAX].", "danger") + return + answer = new_age + + if(istype(active2) && (field in active2.fields)) + active2.fields[field] = answer + else if(istype(active1) && (field in active1.fields)) + active1.fields[field] = answer + if("add_c") + if(!length(answer) || !istype(active2) || !length(authenticated)) + return + active2.fields["comments"] += list(list( + header = "Made by [authenticated] ([rank]) on [GLOB.current_date_string] [station_time_timestamp()]", + text = answer + )) + else + return FALSE + else + return FALSE + +/** + * Called when the print timer finishes + */ +/obj/machinery/computer/med_data/proc/print_finish() + var/obj/item/paper/P = new /obj/item/paper(loc) + P.info = "
Medical Record

" + if(istype(active1, /datum/data/record) && GLOB.data_core.general.Find(active1)) + P.info += {"Name: [active1.fields["name"]] ID: [active1.fields["id"]] +
\nSex: [active1.fields["sex"]] +
\nAge: [active1.fields["age"]] +
\nFingerprint: [active1.fields["fingerprint"]] +
\nPhysical Status: [active1.fields["p_stat"]] +
\nMental Status: [active1.fields["m_stat"]]
"} + else + P.info += "General Record Lost!
" + if(istype(active2, /datum/data/record) && GLOB.data_core.medical.Find(active2)) + P.info += {"
\n
Medical Data
+
\nBlood Type: [active2.fields["blood_type"]] +
\nDNA: [active2.fields["b_dna"]]
\n +
\nMinor Disabilities: [active2.fields["mi_dis"]] +
\nDetails: [active2.fields["mi_dis_d"]]
\n +
\nMajor Disabilities: [active2.fields["ma_dis"]] +
\nDetails: [active2.fields["ma_dis_d"]]
\n +
\nAllergies: [active2.fields["alg"]] +
\nDetails: [active2.fields["alg_d"]]
\n +
\nCurrent Diseases: [active2.fields["cdi"]] (per disease info placed in log/comment section) +
\nDetails: [active2.fields["cdi_d"]]
\n +
\nImportant Notes: +
\n\t[active2.fields["notes"]]
\n +
\n +
Comments/Log

"} + for(var/c in active2.fields["comments"]) + P.info += "[c]
" + else + P.info += "Medical Record Lost!
" + P.info += "" + P.name = "paper - 'Medical Record: [active1.fields["name"]]'" + printing = FALSE + SStgui.update_uis(src) + +/** + * Sets a temporary message to display to the user + * + * Arguments: + * * text - Text to display, null/empty to clear the message from the UI + * * style - The style of the message: (color name), info, success, warning, danger, virus + */ +/obj/machinery/computer/med_data/proc/set_temp(text = "", style = "info", update_now = FALSE) + temp = list(text = text, style = style) + if(update_now) + SStgui.update_uis(src) /obj/machinery/computer/med_data/emp_act(severity) if(stat & (BROKEN|NOPOWER)) @@ -564,9 +502,10 @@ icon_screen = "medlaptop" density = 0 -#undef MED_DATA_MAIN #undef MED_DATA_R_LIST #undef MED_DATA_MAINT #undef MED_DATA_RECORD #undef MED_DATA_V_DATA #undef MED_DATA_MEDBOT +#undef FIELD +#undef MED_FIELD diff --git a/code/game/machinery/computer/sm_monitor.dm b/code/game/machinery/computer/sm_monitor.dm new file mode 100644 index 00000000000..f2e01e6c4fa --- /dev/null +++ b/code/game/machinery/computer/sm_monitor.dm @@ -0,0 +1,147 @@ +/obj/machinery/computer/sm_monitor + name = "supermatter monitoring console" + desc = "Used to monitor supermatter shards." + icon_keyboard = "power_key" + icon_screen = "smmon_0" + circuit = /obj/item/circuitboard/sm_monitor + light_color = LIGHT_COLOR_YELLOW + /// Cache-list of all supermatter shards + var/list/supermatters + /// Last status of the active supermatter for caching purposes + var/last_status + /// Reference to the active shard + var/obj/machinery/power/supermatter_shard/active + +/obj/machinery/computer/sm_monitor/Destroy() + active = null + return ..() + +/obj/machinery/computer/sm_monitor/attack_ai(mob/user) + attack_hand(user) + +/obj/machinery/computer/sm_monitor/attack_hand(mob/user) + add_fingerprint(user) + if(stat & (BROKEN|NOPOWER)) + return + tgui_interact(user) + +/obj/machinery/computer/sm_monitor/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, "SupermatterMonitor", name, 600, 325, master_ui, state) + ui.open() + +/obj/machinery/computer/sm_monitor/tgui_data(mob/user) + var/list/data = list() + + if(istype(active)) + var/turf/T = get_turf(active) + // If we somehow delam during this proc, handle it somewhat + if(!T) + active = null + refresh() + return + var/datum/gas_mixture/air = T.return_air() + if(!air) + active = null + return + + data["active"] = TRUE + data["SM_integrity"] = active.get_integrity() + data["SM_power"] = active.power + data["SM_ambienttemp"] = air.temperature + data["SM_ambientpressure"] = air.return_pressure() + //data["SM_EPR"] = round((air.total_moles / air.group_multiplier) / 23.1, 0.01) + var/other_moles = air.total_trace_moles() + var/TM = air.total_moles() + if(TM) + data["SM_gas_O2"] = round(100*air.oxygen/TM, 0.01) + data["SM_gas_CO2"] = round(100*air.carbon_dioxide/TM, 0.01) + data["SM_gas_N2"] = round(100*air.nitrogen/TM, 0.01) + data["SM_gas_PL"] = round(100*air.toxins/TM, 0.01) + if(other_moles) + data["SM_gas_OTHER"] = round(100 * other_moles / TM, 0.01) + else + data["SM_gas_OTHER"] = 0 + else + data["SM_gas_O2"] = 0 + data["SM_gas_CO2"] = 0 + data["SM_gas_N2"] = 0 + data["SM_gas_PH"] = 0 + data["SM_gas_OTHER"] = 0 + else + var/list/SMS = list() + for(var/I in supermatters) + var/obj/machinery/power/supermatter_shard/S = I + var/area/A = get_area(S) + if(!A) + continue + + SMS.Add(list(list( + "area_name" = A.name, + "integrity" = S.get_integrity(), + "uid" = S.UID() + ))) + + data["active"] = FALSE + data["supermatters"] = SMS + + return data + +/** + * Supermatter List Refresher + * + * This proc loops through the list of supermatters in the atmos SS and adds them to this console's cache list + */ +/obj/machinery/computer/sm_monitor/proc/refresh() + supermatters = list() + var/turf/T = get_turf(tgui_host()) // Get the TGUI host incase this ever turned into a supermatter monitoring module for AIs to use or something + if(!T) + return + for(var/obj/machinery/power/supermatter_shard/S in SSair.atmos_machinery) + // Delaminating, not within coverage, not on a tile. + if(!(is_station_level(S.z) || is_mining_level(S.z) || atoms_share_level(S, T) || !istype(S.loc, /turf/simulated/))) + continue + supermatters.Add(S) + + if(!(active in supermatters)) + active = null + +/obj/machinery/computer/sm_monitor/process() + if(stat & (NOPOWER|BROKEN)) + return FALSE + + if(active) + var/new_status = active.get_status() + if(last_status != new_status) + last_status = new_status + if(last_status == SUPERMATTER_ERROR) + last_status = SUPERMATTER_INACTIVE + icon_screen = "smmon_[last_status]" + update_icon() + + return TRUE + +/obj/machinery/computer/sm_monitor/tgui_act(action, params) + if(..()) + return + + if(stat & (BROKEN|NOPOWER)) + return + + . = TRUE + + switch(action) + if("refresh") + refresh() + + if("view") + var/newuid = params["view"] + for(var/obj/machinery/power/supermatter_shard/S in supermatters) + if(S.UID() == newuid) + active = S + break + + if("back") + active = null + diff --git a/code/game/machinery/cryo.dm b/code/game/machinery/cryo.dm index 8be09ef30b7..ba8f430937c 100644 --- a/code/game/machinery/cryo.dm +++ b/code/game/machinery/cryo.dm @@ -103,7 +103,7 @@ beaker.forceMove(drop_location()) beaker = null -/obj/machinery/atmospherics/unary/cryo_cell/MouseDrop_T(atom/movable/O as mob|obj, mob/living/user as mob) +/obj/machinery/atmospherics/unary/cryo_cell/MouseDrop_T(atom/movable/O, mob/living/user) if(O.loc == user) //no you can't pull things out of your ass return if(user.incapacitated()) //are you cuffed, dying, lying, stunned or other @@ -140,6 +140,7 @@ add_attack_logs(user, L, "put into a cryo cell at [COORD(src)].", ATKLOG_ALL) if(user.pulling == L) user.stop_pulling() + SStgui.update_uis(src) /obj/machinery/atmospherics/unary/cryo_cell/process() ..() @@ -177,14 +178,14 @@ return FALSE -/obj/machinery/atmospherics/unary/cryo_cell/relaymove(mob/user as mob) +/obj/machinery/atmospherics/unary/cryo_cell/relaymove(mob/user) if(user.stat) return go_out() return /obj/machinery/atmospherics/unary/cryo_cell/attack_ghost(mob/user) - return attack_hand(user) + tgui_interact(user) /obj/machinery/atmospherics/unary/cryo_cell/attack_hand(mob/user) if(user == occupant) @@ -194,36 +195,18 @@ to_chat(usr, "Close the maintenance panel first.") 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 (which is inherited by /obj and /mob) - * - * @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/atmospherics/unary/cryo_cell/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) - // 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) +/obj/machinery/atmospherics/unary/cryo_cell/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) - // 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, "cryo.tmpl", "Cryo Cell Control System", 520, 480) - // open the new ui window + ui = new(user, src, ui_key, "Cryo", "Cryo Cell", 520, 490) ui.open() - // auto update every Master Controller tick - ui.set_auto_update(1) -/obj/machinery/atmospherics/unary/cryo_cell/ui_data(mob/user, ui_key = "main", datum/topic_state/state = GLOB.default_state) +/obj/machinery/atmospherics/unary/cryo_cell/tgui_data(mob/user) var/data[0] data["isOperating"] = on - data["hasOccupant"] = occupant ? 1 : 0 + data["hasOccupant"] = occupant ? TRUE : FALSE var/occupantData[0] if(occupant) @@ -246,7 +229,7 @@ else if(air_contents.temperature > TCRYO) data["cellTemperatureStatus"] = "average" - data["isBeakerLoaded"] = beaker ? 1 : 0 + data["isBeakerLoaded"] = beaker ? TRUE : FALSE data["beakerLabel"] = null data["beakerVolume"] = 0 if(beaker) @@ -259,48 +242,44 @@ data["auto_eject_dead"] = (auto_eject_prefs & AUTO_EJECT_DEAD) ? TRUE : FALSE return data -/obj/machinery/atmospherics/unary/cryo_cell/Topic(href, href_list) - if(usr == occupant) - return 0 // don't update UIs attached to this object +/obj/machinery/atmospherics/unary/cryo_cell/tgui_act(action, params) + if(..() || usr == occupant) + return + if(stat & (NOPOWER|BROKEN)) + return - if(..()) - return 0 // don't update UIs attached to this object - - if(href_list["switchOn"]) - on = TRUE - update_icon() - - if(href_list["switchOff"]) - on = FALSE - update_icon() - - if(href_list["auto_eject_healthy_on"]) - auto_eject_prefs |= AUTO_EJECT_HEALTHY - - if(href_list["auto_eject_healthy_off"]) - auto_eject_prefs &= ~AUTO_EJECT_HEALTHY - - if(href_list["auto_eject_dead_on"]) - auto_eject_prefs |= AUTO_EJECT_DEAD - - if(href_list["auto_eject_dead_off"]) - auto_eject_prefs &= ~AUTO_EJECT_DEAD - - if(href_list["ejectBeaker"]) - if(beaker) + . = TRUE + switch(action) + if("switchOn") + on = TRUE + update_icon() + if("switchOff") + on = FALSE + update_icon() + if("auto_eject_healthy_on") + auto_eject_prefs |= AUTO_EJECT_HEALTHY + if("auto_eject_healthy_off") + auto_eject_prefs &= ~AUTO_EJECT_HEALTHY + if("auto_eject_dead_on") + auto_eject_prefs |= AUTO_EJECT_DEAD + if("auto_eject_dead_off") + auto_eject_prefs &= ~AUTO_EJECT_DEAD + if("ejectBeaker") + if(!beaker) + return beaker.forceMove(get_step(loc, SOUTH)) beaker = null - - if(href_list["ejectOccupant"]) - if(!occupant || isslime(usr) || ispAI(usr)) - return 0 // don't update UIs attached to this object - add_attack_logs(usr, occupant, "ejected from cryo cell at [COORD(src)]", ATKLOG_ALL) - go_out() + if("ejectOccupant") + if(!occupant || isslime(usr) || ispAI(usr)) + return + add_attack_logs(usr, occupant, "ejected from cryo cell at [COORD(src)]", ATKLOG_ALL) + go_out() + else + return FALSE add_fingerprint(usr) - return 1 // update UIs attached to this object -/obj/machinery/atmospherics/unary/cryo_cell/attackby(var/obj/item/G as obj, var/mob/user as mob, params) +/obj/machinery/atmospherics/unary/cryo_cell/attackby(var/obj/item/G, var/mob/user, params) if(istype(G, /obj/item/reagent_containers/glass)) var/obj/item/reagent_containers/B = G if(beaker) @@ -313,6 +292,7 @@ beaker = B add_attack_logs(user, null, "Added [B] containing [B.reagents.log_list()] to a cryo cell at [COORD(src)]") user.visible_message("[user] adds \a [B] to [src]!", "You add \a [B] to [src]!") + SStgui.update_uis(src) return if(exchange_parts(user, G)) @@ -357,7 +337,7 @@ return if(occupant) - var/image/pickle = image(occupant.icon, occupant.icon_state) + var/mutable_appearance/pickle = mutable_appearance(occupant.icon, occupant.icon_state) pickle.overlays = occupant.overlays pickle.pixel_y = 22 @@ -455,8 +435,9 @@ playsound(loc, 'sound/machines/ding.ogg', 50, 1) if(AUTO_EJECT_DEAD) playsound(loc, 'sound/machines/buzz-sigh.ogg', 40) + SStgui.update_uis(src) -/obj/machinery/atmospherics/unary/cryo_cell/proc/put_mob(mob/living/carbon/M as mob) +/obj/machinery/atmospherics/unary/cryo_cell/proc/put_mob(mob/living/carbon/M) if(!istype(M)) to_chat(usr, "The cryo cell cannot handle such a lifeform!") return @@ -530,7 +511,7 @@ /datum/data/function/proc/reset() return -/datum/data/function/proc/r_input(href, href_list, mob/user as mob) +/datum/data/function/proc/r_input(href, href_list, mob/user) return /datum/data/function/proc/display() diff --git a/code/game/machinery/doors/airlock.dm b/code/game/machinery/doors/airlock.dm index d2e983b3eb3..3f074c9051d 100644 --- a/code/game/machinery/doors/airlock.dm +++ b/code/game/machinery/doors/airlock.dm @@ -38,6 +38,11 @@ #define AIRLOCK_DAMAGE_DEFLECTION_N 21 // Normal airlock damage deflection #define AIRLOCK_DAMAGE_DEFLECTION_R 30 // Reinforced airlock damage deflection +#define TGUI_GREEN 2 +#define TGUI_ORANGE 1 +#define TGUI_RED 0 + + GLOBAL_LIST_EMPTY(airlock_overlays) /obj/machinery/door/airlock @@ -54,7 +59,7 @@ GLOBAL_LIST_EMPTY(airlock_overlays) normalspeed = 1 siemens_strength = 1 var/security_level = 0 //How much are wires secured - var/aiControlDisabled = FALSE //If TRUE, 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. + var/aiControlDisabled = AICONTROLDISABLED_OFF var/hackProof = FALSE // if TRUE, this door can't be hacked by the AI var/electrified_until = 0 // World time when the door is no longer electrified. -1 if it is permanently electrified until someone fixes it. var/main_power_lost_until = 0 //World time when main power is restored. @@ -65,7 +70,7 @@ GLOBAL_LIST_EMPTY(airlock_overlays) var/spawnPowerRestoreRunning = 0 var/lights = TRUE // bolt lights show by default var/datum/wires/airlock/wires - var/aiDisabledIdScanner = 0 + var/aiDisabledIdScanner = FALSE var/aiHacking = 0 var/obj/machinery/door/airlock/closeOther var/closeOtherId @@ -96,7 +101,7 @@ GLOBAL_LIST_EMPTY(airlock_overlays) var/doorDeni = 'sound/machines/deniedbeep.ogg' // i'm thinkin' Deni's var/boltUp = 'sound/machines/boltsup.ogg' var/boltDown = 'sound/machines/boltsdown.ogg' - var/is_special = 0 + var/is_special = FALSE /obj/machinery/door/airlock/welded welded = TRUE @@ -174,7 +179,7 @@ About the new airlock wires panel: spawn (10) justzap = 0 return - else /*if(justzap)*/ + else return else if(user.hallucination > 50 && prob(10) && !operating) if(user.electrocute_act(50, src, 1, illusion = TRUE)) // We'll just go with a flat 50 damage, instead of doing powernet checks @@ -190,10 +195,10 @@ About the new airlock wires panel: return 0 /obj/machinery/door/airlock/proc/canAIControl() - return ((aiControlDisabled!=1) && (!isAllPowerLoss())) + return ((aiControlDisabled != AICONTROLDISABLED_ON) && (!isAllPowerLoss())) /obj/machinery/door/airlock/proc/canAIHack() - return ((aiControlDisabled==1) && (!hackProof) && (!isAllPowerLoss())) + return ((aiControlDisabled == AICONTROLDISABLED_ON) && (!hackProof) && (!isAllPowerLoss())) /obj/machinery/door/airlock/proc/arePowerSystemsOn() if(stat & (NOPOWER|BROKEN)) @@ -561,38 +566,58 @@ About the new airlock wires panel: /obj/machinery/door/airlock/attack_ghost(mob/user) if(panel_open) wires.Interact(user) - ui_interact(user) + tgui_interact(user) /obj/machinery/door/airlock/attack_ai(mob/user) - ui_interact(user) + tgui_interact(user) -/obj/machinery/door/airlock/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/door/airlock/tgui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = TRUE, 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, "door_control.tmpl", "Door Controls - [src]", 600, 375) + ui = new(user, src, ui_key, "AiAirlock", name, 600, 400, master_ui, state) ui.open() - ui.set_auto_update(1) -/obj/machinery/door/airlock/ui_data(mob/user, datum/topic_state/state = GLOB.default_state) - var/data[0] - data["main_power_loss"] = round(main_power_lost_until > 0 ? max(main_power_lost_until - world.time, 0) / 10 : main_power_lost_until, 1) - data["backup_power_loss"] = round(backup_power_lost_until > 0 ? max(backup_power_lost_until - world.time, 0) / 10 : backup_power_lost_until, 1) - data["electrified"] = round(electrified_until > 0 ? max(electrified_until - world.time, 0) / 10 : electrified_until, 1) - data["open"] = !density +/obj/machinery/door/airlock/tgui_data(mob/user) + var/list/data = list() - var/commands[0] - commands[++commands.len] = list("name" = "IdScan", "command"= "idscan", "active" = !aiDisabledIdScanner,"enabled" = "Enabled", "disabled" = "Disable", "danger" = 0, "act" = 1) - commands[++commands.len] = list("name" = "Bolts", "command"= "bolts", "active" = !locked, "enabled" = "Raised", "disabled" = "Dropped", "danger" = 0, "act" = 0) - commands[++commands.len] = list("name" = "Bolt Lights", "command"= "lights", "active" = lights, "enabled" = "Enabled", "disabled" = "Disable", "danger" = 0, "act" = 1) - commands[++commands.len] = list("name" = "Safeties", "command"= "safeties", "active" = safe, "enabled" = "Nominal", "disabled" = "Overridden", "danger" = 1, "act" = 0) - commands[++commands.len] = list("name" = "Timing", "command"= "timing", "active" = normalspeed, "enabled" = "Nominal", "disabled" = "Overridden", "danger" = 1, "act" = 0) - commands[++commands.len] = list("name" = "Door State", "command"= "open", "active" = density, "enabled" = "Closed", "disabled" = "Opened", "danger" = 0, "act" = 0) - commands[++commands.len] = list("name" = "Emergency Access","command"= "emergency", "active" = !emergency, "enabled" = "Disabled", "disabled" = "Enabled", "danger" = 0, "act" = 0) + var/list/power = list() + power["main"] = main_power_lost_until ? TGUI_RED : TGUI_GREEN + power["main_timeleft"] = max(main_power_lost_until - world.time, 0) / 10 + power["backup"] = backup_power_lost_until ? TGUI_RED : TGUI_GREEN + power["backup_timeleft"] = max(backup_power_lost_until - world.time, 0) / 10 + data["power"] = power + if(electrified_until == -1) + data["shock"] = TGUI_RED + else if(electrified_until > 0) + data["shock"] = TGUI_ORANGE + else + data["shock"] = TGUI_GREEN - data["commands"] = commands + data["shock_timeleft"] = max(electrified_until - world.time, 0) / 10 + data["id_scanner"] = !aiDisabledIdScanner + data["emergency"] = emergency // access + data["locked"] = locked // bolted + data["lights"] = lights // bolt lights + data["safe"] = safe // safeties + data["speed"] = normalspeed // safe speed + data["welded"] = welded // welded + data["opened"] = !density // opened + + var/list/wire = list() + wire["main_power"] = !wires.is_cut(WIRE_MAIN_POWER1) + wire["backup_power"] = !wires.is_cut(WIRE_BACKUP_POWER1) + wire["shock"] = !wires.is_cut(WIRE_ELECTRIFY) + wire["id_scanner"] = !wires.is_cut(WIRE_IDSCAN) + wire["bolts"] = !wires.is_cut(WIRE_DOOR_BOLTS) + wire["lights"] = !wires.is_cut(WIRE_BOLT_LIGHT) + wire["safe"] = !wires.is_cut(WIRE_SAFETY) + wire["timing"] = !wires.is_cut(WIRE_SPEED) + + data["wires"] = wire return data + /obj/machinery/door/airlock/proc/hack(mob/user) set waitfor = 0 if(!aiHacking) @@ -632,7 +657,7 @@ About the new airlock wires panel: to_chat(user, "Transfer complete. Forcing airlock to execute program.") sleep(50) //disable blocked control - aiControlDisabled = 2 + aiControlDisabled = AICONTROLDISABLED_BYPASS to_chat(user, "Receiving control information from airlock.") sleep(10) //bring up airlock dialog @@ -728,125 +753,125 @@ About the new airlock wires panel: else try_to_activate_door(user) -/obj/machinery/door/airlock/CanUseTopic(mob/user) - if(!issilicon(user) && !isobserver(user)) - return STATUS_CLOSE - if(emagged) - to_chat(user, "Unable to interface: Internal error.") - return STATUS_CLOSE - if(!canAIControl() && !isobserver(user)) - if(canAIHack(user)) - hack(user) - else - if(isAllPowerLoss()) //don't really like how this gets checked a second time, but not sure how else to do it. - to_chat(user, "Unable to interface: Connection timed out.") - else - to_chat(user, "Unable to interface: Connection refused.") - return STATUS_CLOSE - - return ..() - -/obj/machinery/door/airlock/Topic(href, href_list, nowindow = 0) +/obj/machinery/door/airlock/tgui_act(action, params) if(..()) - return 1 - - var/activate = text2num(href_list["activate"]) - switch(href_list["command"]) - if("idscan") - if(wires.is_cut(WIRE_IDSCAN)) - to_chat(usr, "The IdScan wire has been cut - IdScan feature permanently disabled.") - else if(activate && aiDisabledIdScanner) - aiDisabledIdScanner = 0 - to_chat(usr, "IdScan feature has been enabled.") - else if(!activate && !aiDisabledIdScanner) - aiDisabledIdScanner = 1 - to_chat(usr, "IdScan feature has been disabled.") - if("main_power") + return + if(!issilicon(usr) && !usr.can_admin_interact()) + to_chat(usr, "Access denied. Only silicons may use this interface.") + return + if(issilicon(usr) && emagged) + to_chat(usr, "Unable to interface: Internal error.") + return + if(!canAIControl() && !isobserver(usr)) + if(canAIHack(usr)) + hack(usr) + else + if(isAllPowerLoss()) + to_chat(usr, "Unable to interface: Connection timed out.") + else + to_chat(usr, "Unable to interface: Connection refused.") + return + . = TRUE + switch(action) + if("disrupt-main") if(!main_power_lost_until) loseMainPower() update_icon() - if("backup_power") + else + to_chat(usr, "Main power is already offline.") + . = FALSE + if("disrupt-backup") if(!backup_power_lost_until) loseBackupPower() update_icon() - if("bolts") - if(wires.is_cut(WIRE_DOOR_BOLTS)) - to_chat(usr, "The door bolt control wire has been cut - Door bolts permanently dropped.") - else if(activate && lock()) - to_chat(usr, "The door bolts have been dropped.") - else if(!activate && unlock()) - to_chat(usr, "The door bolts have been raised.") - if("electrify_temporary") - if(activate && wires.is_cut(WIRE_ELECTRIFY)) - to_chat(usr, text("The electrification wire is cut - Door permanently electrified.")) - else if(!activate && electrified_until != 0) - to_chat(usr, "The door is now un-electrified.") - electrify(0) - else if(activate) //electrify door for 30 seconds - shockedby += text("\[[time_stamp()]\][usr](ckey:[usr.ckey])") - usr.create_attack_log("Electrified the [name] at [x] [y] [z]") - add_attack_logs(usr, src, "Electrified", ATKLOG_ALL) - to_chat(usr, "The door is now electrified for thirty seconds.") - electrify(30) - if("electrify_permanently") - if(wires.is_cut(WIRE_ELECTRIFY)) - to_chat(usr, text("The electrification wire is cut - Cannot electrify the door.")) - else if(!activate && electrified_until != 0) - to_chat(usr, "The door is now un-electrified.") - electrify(0) - else if(activate) - shockedby += text("\[[time_stamp()]\][usr](ckey:[usr.ckey])") - usr.create_attack_log("Electrified the [name] at [x] [y] [z]") - add_attack_logs(usr, src, "Electrified", ATKLOG_ALL) - to_chat(usr, "The door is now electrified.") - electrify(-1) - if("open") - if(welded) - to_chat(usr, text("The airlock has been welded shut!")) - else if(locked) - to_chat(usr, text("The door bolts are down!")) - else if(activate && density) - open() - else if(!activate && !density) - close() - if("safeties") - // Safeties! We don't need no stinking safeties! - if(wires.is_cut(WIRE_SAFETY)) - to_chat(usr, text("The safety wire is cut - Cannot secure the door.")) - else if(activate && safe) - safe = 0 - else if(!activate && !safe) - safe = 1 - if("timing") - // Door speed control - if(wires.is_cut(WIRE_SPEED)) - to_chat(usr, text("The timing wire is cut - Cannot alter timing.")) - else if(activate && normalspeed) - normalspeed = 0 - else if(!activate && !normalspeed) - normalspeed = 1 - if("lights") - // Bolt lights - if(wires.is_cut(WIRE_BOLT_LIGHT)) - to_chat(usr, "The bolt lights wire has been cut - The door bolt lights are permanently disabled.") - else if(!activate && lights) - lights = 0 - to_chat(usr, "The door bolt lights have been disabled.") - else if(activate && !lights) - lights = 1 - to_chat(usr, "The door bolt lights have been enabled.") - update_icon() - if("emergency") - // Emergency access - if(emergency) - emergency = 0 - to_chat(usr, "Emergency access has been disabled.") else - emergency = 1 - to_chat(usr, "Emergency access has been enabled.") + to_chat(usr, "Backup power is already offline.") + if("shock-restore") + to_chat(usr, "The door is now un-electrified.") + electrify(0) + if("shock-temp") + if(wires.is_cut(WIRE_ELECTRIFY)) + to_chat(usr, "The electrification wire is cut - Door permanently electrified.") + . = FALSE + else + //electrify door for 30 seconds + shockedby += text("\[[time_stamp()]\][usr](ckey:[usr.ckey])") + usr.create_attack_log("Electrified the [name] at [x] [y] [z]") + add_attack_logs(usr, src, "Electrified", ATKLOG_ALL) + to_chat(usr, "The door is now electrified for thirty seconds.") + electrify(30) + if("shock-perm") + if(wires.is_cut(WIRE_ELECTRIFY)) + to_chat(usr, "The electrification wire is cut - Cannot electrify the door.") + . = FALSE + else + shockedby += text("\[[time_stamp()]\][usr](ckey:[usr.ckey])") + usr.create_attack_log("Electrified the [name] at [x] [y] [z]") + add_attack_logs(usr, src, "Electrified", ATKLOG_ALL) + to_chat(usr, "The door is now electrified.") + electrify(-1) + if("idscan-toggle") + if(wires.is_cut(WIRE_IDSCAN)) + to_chat(usr, "The IdScan wire has been cut - IdScan feature permanently disabled.") + . = FALSE + else if(aiDisabledIdScanner) + aiDisabledIdScanner = FALSE + to_chat(usr, "IdScan feature has been enabled.") + else + aiDisabledIdScanner = TRUE + to_chat(usr, "IdScan feature has been disabled.") + if("emergency-toggle") + emergency = !emergency + if(emergency) + to_chat(usr, "Emergency access has been enabled.") + else + to_chat(usr, "Emergency access has been disabled.") update_icon() - return 1 + if("bolt-toggle") + if(wires.is_cut(WIRE_DOOR_BOLTS)) + to_chat(usr, "The door bolt control wire has been cut - Door bolts permanently dropped.") + else if(lock()) + to_chat(usr, "The door bolts have been dropped.") + else if(unlock()) + to_chat(usr, "The door bolts have been raised.") + if("light-toggle") + if(wires.is_cut(WIRE_BOLT_LIGHT)) + to_chat(usr, "The bolt lights wire has been cut - The door bolt lights are permanently disabled.") + else if(lights) + lights = FALSE + to_chat(usr, "The door bolt lights have been disabled.") + else if(!lights) + lights = TRUE + to_chat(usr, "The door bolt lights have been enabled.") + update_icon() + if("safe-toggle") + if(wires.is_cut(WIRE_SAFETY)) + to_chat(usr, "The safety wire is cut - Cannot secure the door.") + else if(safe) + safe = 0 + to_chat(usr, "The door safeties have been disabled.") + else + safe = 1 + to_chat(usr, "The door safeties have been enabled.") + if("speed-toggle") + if(wires.is_cut(WIRE_SPEED)) + to_chat(usr, "The timing wire is cut - Cannot alter timing.") + else if(normalspeed) + normalspeed = 0 + else + normalspeed = 1 + if("open-close") + if(welded) + to_chat(usr, "The airlock has been welded shut!") + else if(locked) + to_chat(usr, "The door bolts are down!") + else if(density) + open() + else + close() + else + . = FALSE /obj/machinery/door/airlock/attackby(obj/item/C, mob/user, params) add_fingerprint(user) @@ -1055,7 +1080,7 @@ About the new airlock wires panel: return return TRUE -/obj/machinery/door/airlock/try_to_crowbar(mob/living/user, obj/item/I) //*scream +/obj/machinery/door/airlock/try_to_crowbar(mob/living/user, obj/item/I) if(operating) return if(istype(I, /obj/item/twohanded/fireaxe)) //let's make this more specific //FUCK YOU @@ -1399,10 +1424,10 @@ About the new airlock wires panel: qdel(src) /obj/machinery/door/airlock/proc/ai_control_callback() - if(aiControlDisabled == 1) - aiControlDisabled = 0 - else if(aiControlDisabled == 2) - aiControlDisabled = -1 + if(aiControlDisabled == AICONTROLDISABLED_ON) + aiControlDisabled = AICONTROLDISABLED_OFF + else if(aiControlDisabled == AICONTROLDISABLED_BYPASS) + aiControlDisabled = AICONTROLDISABLED_PERMA #undef AIRLOCK_CLOSED #undef AIRLOCK_CLOSING @@ -1423,3 +1448,7 @@ About the new airlock wires panel: #undef AIRLOCK_INTEGRITY_MULTIPLIER #undef AIRLOCK_DAMAGE_DEFLECTION_N #undef AIRLOCK_DAMAGE_DEFLECTION_R + +#undef TGUI_GREEN +#undef TGUI_ORANGE +#undef TGUI_RED diff --git a/code/game/machinery/doors/airlock_types.dm b/code/game/machinery/doors/airlock_types.dm index dd9581a5af3..1d194507357 100644 --- a/code/game/machinery/doors/airlock_types.dm +++ b/code/game/machinery/doors/airlock_types.dm @@ -365,14 +365,14 @@ assemblytype = /obj/structure/door_assembly/door_assembly_vault security_level = 6 hackProof = TRUE - aiControlDisabled = TRUE + aiControlDisabled = AICONTROLDISABLED_ON /obj/machinery/door/airlock/hatch/gamma name = "gamma level hatch" - hackProof = 1 - aiControlDisabled = 1 + hackProof = TRUE + aiControlDisabled = AICONTROLDISABLED_ON resistance_flags = FIRE_PROOF | ACID_PROOF - is_special = 1 + is_special = TRUE /obj/machinery/door/airlock/hatch/gamma/attackby(obj/C, mob/user, params) if(!issilicon(user)) @@ -432,8 +432,8 @@ /obj/machinery/door/airlock/highsecurity/red name = "secure armory airlock" - hackProof = 1 - aiControlDisabled = 1 + hackProof = TRUE + aiControlDisabled = AICONTROLDISABLED_ON /obj/machinery/door/airlock/highsecurity/red/attackby(obj/C, mob/user, params) if(!issilicon(user)) @@ -487,7 +487,7 @@ damage_deflection = 30 explosion_block = 3 hackProof = TRUE - aiControlDisabled = 1 + aiControlDisabled = AICONTROLDISABLED_ON normal_integrity = 700 security_level = 1 paintable = FALSE @@ -504,7 +504,7 @@ assemblytype = /obj/structure/door_assembly/door_assembly_cult damage_deflection = 10 hackProof = TRUE - aiControlDisabled = TRUE + aiControlDisabled = AICONTROLDISABLED_ON paintable = FALSE var/openingoverlaytype = /obj/effect/temp_visual/cult/door var/friendly = FALSE diff --git a/code/game/machinery/portable_tag_turret.dm b/code/game/machinery/portable_tag_turret.dm index 12bae1bbf5a..4b22315f387 100644 --- a/code/game/machinery/portable_tag_turret.dm +++ b/code/game/machinery/portable_tag_turret.dm @@ -6,6 +6,14 @@ // Reasonable defaults, in case someone manually spawns us var/lasercolor = "r" //Something to do with lasertag turrets, blame Sieve for not adding a comment. installation = /obj/item/gun/energy/laser/tag/red + targetting_is_configurable = FALSE + lethal_is_configurable = FALSE + shot_delay = 30 + iconholder = 1 + has_cover = FALSE + always_up = TRUE + raised = TRUE + req_access = list(ACCESS_MAINT_TUNNELS, ACCESS_THEATRE) /obj/machinery/porta_turret/tag/red @@ -18,43 +26,15 @@ icon_state = "[lasercolor]grey_target_prism" /obj/machinery/porta_turret/tag/weapon_setup(var/obj/item/gun/energy/E) - switch(E.type) - if(/obj/item/gun/energy/laser/tag/blue) - eprojectile = /obj/item/gun/energy/laser/tag/blue - lasercolor = "b" - req_access = list(ACCESS_MAINT_TUNNELS, ACCESS_THEATRE) - check_arrest = 0 - check_records = 0 - check_weapons = 1 - check_access = 0 - check_anomalies = 0 - shot_delay = 30 + return - if(/obj/item/gun/energy/laser/tag/red) - eprojectile = /obj/item/gun/energy/laser/tag/red - lasercolor = "r" - req_access = list(ACCESS_MAINT_TUNNELS, ACCESS_THEATRE) - check_arrest = 0 - check_records = 0 - check_weapons = 1 - check_access = 0 - check_anomalies = 0 - shot_delay = 30 - iconholder = 1 - -/obj/machinery/porta_turret/tag/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) - ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "turret_control.tmpl", "Turret Controls", 500, 300) - ui.open() - ui.set_auto_update(1) - -/obj/machinery/porta_turret/tag/ui_data(mob/user, ui_key = "main", datum/topic_state/state = GLOB.default_state) - var/data[0] - data["access"] = !isLocked(user) - data["locked"] = locked - data["enabled"] = enabled - data["is_lethal"] = 0 +/obj/machinery/porta_turret/tag/tgui_data(mob/user) + var/list/data = list( + "locked" = isLocked(user), // does the current user have access? + "on" = enabled, // is turret turned on? + "lethal" = FALSE, + "lethal_is_configurable" = lethal_is_configurable + ) return data /obj/machinery/porta_turret/tag/update_icon() diff --git a/code/game/machinery/portable_turret.dm b/code/game/machinery/portable_turret.dm index 79f1eb83e23..346008fce3f 100644 --- a/code/game/machinery/portable_turret.dm +++ b/code/game/machinery/portable_turret.dm @@ -7,18 +7,18 @@ name = "turret" icon = 'icons/obj/turrets.dmi' icon_state = "turretCover" - anchored = 1 - density = 0 + anchored = TRUE + density = FALSE use_power = IDLE_POWER_USE //this turret uses and requires power idle_power_usage = 50 //when inactive, this turret takes up constant 50 Equipment power active_power_usage = 300 //when active, this turret takes up constant 300 Equipment power power_channel = EQUIP //drains power from the EQUIPMENT channel armor = list(melee = 50, bullet = 30, laser = 30, energy = 30, bomb = 30, bio = 0, rad = 0, fire = 90, acid = 90) - var/raised = 0 //if the turret cover is "open" and the turret is raised - var/raising= 0 //if the turret is currently opening or closing its cover + var/raised = FALSE //if the turret cover is "open" and the turret is raised + var/raising= FALSE //if the turret is currently opening or closing its cover var/health = 80 //the turret's health - var/locked = 1 //if the turret's behaviour control access is locked - var/controllock = 0 //if the turret responds to control panels + var/locked = TRUE //if the turret's behaviour control access is locked + var/controllock = FALSE //if the turret responds to control panels. TRUE = does NOT respond var/installation = /obj/item/gun/energy/gun/turret //the type of weapon installed var/gun_charge = 0 //the charge of the gun inserted @@ -31,68 +31,49 @@ var/last_fired = 0 //1: if the turret is cooling down from a shot, 0: turret is ready to fire var/shot_delay = 15 //1.5 seconds between each shot - var/check_arrest = 1 //checks if the perp is set to arrest - var/check_records = 1 //checks if a security record exists at all - var/check_weapons = 0 //checks if it can shoot people that have a weapon they aren't authorized to have - var/check_access = 1 //if this is active, the turret shoots everything that does not meet the access requirements - var/check_anomalies = 1 //checks if it can shoot at unidentified lifeforms (ie xenos) - var/check_synth = 0 //if active, will shoot at anything not an AI or cyborg - var/ailock = 0 // AI cannot use this + var/targetting_is_configurable = TRUE // if false, you cannot change who this turret attacks via its UI + var/check_arrest = TRUE //checks if the perp is set to arrest + var/check_records = TRUE //checks if a security record exists at all + var/check_weapons = FALSE //checks if it can shoot people that have a weapon they aren't authorized to have + var/check_access = TRUE //if this is active, the turret shoots everything that does not meet the access requirements + var/check_anomalies = TRUE //checks if it can shoot at unidentified lifeforms (ie xenos) + var/check_synth = FALSE //if active, will shoot at anything not an AI or cyborg + var/check_borgs = FALSE //if TRUE, target all cyborgs. + var/ailock = FALSE // if TRUE, AI cannot use this - var/attacked = 0 //if set to 1, the turret gets pissed off and shoots at people nearby (unless they have sec access!) + var/attacked = FALSE //if set to 1, the turret gets pissed off and shoots at people nearby (unless they have sec access!) - var/enabled = 1 //determines if the turret is on - var/lethal = 0 //whether in lethal or stun mode - var/disabled = 0 + var/enabled = TRUE //determines if the turret is on + var/lethal = FALSE //whether in lethal or stun mode + var/lethal_is_configurable = TRUE // if false, its lethal setting cannot be changed + var/disabled = FALSE var/shot_sound //what sound should play when the turret fires var/eshot_sound //what sound should play when the emagged turret fires var/datum/effect_system/spark_spread/spark_system //the spark system, used for generating... sparks? - var/wrenching = 0 + var/wrenching = FALSE var/last_target //last target fired at, prevents turrets from erratically firing at all valid targets in range - var/screen = 0 // Screen 0: main control, screen 1: access levels - var/one_access = 0 // Determines if access control is set to req_one_access or req_access + var/one_access = FALSE // Determines if access control is set to req_one_access or req_access + var/region_min = REGION_GENERAL + var/region_max = REGION_COMMAND - var/syndicate = 0 //is the turret a syndicate turret? + var/syndicate = FALSE //is the turret a syndicate turret? var/faction = "" - var/emp_vulnerable = 1 // Can be empd + var/emp_vulnerable = TRUE // Can be empd var/scan_range = 7 - var/always_up = 0 //Will stay active - var/has_cover = 1 //Hides the cover + var/always_up = FALSE //Will stay active + var/has_cover = TRUE //Hides the cover -/obj/machinery/porta_turret/centcom - name = "Centcom Turret" - enabled = 0 - ailock = 1 - check_synth = 0 - check_access = 1 - check_arrest = 1 - check_records = 1 - check_weapons = 1 - check_anomalies = 1 - -/obj/machinery/porta_turret/centcom/pulse - name = "Pulse Turret" - health = 200 - enabled = 1 - lethal = 1 - req_access = list(ACCESS_CENT_COMMANDER) - installation = /obj/item/gun/energy/pulse/turret - -/obj/machinery/porta_turret/stationary - ailock = 1 - lethal = 1 - installation = /obj/item/gun/energy/laser /obj/machinery/porta_turret/Initialize(mapload) . = ..() if(req_access && req_access.len) req_access.Cut() req_one_access = list(ACCESS_SECURITY, ACCESS_HEADS) - one_access = 1 + one_access = TRUE //Sets up a spark system spark_system = new /datum/effect_system/spark_spread @@ -110,7 +91,7 @@ if(req_one_access && req_one_access.len) req_one_access.Cut() req_access = list(ACCESS_CENT_SPECOPS) - one_access = 0 + one_access = FALSE /obj/machinery/porta_turret/proc/setup() var/obj/item/gun/energy/E = new installation //All energy-based weapons are applicable @@ -185,152 +166,151 @@ GLOBAL_LIST_EMPTY(turret_icons) else icon_state = "turretCover" -/obj/machinery/porta_turret/proc/isLocked(mob/user) - if(ailock && (isrobot(user) || isAI(user))) - to_chat(user, "There seems to be a firewall preventing you from accessing this device.") - return 1 - - if(locked && !(isrobot(user) || isAI(user) || isobserver(user))) - to_chat(user, "Access denied.") - return 1 - - return 0 - -/obj/machinery/porta_turret/attack_ai(mob/user) - if(isLocked(user)) - return - - ui_interact(user) - -/obj/machinery/porta_turret/attack_ghost(mob/user) - ui_interact(user) - -/obj/machinery/porta_turret/attack_hand(mob/user) - if(isLocked(user)) - return - - ui_interact(user) - -/obj/machinery/porta_turret/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) - ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "turret_control.tmpl", "Turret Controls", 500, 320) - ui.open() - ui.set_auto_update(1) - -/obj/machinery/porta_turret/ui_data(mob/user, ui_key = "main", datum/topic_state/state = GLOB.default_state) - var/data[0] - data["access"] = !isLocked(user) - data["screen"] = screen - data["locked"] = locked - data["enabled"] = enabled - data["lethal_control"] = !syndicate ? 1 : 0 - data["lethal"] = lethal - - if(data["access"] && !syndicate) - var/settings[0] - settings[++settings.len] = list("category" = "Neutralize All Non-Synthetics", "setting" = "check_synth", "value" = check_synth) - settings[++settings.len] = list("category" = "Check Weapon Authorization", "setting" = "check_weapons", "value" = check_weapons) - settings[++settings.len] = list("category" = "Check Security Records", "setting" = "check_records", "value" = check_records) - settings[++settings.len] = list("category" = "Check Arrest Status", "setting" = "check_arrest", "value" = check_arrest) - settings[++settings.len] = list("category" = "Check Access Authorization", "setting" = "check_access", "value" = check_access) - settings[++settings.len] = list("category" = "Check Misc. Lifeforms", "setting" = "check_anomalies", "value" = check_anomalies) - data["settings"] = settings - - if(!syndicate) - data["one_access"] = one_access - var/accesses[0] - var/list/access_list = get_all_accesses() - for(var/access in access_list) - var/name = get_access_desc(access) - var/active - if(one_access) - active = (access in req_one_access) - else - active = (access in req_access) - accesses[++accesses.len] = list("name" = name, "active" = active, "number" = access) - data["accesses"] = accesses - return data - /obj/machinery/porta_turret/proc/HasController() var/area/A = get_area(src) return A && A.turret_controls.len > 0 -/obj/machinery/porta_turret/CanUseTopic(var/mob/user) +/obj/machinery/porta_turret/proc/access_is_configurable() + return targetting_is_configurable && !HasController() + +/obj/machinery/porta_turret/proc/isLocked(mob/user) if(HasController()) - to_chat(user, "Turrets can only be controlled using the assigned turret controller.") - return STATUS_CLOSE + return TRUE + if(isrobot(user) || isAI(user)) + if(ailock) + to_chat(user, "There seems to be a firewall preventing you from accessing this device.") + return TRUE + else + return FALSE + if(isobserver(user)) + if(user.can_admin_interact()) + return FALSE + else + return TRUE + if(locked) + return TRUE + return FALSE - if(isLocked(user)) - return STATUS_CLOSE +/obj/machinery/porta_turret/attack_ai(mob/user) + tgui_interact(user) - if(!anchored) - to_chat(usr, "\The [src] has to be secured first!") - return STATUS_CLOSE +/obj/machinery/porta_turret/attack_ghost(mob/user) + tgui_interact(user) - return ..() +/obj/machinery/porta_turret/attack_hand(mob/user) + tgui_interact(user) -/obj/machinery/porta_turret/Topic(href, href_list, var/nowindow = 0) - if(..()) - return 1 - - if(href_list["command"] && href_list["value"]) - var/value = text2num(href_list["value"]) - if(href_list["command"] == "enable") - enabled = value - else if(syndicate) - return 1 - else if(href_list["command"] == "screen") - screen = value - else if(href_list["command"] == "lethal") - lethal = value - else if(href_list["command"] == "check_synth") - check_synth = value - else if(href_list["command"] == "check_weapons") - check_weapons = value - else if(href_list["command"] == "check_records") - check_records = value - else if(href_list["command"] == "check_arrest") - check_arrest = value - else if(href_list["command"] == "check_access") - check_access = value - else if(href_list["command"] == "check_anomalies") - check_anomalies = value - - if(!syndicate) - if(href_list["one_access"]) - toggle_one_access(href_list["one_access"]) - - if(href_list["access"]) - toggle_access(href_list["access"]) - - return 1 - -/obj/machinery/porta_turret/proc/toggle_one_access(var/access) - one_access = text2num(access) - - if(one_access == 1) - req_one_access = req_access.Copy() - req_access.Cut() - else if(one_access == 0) - req_access = req_one_access.Copy() - req_one_access.Cut() - -/obj/machinery/porta_turret/proc/toggle_access(var/access) - var/required = text2num(access) - if(!(required in get_all_accesses())) +/obj/machinery/porta_turret/tgui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = TRUE, datum/tgui/master_ui = null, datum/tgui_state/state = GLOB.tgui_default_state) + if(HasController()) + to_chat(user, "[src] can only be controlled using the assigned turret controller.") return + if(!anchored) + to_chat(user, "[src] has to be secured first!") + return + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "PortableTurret", name, 500, access_is_configurable() ? 800 : 400) + ui.open() - if(one_access) - if((required in req_one_access)) - req_one_access -= required - else - req_one_access += required - else - if((required in req_access)) - req_access -= required - else - req_access += required +/obj/machinery/porta_turret/tgui_data(mob/user) + var/list/data = list( + "locked" = isLocked(user), // does the current user have access? + "on" = enabled, + "targetting_is_configurable" = targetting_is_configurable, // If false, targetting settings don't show up + "lethal" = lethal, + "lethal_is_configurable" = lethal_is_configurable, + "check_weapons" = check_weapons, + "neutralize_noaccess" = check_access, + "one_access" = one_access, + "selectedAccess" = one_access ? req_one_access : req_access, + "access_is_configurable" = access_is_configurable(), + "neutralize_norecord" = check_records, + "neutralize_criminals" = check_arrest, + "neutralize_all" = check_synth, + "neutralize_unidentified" = check_anomalies, + "neutralize_cyborgs" = check_borgs + ) + return data + +/obj/machinery/porta_turret/tgui_static_data(mob/user) + var/list/data = list() + data["regions"] = get_accesslist_static_data(region_min, region_max) + return data + +/obj/machinery/porta_turret/tgui_act(action, params) + if (..()) + return + if(isLocked(usr)) + return + . = TRUE + switch(action) + if("power") + enabled = !enabled + if("lethal") + if(lethal_is_configurable) + lethal = !lethal + if(targetting_is_configurable) + switch(action) + if("authweapon") + check_weapons = !check_weapons + if("authaccess") + check_access = !check_access + if("authnorecord") + check_records = !check_records + if("autharrest") + check_arrest = !check_arrest + if("authxeno") + check_anomalies = !check_anomalies + if("authsynth") + check_synth = !check_synth + if("authborgs") + check_borgs = !check_borgs + if("set") + var/access = text2num(params["access"]) + if(one_access) + if(!(access in req_one_access)) + req_one_access += access + else + req_one_access -= access + else + if(!(access in req_access)) + req_access += access + else + req_access -= access + if(access_is_configurable()) + switch(action) + if("grant_region") + var/region = text2num(params["region"]) + if(isnull(region)) + return + if(one_access) + req_one_access |= get_region_accesses(region) + else + req_access |= get_region_accesses(region) + if("deny_region") + var/region = text2num(params["region"]) + if(isnull(region)) + return + if(one_access) + req_one_access -= get_region_accesses(region) + else + req_access -= get_region_accesses(region) + if("clear_all") + if(one_access) + req_one_access = list() + else + req_access = list() + if("grant_all") + if(one_access) + req_one_access = get_all_accesses() + else + req_access = get_all_accesses() + if("one_access") + if(one_access) + req_one_access = list() + else + req_access = list() + one_access = !one_access /obj/machinery/porta_turret/power_change() if(powered() || !use_power) @@ -379,23 +359,25 @@ GLOBAL_LIST_EMPTY(turret_icons) "You begin [anchored ? "un" : ""]securing the turret." \ ) - wrenching = 1 + wrenching = TRUE if(do_after(user, 50 * I.toolspeed, target = src)) //This code handles moving the turret around. After all, it's a portable turret! if(!anchored) playsound(loc, I.usesound, 100, 1) - anchored = 1 + anchored = TRUE update_icon() to_chat(user, "You secure the exterior bolts on the turret.") else if(anchored) playsound(loc, I.usesound, 100, 1) - anchored = 0 + anchored = FALSE to_chat(user, "You unsecure the exterior bolts on the turret.") update_icon() - wrenching = 0 + wrenching = FALSE else if(istype(I, /obj/item/card/id) || istype(I, /obj/item/pda)) - if(allowed(user)) + if(HasController()) + to_chat(user, "Turrets regulated by a nearby turret controller are not unlockable.") + else if(allowed(user)) locked = !locked to_chat(user, "Controls are now [locked ? "locked" : "unlocked"].") updateUsrDialog() @@ -409,9 +391,9 @@ GLOBAL_LIST_EMPTY(turret_icons) playsound(src.loc, 'sound/weapons/smash.ogg', 60, 1) if(I.force * 0.5 > 1) //if the force of impact dealt at least 1 damage, the turret gets pissed off if(!attacked && !emagged) - attacked = 1 + attacked = TRUE spawn(60) - attacked = 0 + attacked = FALSE ..() @@ -445,12 +427,12 @@ GLOBAL_LIST_EMPTY(turret_icons) if(user) to_chat(user, "You short out [src]'s threat assessment circuits.") visible_message("[src] hums oddly...") - emagged = 1 + emagged = TRUE iconholder = 1 - controllock = 1 - enabled = 0 //turns off the turret temporarily + controllock = TRUE + enabled = FALSE //turns off the turret temporarily sleep(60) //6 seconds for the traitor to gtfo of the area before the turret decides to ruin his shit - enabled = 1 //turns it back on. The cover popUp() popDown() are automatically called in process(), no need to define it here + enabled = TRUE //turns it back on. The cover popUp() popDown() are automatically called in process(), no need to define it here /obj/machinery/porta_turret/take_damage(force) if(!raised && !raising) @@ -470,9 +452,9 @@ GLOBAL_LIST_EMPTY(turret_icons) if(enabled) if(!attacked && !emagged) - attacked = 1 + attacked = TRUE spawn(60) - attacked = 0 + attacked = FALSE ..() @@ -489,12 +471,12 @@ GLOBAL_LIST_EMPTY(turret_icons) check_access = prob(20) // check_access is a pretty big deal, so it's least likely to get turned on check_anomalies = prob(50) if(prob(5)) - emagged = 1 + emagged = TRUE enabled=0 - spawn(rand(60,600)) + spawn(rand(60, 600)) if(!enabled) - enabled=1 + enabled = TRUE ..() @@ -585,8 +567,8 @@ GLOBAL_LIST_EMPTY(turret_icons) if(get_turf(L) == get_turf(src)) return TURRET_NOT_TARGET - if(!emagged && !syndicate && (issilicon(L) || isbot(L))) // Don't target silica - return TURRET_NOT_TARGET + if(!emagged && !syndicate && (issilicon(L) || isbot(L))) + return (check_borgs && isrobot(L)) ? TURRET_PRIORITY_TARGET : TURRET_NOT_TARGET if(L.stat && !emagged) //if the perp is dead/dying, no need to bother really return TURRET_NOT_TARGET //move onto next potential victim! @@ -643,7 +625,7 @@ GLOBAL_LIST_EMPTY(turret_icons) return if(stat & BROKEN) return - set_raised_raising(raised, 1) + set_raised_raising(raised, TRUE) playsound(get_turf(src), 'sound/effects/turret/open.wav', 60, 1) update_icon() @@ -653,7 +635,7 @@ GLOBAL_LIST_EMPTY(turret_icons) sleep(10) qdel(flick_holder) - set_raised_raising(1, 0) + set_raised_raising(TRUE, FALSE) update_icon() /obj/machinery/porta_turret/proc/popDown() //pops the turret down @@ -664,7 +646,7 @@ GLOBAL_LIST_EMPTY(turret_icons) return if(stat & BROKEN) return - set_raised_raising(raised, 1) + set_raised_raising(raised, TRUE) playsound(get_turf(src), 'sound/effects/turret/open.wav', 60, 1) update_icon() @@ -674,7 +656,7 @@ GLOBAL_LIST_EMPTY(turret_icons) sleep(10) qdel(flick_holder) - set_raised_raising(0, 0) + set_raised_raising(FALSE, FALSE) update_icon() /obj/machinery/porta_turret/on_assess_perp(mob/living/carbon/human/perp) @@ -739,6 +721,27 @@ GLOBAL_LIST_EMPTY(turret_icons) A.throw_at(target, scan_range, 1) return A +/obj/machinery/porta_turret/centcom + name = "Centcom Turret" + enabled = FALSE + ailock = TRUE + check_synth = FALSE + check_access = TRUE + check_arrest = TRUE + check_records = TRUE + check_weapons = TRUE + check_anomalies = TRUE + region_max = REGION_CENTCOMM // Non-turretcontrolled turrets at CC can have their access customized to check for CC accesses. + +/obj/machinery/porta_turret/centcom/pulse + name = "Pulse Turret" + health = 200 + enabled = TRUE + lethal = TRUE + lethal_is_configurable = FALSE + req_access = list(ACCESS_CENT_COMMANDER) + installation = /obj/item/gun/energy/pulse/turret + /datum/turret_checks var/enabled var/lethal @@ -748,6 +751,7 @@ GLOBAL_LIST_EMPTY(turret_icons) var/check_arrest var/check_weapons var/check_anomalies + var/check_borgs var/ailock /obj/machinery/porta_turret/proc/setState(var/datum/turret_checks/TC) @@ -763,6 +767,7 @@ GLOBAL_LIST_EMPTY(turret_icons) check_arrest = TC.check_arrest check_weapons = TC.check_weapons check_anomalies = TC.check_anomalies + check_borgs = TC.check_borgs ailock = TC.ailock power_change() @@ -791,7 +796,7 @@ GLOBAL_LIST_EMPTY(turret_icons) if(istype(I, /obj/item/wrench) && !anchored) playsound(loc, I.usesound, 100, 1) to_chat(user, "You secure the external bolts.") - anchored = 1 + anchored = TRUE build_step = 1 return @@ -816,7 +821,7 @@ GLOBAL_LIST_EMPTY(turret_icons) else if(istype(I, /obj/item/wrench)) playsound(loc, I.usesound, 75, 1) to_chat(user, "You unfasten the external bolts.") - anchored = 0 + anchored = FALSE build_step = 0 return @@ -841,8 +846,10 @@ GLOBAL_LIST_EMPTY(turret_icons) gun_charge = E.cell.charge //the gun's charge is stored in gun_charge to_chat(user, "You add [I] to the turret.") - if(istype(installation, /obj/item/gun/energy/laser/tag/blue) || istype(installation, /obj/item/gun/energy/laser/tag/red)) - target_type = /obj/machinery/porta_turret/tag + if(istype(E, /obj/item/gun/energy/laser/tag/blue)) + target_type = /obj/machinery/porta_turret/tag/blue + else if(istype(E, /obj/item/gun/energy/laser/tag/red)) + target_type = /obj/machinery/porta_turret/tag/red else target_type = /obj/machinery/porta_turret @@ -934,7 +941,7 @@ GLOBAL_LIST_EMPTY(turret_icons) Turret.name = finish_name Turret.installation = installation Turret.gun_charge = gun_charge - Turret.enabled = 0 + Turret.enabled = FALSE Turret.setup() qdel(src) @@ -981,25 +988,27 @@ GLOBAL_LIST_EMPTY(turret_icons) var/icon_state_active = "syndieturret1" var/icon_state_destroyed = "syndieturret2" - syndicate = 1 + syndicate = TRUE installation = null - always_up = 1 + always_up = TRUE use_power = NO_POWER_USE - has_cover = 0 - raised = 1 + has_cover = FALSE + raised = TRUE scan_range = 9 faction = "syndicate" - emp_vulnerable = 0 + emp_vulnerable = FALSE - lethal = 1 - check_arrest = 0 - check_records = 0 - check_weapons = 0 - check_access = 0 - check_anomalies = 1 - check_synth = 1 - ailock = 1 + lethal = TRUE + lethal_is_configurable = FALSE + targetting_is_configurable = FALSE + check_arrest = FALSE + check_records = FALSE + check_weapons = FALSE + check_access = FALSE + check_anomalies = TRUE + check_synth = TRUE + ailock = TRUE var/area/syndicate_depot/core/depotarea /obj/machinery/porta_turret/syndicate/die() @@ -1018,7 +1027,7 @@ GLOBAL_LIST_EMPTY(turret_icons) if(req_one_access && req_one_access.len) req_one_access.Cut() req_access = list(ACCESS_SYNDICATE) - one_access = 0 + one_access = FALSE /obj/machinery/porta_turret/syndicate/update_icon() if(stat & BROKEN) diff --git a/code/game/machinery/slotmachine.dm b/code/game/machinery/slotmachine.dm index 5952108b6a9..1624ef4bb92 100644 --- a/code/game/machinery/slotmachine.dm +++ b/code/game/machinery/slotmachine.dm @@ -12,29 +12,26 @@ var/resultlvl = null /obj/machinery/slot_machine/attack_hand(mob/user as mob) + tgui_interact(user) + +/obj/machinery/slot_machine/tgui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, 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, "SlotMachine", name, 350, 200, master_ui, state) + ui.open() + +/obj/machinery/slot_machine/tgui_data(mob/user) + var/list/data = list() + // Get account account = user.get_worn_id_account() if(!account) if(istype(user.get_active_hand(), /obj/item/card/id)) account = get_card_account(user.get_active_hand()) else account = null - ui_interact(user) -/obj/machinery/slot_machine/wrench_act(mob/user, obj/item/I) - . = TRUE - if(!I.tool_use_check(user, 0)) - return - default_unfasten_wrench(user, I) -/obj/machinery/slot_machine/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) - ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "slotmachine.tmpl", name, 350, 200) - ui.open() - ui.set_auto_update(1) - -/obj/machinery/slot_machine/ui_data(mob/user, ui_key = "main", datum/topic_state/state = GLOB.default_state) - var/data[0] + // Send data data["working"] = working data["money"] = account ? account.money : null data["plays"] = plays @@ -42,22 +39,23 @@ data["resultlvl"] = resultlvl return data -/obj/machinery/slot_machine/Topic(href, href_list) +/obj/machinery/slot_machine/tgui_act(action, params) + if(..()) + return add_fingerprint(usr) - if(href_list["ops"]) - if(text2num(href_list["ops"])) // Play - if(working) - return - if(!account || account.money < 10) - return - if(!account.charge(10, null, "Bet", "Slot Machine", "Slot Machine")) - return - plays++ - working = 1 - icon_state = "slots-on" - playsound(src.loc, 'sound/machines/ding.ogg', 50, 1) - addtimer(CALLBACK(src, .proc/spin_slots, usr.name), 25) + if(action == "spin") + if(working) + return + if(!account || account.money < 10) + return + if(!account.charge(10, null, "Bet", "Slot Machine", "Slot Machine")) + return + plays++ + working = TRUE + icon_state = "slots-on" + playsound(src.loc, 'sound/machines/ding.ogg', 50, 1) + addtimer(CALLBACK(src, .proc/spin_slots, usr.name), 25) /obj/machinery/slot_machine/proc/spin_slots(userName) switch(rand(1,4050)) @@ -65,44 +63,45 @@ atom_say("JACKPOT! [userName] has won a MILLION CREDITS!") GLOB.event_announcement.Announce("Congratulations to [userName] on winning the Jackpot of ONE MILLION CREDITS!", "Jackpot Winner") result = "JACKPOT! You win one million credits!" - resultlvl = "highlight" + resultlvl = "teal" win_money(1000000, 'sound/goonstation/misc/airraid_loop.ogg') if(2 to 5) // .07% atom_say("Big Winner! [userName] has won a hundred thousand credits!") GLOB.event_announcement.Announce("Congratulations to [userName] on winning a hundred thousand credits!", "Big Winner") result = "Big Winner! You win a hundred thousand credits!" - resultlvl = "good" + resultlvl = "green" win_money(100000, 'sound/goonstation/misc/klaxon.ogg') if(6 to 50) // 1.08% atom_say("Big Winner! [userName] has won ten thousand credits!") result = "You win ten thousand credits!" - resultlvl = "good" + resultlvl = "green" win_money(10000, 'sound/goonstation/misc/klaxon.ogg') if(51 to 100) // 1.21% atom_say("Winner! [userName] has won a thousand credits!") result = "You win a thousand credits!" - resultlvl = "good" + resultlvl = "green" win_money(1000, 'sound/goonstation/misc/bell.ogg') if(101 to 200) // 2.44% atom_say("Winner! [userName] has won a hundred credits!") result = "You win a hundred credits!" - resultlvl = "good" + resultlvl = "green" win_money(100, 'sound/goonstation/misc/bell.ogg') if(201 to 300) // 2.44% atom_say("Winner! [userName] has won fifty credits!") result = "You win fifty credits!" - resultlvl = "good" + resultlvl = "green" win_money(50) if(301 to 1000) // 17.26% atom_say("Winner! [userName] has won ten credits!") result = "You win ten credits!" - resultlvl = "good" + resultlvl = "green" win_money(10) else // 75.31% - result = "No luck!" - resultlvl = "average" - working = 0 + result = "No luck!" + resultlvl = "orange" + working = FALSE icon_state = "slots-off" + SStgui.update_uis(src) // Push a UI update /obj/machinery/slot_machine/proc/win_money(amt, sound='sound/machines/ping.ogg') if(sound) @@ -110,3 +109,9 @@ if(!account) return account.credit(amt, "Slot Winnings", "Slot Machine", account.owner_name) + +/obj/machinery/slot_machine/wrench_act(mob/user, obj/item/I) + . = TRUE + if(!I.tool_use_check(user, 0)) + return + default_unfasten_wrench(user, I) diff --git a/code/game/machinery/turret_control.dm b/code/game/machinery/turret_control.dm index 9dda3b193bb..71cb58c6478 100644 --- a/code/game/machinery/turret_control.dm +++ b/code/game/machinery/turret_control.dm @@ -11,51 +11,56 @@ desc = "Used to control a room's automated defenses." icon = 'icons/obj/machines/turret_control.dmi' icon_state = "control_standby" - anchored = 1 - density = 0 + anchored = TRUE + density = FALSE resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF - var/enabled = 0 - var/lethal = 0 - var/locked = 1 + var/enabled = FALSE + var/lethal = FALSE + var/lethal_is_configurable = TRUE + var/locked = TRUE var/area/control_area //can be area name, path or nothing. - var/check_arrest = 1 //checks if the perp is set to arrest - var/check_records = 1 //checks if a security record exists at all - var/check_weapons = 0 //checks if it can shoot people that have a weapon they aren't authorized to have - var/check_access = 1 //if this is active, the turret shoots everything that does not meet the access requirements - var/check_anomalies = 1 //checks if it can shoot at unidentified lifeforms (ie xenos) - var/check_synth = 0 //if active, will shoot at anything not an AI or cyborg - var/ailock = 0 //Silicons cannot use this + var/targetting_is_configurable = TRUE // if false, you cannot change who this turret attacks via its UI + var/check_arrest = TRUE //checks if the perp is set to arrest + var/check_records = TRUE //checks if a security record exists at all + var/check_weapons = FALSE //checks if it can shoot people that have a weapon they aren't authorized to have + var/check_access = TRUE //if this is active, the turret shoots everything that does not meet the access requirements + var/check_anomalies = TRUE //checks if it can shoot at unidentified lifeforms (ie xenos) + var/check_synth = FALSE //if active, will shoot at anything not an AI or cyborg + var/check_borgs = FALSE //if TRUE, target all cyborgs. + var/ailock = FALSE //Silicons cannot use this - var/syndicate = 0 + var/syndicate = FALSE var/faction = "" // Turret controls can only access turrets that are in the same faction req_access = list(ACCESS_AI_UPLOAD) /obj/machinery/turretid/stun - enabled = 1 + enabled = TRUE icon_state = "control_stun" /obj/machinery/turretid/lethal - enabled = 1 - lethal = 1 + enabled = TRUE + lethal = TRUE icon_state = "control_kill" /obj/machinery/turretid/syndicate - enabled = 1 - lethal = 1 + enabled = TRUE + lethal = TRUE + lethal_is_configurable = FALSE + targetting_is_configurable = FALSE icon_state = "control_kill" - lethal = 1 - check_arrest = 0 - check_records = 0 - check_weapons = 0 - check_access = 0 - check_anomalies = 1 - check_synth = 1 - ailock = 1 + check_arrest = FALSE + check_records = FALSE + check_weapons = FALSE + check_access = FALSE + check_anomalies = TRUE + check_synth = TRUE + check_borgs = FALSE + ailock = TRUE - syndicate = 1 + syndicate = TRUE faction = "syndicate" req_access = list(ACCESS_SYNDICATE_LEADER) @@ -87,21 +92,23 @@ return /obj/machinery/turretid/proc/isLocked(mob/user) - if(ailock && (isrobot(user) || isAI(user))) - to_chat(user, "There seems to be a firewall preventing you from accessing this device.") - return 1 + if(isrobot(user) || isAI(user)) + if(ailock) + to_chat(user, "There seems to be a firewall preventing you from accessing this device.") + return TRUE + else + return FALSE - if(locked && !(isrobot(user) || isAI(user) || isobserver(user))) - to_chat(user, "Access denied.") - return 1 + if(isobserver(user)) + if(user.can_admin_interact()) + return FALSE + else + return TRUE - return 0 + if(locked) + return TRUE -/obj/machinery/turretid/CanUseTopic(mob/user) - if(isLocked(user)) - return STATUS_CLOSE - - return ..() + return FALSE /obj/machinery/turretid/attackby(obj/item/W, mob/user) if(stat & BROKEN) @@ -120,89 +127,82 @@ /obj/machinery/turretid/emag_act(user as mob) if(!emagged) to_chat(user, "You short out the turret controls' access analysis module.") - emagged = 1 - locked = 0 - ailock = 0 + emagged = TRUE + locked = FALSE + ailock = FALSE return /obj/machinery/turretid/attack_ai(mob/user as mob) - if(isLocked(user)) - return - - ui_interact(user) + tgui_interact(user) /obj/machinery/turretid/attack_ghost(mob/user as mob) - ui_interact(user) + tgui_interact(user) /obj/machinery/turretid/attack_hand(mob/user as mob) - if(isLocked(user)) - return + tgui_interact(user) - ui_interact(user) - -/obj/machinery/turretid/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) - ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open) +/obj/machinery/turretid/tgui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = TRUE, 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, "turret_control.tmpl", "Turret Controls", 500, 350) + ui = new(user, src, ui_key, "PortableTurret", name, 500, 400) ui.open() - ui.set_auto_update(1) - -/obj/machinery/turretid/ui_data(mob/user, ui_key = "main", datum/topic_state/state = GLOB.default_state) - var/data[0] - data["access"] = !isLocked(user) - data["locked"] = locked - data["enabled"] = enabled - data["lethal_control"] = !syndicate ? 1 : 0 - data["lethal"] = lethal - - if(data["access"] && !syndicate) - var/settings[0] - settings[++settings.len] = list("category" = "Neutralize All Non-Synthetics", "setting" = "check_synth", "value" = check_synth) - settings[++settings.len] = list("category" = "Check Weapon Authorization", "setting" = "check_weapons", "value" = check_weapons) - settings[++settings.len] = list("category" = "Check Security Records", "setting" = "check_records", "value" = check_records) - settings[++settings.len] = list("category" = "Check Arrest Status", "setting" = "check_arrest", "value" = check_arrest) - settings[++settings.len] = list("category" = "Check Access Authorization", "setting" = "check_access", "value" = check_access) - settings[++settings.len] = list("category" = "Check Misc. Lifeforms", "setting" = "check_anomalies", "value" = check_anomalies) - data["settings"] = settings +/obj/machinery/turretid/tgui_data(mob/user) + var/list/data = list( + "locked" = isLocked(user), // does the current user have access? + "on" = enabled, + "targetting_is_configurable" = targetting_is_configurable, // If false, targetting settings don't show up + "lethal" = lethal, + "lethal_is_configurable" = lethal_is_configurable, + "check_weapons" = check_weapons, + "neutralize_noaccess" = check_access, + "one_access" = FALSE, + "selectedAccess" = list(), + "access_is_configurable" = FALSE, + "neutralize_norecord" = check_records, + "neutralize_criminals" = check_arrest, + "neutralize_all" = check_synth, + "neutralize_unidentified" = check_anomalies, + "neutralize_cyborgs" = check_borgs + ) return data -/obj/machinery/turretid/Topic(href, href_list, var/nowindow = 0) - if(..()) - return 1 - +/obj/machinery/turretid/tgui_act(action, params) + if (..()) + return if(isLocked(usr)) - return 1 - - if(href_list["command"] && href_list["value"]) - var/value = text2num(href_list["value"]) - if(href_list["command"] == "enable") - enabled = value - else if(syndicate) - return 1 - else if(href_list["command"] == "lethal") - lethal = value - else if(href_list["command"] == "check_synth") - check_synth = value - else if(href_list["command"] == "check_weapons") - check_weapons = value - else if(href_list["command"] == "check_records") - check_records = value - else if(href_list["command"] == "check_arrest") - check_arrest = value - else if(href_list["command"] == "check_access") - check_access = value - else if(href_list["command"] == "check_anomalies") - check_anomalies = value - - updateTurrets() - return 1 + return + . = TRUE + switch(action) + if("power") + enabled = !enabled + if("lethal") + if(lethal_is_configurable) + lethal = !lethal + if(targetting_is_configurable) + switch(action) + if("authweapon") + check_weapons = !check_weapons + if("authaccess") + check_access = !check_access + if("authnorecord") + check_records = !check_records + if("autharrest") + check_arrest = !check_arrest + if("authxeno") + check_anomalies = !check_anomalies + if("authsynth") + check_synth = !check_synth + if("authborgs") + check_borgs = !check_borgs + updateTurrets() /obj/machinery/turretid/proc/updateTurrets() var/datum/turret_checks/TC = new TC.enabled = enabled TC.lethal = lethal TC.check_synth = check_synth + TC.check_borgs = check_borgs TC.check_access = check_access TC.check_records = check_records TC.check_arrest = check_arrest diff --git a/code/game/machinery/vending.dm b/code/game/machinery/vending.dm index d7a2213c5b7..fdca5c56194 100644 --- a/code/game/machinery/vending.dm +++ b/code/game/machinery/vending.dm @@ -1372,7 +1372,6 @@ /obj/item/clothing/glasses/gglasses = 1, /obj/item/clothing/shoes/jackboots = 1, /obj/item/clothing/under/schoolgirl = 1, - /obj/item/clothing/head/kitty = 1, /obj/item/clothing/under/blackskirt = 1, /obj/item/clothing/suit/toggle/owlwings = 1, /obj/item/clothing/under/owl = 1, diff --git a/code/game/objects/items/weapons/storage/backpack.dm b/code/game/objects/items/weapons/storage/backpack.dm index 26045731bd9..e7880fd0868 100644 --- a/code/game/objects/items/weapons/storage/backpack.dm +++ b/code/game/objects/items/weapons/storage/backpack.dm @@ -372,6 +372,15 @@ new /obj/item/ammo_box/magazine/m12g/buckshot(src) new /obj/item/ammo_box/magazine/m12g/dragon(src) +/obj/item/storage/backpack/duffel/syndie/ammo/shotgunXLmags + desc = "A large duffelbag, containing three types of extended drum magazines." + +/obj/item/storage/backpack/duffel/syndie/ammo/shotgunXLmags/New() + ..() + new /obj/item/ammo_box/magazine/m12g/XtrLrg(src) + new /obj/item/ammo_box/magazine/m12g/XtrLrg/buckshot(src) + new /obj/item/ammo_box/magazine/m12g/XtrLrg/dragon(src) + /obj/item/storage/backpack/duffel/mining_conscript/ name = "mining conscription kit" desc = "A kit containing everything a crewmember needs to support a shaft miner in the field." diff --git a/code/game/objects/structures/musician.dm b/code/game/objects/structures/musician.dm deleted file mode 100644 index c3d63c47862..00000000000 --- a/code/game/objects/structures/musician.dm +++ /dev/null @@ -1,341 +0,0 @@ - - -/datum/song - var/name = "Untitled" - var/list/lines = new() - var/tempo = 5 // delay between notes - - var/playing = 0 // if we're playing - var/help = 0 // if help is open - var/repeat = 0 // number of times remaining to repeat - var/max_repeat = 10 // maximum times we can repeat - - var/instrumentDir = "piano" // the folder with the sounds - var/instrumentExt = "ogg" // the file extension - var/obj/instrumentObj = null // the associated obj playing the sound - -/datum/song/New(dir, obj, ext = "ogg") - tempo = sanitize_tempo(tempo) - instrumentDir = dir - instrumentObj = obj - instrumentExt = ext - -/datum/song/Destroy() - instrumentObj = null - return ..() - -// note is a number from 1-7 for A-G -// acc is either "b", "n", or "#" -// oct is 1-8 (or 9 for C) -/datum/song/proc/playnote(note, acc as text, oct) - // handle accidental -> B<>C of E<>F - if(acc == "b" && (note == 3 || note == 6)) // C or F - if(note == 3) - oct-- - note-- - acc = "n" - else if(acc == "#" && (note == 2 || note == 5)) // B or E - if(note == 2) - oct++ - note++ - acc = "n" - else if(acc == "#" && (note == 7)) //G# - note = 1 - acc = "b" - else if(acc == "#") // mass convert all sharps to flats, octave jump already handled - acc = "b" - note++ - - // check octave, C is allowed to go to 9 - if(oct < 1 || (note == 3 ? oct > 9 : oct > 8)) - return - - // now generate name - var/soundfile = "sound/instruments/[instrumentDir]/[ascii2text(note+64)][acc][oct].[instrumentExt]" - soundfile = file(soundfile) - // make sure the note exists - if(!fexists(soundfile)) - return - // and play - var/turf/source = get_turf(instrumentObj) - var/sound/music_played = sound(soundfile) - for(var/A in hearers(15, source)) - var/mob/M = A - if(!M.client || !(M.client.prefs.sound & SOUND_INSTRUMENTS)) - continue - M.playsound_local(source, null, 100, falloff = 5, S = music_played) - -/datum/song/proc/shouldStopPlaying(mob/user) - if(instrumentObj) - //if(!user.canUseTopic(instrumentObj)) - //return 1 - return !instrumentObj.anchored // add special cases to stop in subclasses - else - return 1 - -/datum/song/proc/playsong(mob/user) - while(repeat >= 0) - var/cur_oct[7] - var/cur_acc[7] - for(var/i = 1 to 7) - cur_oct[i] = 3 - cur_acc[i] = "n" - - for(var/line in lines) - for(var/beat in splittext(lowertext(line), ",")) - var/list/notes = splittext(beat, "/") - for(var/note in splittext(notes[1], "-")) - if(!playing || shouldStopPlaying(user)) //If the instrument is playing, or special case - playing = 0 - return - if(length(note) == 0) - continue - var/cur_note = text2ascii(note) - 96 - if(cur_note < 1 || cur_note > 7) - continue - for(var/i=2 to length(note)) - var/ni = copytext(note,i,i+1) - if(!text2num(ni)) - if(ni == "#" || ni == "b" || ni == "n") - cur_acc[cur_note] = ni - else if(ni == "s") - cur_acc[cur_note] = "#" // so shift is never required - else - cur_oct[cur_note] = text2num(ni) - playnote(cur_note, cur_acc[cur_note], cur_oct[cur_note]) - if(notes.len >= 2 && text2num(notes[2])) - sleep(sanitize_tempo(tempo / text2num(notes[2]))) - else - sleep(tempo) - repeat-- - playing = 0 - repeat = 0 - -/datum/song/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) - if(!instrumentObj) - return - - ui = SSnanoui.try_update_ui(user, instrumentObj, ui_key, ui, force_open) - if(!ui) - ui = new(user, instrumentObj, ui_key, "song.tmpl", instrumentObj.name, 700, 500) - ui.open() - ui.set_auto_update(1) - -/datum/song/ui_data(mob/user, ui_key = "main", datum/topic_state/state = GLOB.default_state) - var/data[0] - - data["lines"] = lines - data["tempo"] = tempo - - data["playing"] = playing - data["help"] = help - data["repeat"] = repeat - data["maxRepeat"] = max_repeat - data["minTempo"] = world.tick_lag - data["maxTempo"] = 600 - - return data - -/datum/song/Topic(href, href_list) - if(!in_range(instrumentObj, usr) || (issilicon(usr) && instrumentObj.loc != usr) || !isliving(usr) || usr.incapacitated()) - usr << browse(null, "window=instrument") - usr.unset_machine() - return 1 - - instrumentObj.add_fingerprint(usr) - - if(href_list["newsong"]) - playing = 0 - lines = new() - tempo = sanitize_tempo(5) // default 120 BPM - name = "" - SSnanoui.update_uis(src) - - else if(href_list["import"]) - playing = 0 - var/t = "" - do - t = html_encode(input(usr, "Please paste the entire song, formatted:", text("[]", name), t) as message) - if(!in_range(instrumentObj, usr)) - return - - if(length(t) >= 12000) - var/cont = input(usr, "Your message is too long! Would you like to continue editing it?", "", "yes") in list("yes", "no") - if(cont == "no") - break - while(length(t) > 12000) - - //split into lines - spawn() - lines = splittext(t, "\n") - if(lines.len == 0) - return 1 - if(copytext(lines[1],1,6) == "BPM: ") - tempo = sanitize_tempo(600 / text2num(copytext(lines[1],6))) - lines.Cut(1,2) - else - tempo = sanitize_tempo(5) // default 120 BPM - if(lines.len > 200) - to_chat(usr, "Too many lines!") - lines.Cut(201) - var/linenum = 1 - for(var/l in lines) - if(length(l) > 200) - to_chat(usr, "Line [linenum] too long!") - lines.Remove(l) - else - linenum++ - SSnanoui.update_uis(src) - - else if(href_list["help"]) - help = !help - SSnanoui.update_uis(src) - - if(href_list["repeat"]) //Changing this from a toggle to a number of repeats to avoid infinite loops. - if(playing) - return //So that people cant keep adding to repeat. If the do it intentionally, it could result in the server crashing. - repeat += round(text2num(href_list["repeat"])) - if(repeat < 0) - repeat = 0 - if(repeat > max_repeat) - repeat = max_repeat - SSnanoui.update_uis(src) - - else if(href_list["tempo"]) - tempo = sanitize_tempo(tempo + text2num(href_list["tempo"]) * world.tick_lag) - SSnanoui.update_uis(src) - - else if(href_list["play"]) - if(playing) - return - playing = 1 - spawn() - playsong(usr) - SSnanoui.update_uis(src) - - else if(href_list["insertline"]) - var/num = round(text2num(href_list["insertline"])) - if(num < 1 || num > lines.len + 1) - return - - var/newline = html_encode(input("Enter your line: ", instrumentObj.name) as text|null) - if(!newline || !in_range(instrumentObj, usr)) - return - if(lines.len > 200) - return - if(length(newline) > 200) - newline = copytext(newline, 1, 200) - - lines.Insert(num, newline) - SSnanoui.update_uis(src) - - else if(href_list["deleteline"]) - var/num = round(text2num(href_list["deleteline"])) - if(num > lines.len || num < 1) - return - lines.Cut(num, num + 1) - SSnanoui.update_uis(src) - - else if(href_list["modifyline"]) - var/num = round(text2num(href_list["modifyline"])) - var/content = html_encode(input("Enter your line: ", instrumentObj.name, lines[num]) as text|null) - if(!content || !in_range(instrumentObj, usr)) - return - if(length(content) > 200) - content = copytext(content, 1, 200) - if(num > lines.len || num < 1) - return - lines[num] = content - SSnanoui.update_uis(src) - - else if(href_list["stop"]) - playing = 0 - SSnanoui.update_uis(src) - -/datum/song/proc/sanitize_tempo(new_tempo) - new_tempo = abs(new_tempo) - return max(round(new_tempo, world.tick_lag), world.tick_lag) - -// subclass for handheld instruments, like violin -/datum/song/handheld - -/datum/song/handheld/shouldStopPlaying() - if(instrumentObj) - return !isliving(instrumentObj.loc) - else - return 1 - - -////////////////////////////////////////////////////////////////////////// - - -/obj/structure/piano - name = "space minimoog" - icon = 'icons/obj/musician.dmi' - icon_state = "minimoog" - anchored = 1 - density = 1 - var/datum/song/song - - -/obj/structure/piano/New() - ..() - song = new("piano", src) - - if(prob(50)) - name = "space minimoog" - desc = "This is a minimoog, like a space piano, but more spacey!" - icon_state = "minimoog" - else - name = "space piano" - desc = "This is a space piano, like a regular piano, but always in tune! Even if the musician isn't." - icon_state = "piano" - -/obj/structure/piano/Destroy() - QDEL_NULL(song) - return ..() - -/obj/structure/piano/Initialize() - if(song) - song.tempo = song.sanitize_tempo(song.tempo) // tick_lag isn't set when the map is loaded - ..() - -/obj/structure/piano/attack_hand(mob/user as mob) - ui_interact(user) - -/obj/structure/piano/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) - if(!isliving(user) || user.incapacitated() || !anchored) - return - - song.ui_interact(user, ui_key, ui, force_open) - -/obj/structure/piano/ui_data(mob/user, ui_key = "main", datum/topic_state/state = GLOB.default_state) - return song.ui_data(user, ui_key, state) - -/obj/structure/piano/Topic(href, href_list) - song.Topic(href, href_list) - -/obj/structure/piano/wrench_act(mob/user, obj/item/I) - . = TRUE - if(!I.tool_use_check(user, 0)) - return - if(!anchored && !isinspace()) - WRENCH_ANCHOR_MESSAGE - if(!I.use_tool(src, user, 20, volume = I.tool_volume)) - return - user.visible_message( \ - "[user] tightens [src]'s casters.", \ - " You have tightened [src]'s casters. Now it can be played again.", \ - "You hear ratchet.") - anchored = TRUE - else if(anchored) - to_chat(user, " You begin to loosen [src]'s casters...") - if(!I.use_tool(src, user, 40, volume = I.tool_volume)) - return - user.visible_message( \ - "[user] loosens [src]'s casters.", \ - " You have loosened [src]. Now it can be pulled somewhere else.", \ - "You hear ratchet.") - anchored = FALSE - else - to_chat(user, "[src] needs to be bolted to the floor!") diff --git a/code/game/sound.dm b/code/game/sound.dm index 870ac5e6966..6882c27df3d 100644 --- a/code/game/sound.dm +++ b/code/game/sound.dm @@ -4,12 +4,13 @@ return var/turf/turf_source = get_turf(source) - if(!turf_source) return + if(!SSsounds.channel_list) // Not ready yet + return //allocate a channel if necessary now so its the same for everyone - channel = channel || open_sound_channel() + channel = channel || SSsounds.random_available_channel() // Looping through the player list has the added bonus of working for mobs inside containers var/sound/S = sound(get_sfx(soundin)) @@ -33,7 +34,7 @@ if(distance <= maxdistance) M.playsound_local(turf_source, soundin, vol, vary, frequency, falloff, channel, pressure_affected, S) -/mob/proc/playsound_local(turf/turf_source, soundin, vol as num, vary, frequency, falloff, channel = 0, pressure_affected = TRUE, sound/S) +/mob/proc/playsound_local(turf/turf_source, soundin, vol as num, vary, frequency, falloff, channel = 0, pressure_affected = TRUE, sound/S, distance_multiplier = 1) if(!client || !can_hear()) return @@ -41,7 +42,7 @@ S = sound(get_sfx(soundin)) S.wait = 0 //No queue - S.channel = channel || open_sound_channel() + S.channel = channel || SSsounds.random_available_channel() S.volume = vol if(vary) @@ -55,6 +56,7 @@ //sound volume falloff with distance var/distance = get_dist(T, turf_source) + distance *= distance_multiplier S.volume -= max(distance - world.view, 0) * 2 //multiplicative falloff to add on top of natural audio falloff. @@ -81,9 +83,9 @@ return //No sound var/dx = turf_source.x - T.x // Hearing from the right/left - S.x = dx + S.x = dx * distance_multiplier var/dz = turf_source.y - T.y // Hearing from infront/behind - S.z = dz + S.z = dz * distance_multiplier // The y value is for above your head, but there is no ceiling in 2d spessmens. S.y = 1 S.falloff = (falloff ? falloff : FALLOFF_SOUNDS) @@ -98,15 +100,14 @@ var/mob/M = m M.playsound_local(M, null, volume, vary, frequency, falloff, channel, pressure_affected, S) -/proc/open_sound_channel() - var/static/next_channel = 1 //loop through the available 1024 - (the ones we reserve) channels and pray that its not still being used - . = ++next_channel - if(next_channel > CHANNEL_HIGHEST_AVAILABLE) - next_channel = 1 - /mob/proc/stop_sound_channel(chan) SEND_SOUND(src, sound(null, repeat = 0, wait = 0, channel = chan)) +/mob/proc/set_sound_channel_volume(channel, volume) + var/sound/S = sound(null, FALSE, FALSE, channel, volume) + S.status = SOUND_UPDATE + SEND_SOUND(src, S) + /client/proc/playtitlemusic() if(!SSticker || !SSticker.login_music || config.disable_lobby_music) return diff --git a/code/modules/client/asset_cache.dm b/code/modules/client/asset_cache.dm index 8ca8bf0fbb1..8d8d83b4ae2 100644 --- a/code/modules/client/asset_cache.dm +++ b/code/modules/client/asset_cache.dm @@ -309,6 +309,20 @@ GLOBAL_LIST_EMPTY(asset_datums) /datum/asset/chem_master/send(client) send_asset_list(client, assets, verify) +//Cloning pod sprites for UIs +/datum/asset/cloning + var/assets = list() + var/verify = FALSE + +/datum/asset/cloning/register() + assets["pod_idle.gif"] = icon('icons/obj/cloning.dmi', "pod_idle") + assets["pod_cloning.gif"] = icon('icons/obj/cloning.dmi', "pod_cloning") + assets["pod_mess.gif"] = icon('icons/obj/cloning.dmi', "pod_mess") + for(var/asset_name in assets) + register_asset(asset_name, assets[asset_name]) + +/datum/asset/cloning/send(client) + send_asset_list(client, assets, verify) //Pipe sprites for UIs /datum/asset/rpd diff --git a/code/modules/client/preference/loadout/loadout_hat.dm b/code/modules/client/preference/loadout/loadout_hat.dm index 4993c8502ea..7e277677f94 100644 --- a/code/modules/client/preference/loadout/loadout_hat.dm +++ b/code/modules/client/preference/loadout/loadout_hat.dm @@ -166,7 +166,3 @@ /datum/gear/hat/flowerpin display_name = "hair flower" path = /obj/item/clothing/head/hairflower - -/datum/gear/hat/kitty - display_name = "kitty headband" - path = /obj/item/clothing/head/kitty diff --git a/code/modules/clothing/ears/ears.dm b/code/modules/clothing/ears/ears.dm index ea8f4198f50..d00751e2cfb 100644 --- a/code/modules/clothing/ears/ears.dm +++ b/code/modules/clothing/ears/ears.dm @@ -8,21 +8,3 @@ strip_delay = 15 put_on_delay = 25 resistance_flags = FLAMMABLE - -/obj/item/clothing/ears/headphones - name = "headphones" - desc = "Unce unce unce unce." - var/on = 0 - icon_state = "headphones0" - item_state = null - actions_types = list(/datum/action/item_action/toggle_headphones) - -/obj/item/clothing/ears/headphones/attack_self(mob/user) - on = !on - icon_state = "headphones[on]" - - for(var/X in actions) - var/datum/action/A = X - A.UpdateButtonIcon() - - user.update_inv_ears() diff --git a/code/modules/instruments/_instrument_data.dm b/code/modules/instruments/_instrument_data.dm new file mode 100644 index 00000000000..2fde3de59fb --- /dev/null +++ b/code/modules/instruments/_instrument_data.dm @@ -0,0 +1,113 @@ +/** + * Get all non admin_only instruments as a list of text ids. + */ +/proc/get_allowed_instrument_ids() + . = list() + for(var/id in SSinstruments.instrument_data) + var/datum/instrument/I = SSinstruments.instrument_data[id] + if(!I.admin_only) + . += I.id + +/** + * # Instrument Datums + * + * Instrument datums hold the data for any given instrument, as well as data on how to play it and what bounds there are to playing it. + * + * The datums themselves are kept in SSinstruments in a list by their unique ID. The reason it uses ID instead of typepath is to support the runtime creation of instruments. + * Since songs cache them while playing, there isn't realistic issues regarding performance from accessing. + */ +/datum/instrument + /// Name of the instrument + var/name = "Generic instrument" + /// Uniquely identifies this instrument so runtime changes are possible as opposed to paths. If this is unset, things will use path instead. + var/id + /// Category + var/category = "Unsorted" + /// Used for categorization subtypes + var/abstract_type = /datum/instrument + /// Write here however many samples, follow this syntax: "%note num%"='%sample file%' eg. "27"='synthesizer/e2.ogg'. Key must never be lower than 0 and higher than 127 + var/list/real_samples + /// assoc list key = /datum/instrument_key. do not fill this yourself! + var/list/samples + /// See __DEFINES/flags/instruments.dm + var/instrument_flags = NONE + /// For legacy instruments, the path to our notes + var/legacy_instrument_path + /// For legacy instruments, our file extension + var/legacy_instrument_ext + /// What songs are using us + var/list/datum/song/songs_using = list() + /// Don't touch this + var/static/HIGHEST_KEY = 127 + /// Don't touch this x2 + var/static/LOWEST_KEY = 0 + /// Oh no - For truly troll instruments. + var/admin_only = FALSE + /// Volume multiplier. Synthesized instruments are quite loud and I don't like to cut off potential detail via editing. (someone correct me if this isn't a thing) + var/volume_multiplier = 0.33 + +/datum/instrument/New() + if(isnull(id)) + id = "[type]" + +/datum/instrument/Destroy() + SSinstruments.instrument_data -= id + for(var/i in songs_using) + var/datum/song/S = i + S.set_instrument(null) + real_samples = null + samples = null + songs_using = null + return ..() + +/** + * Initializes the instrument, calculating its samples if necessary. + */ +/datum/instrument/proc/Initialize() + if(instrument_flags & (INSTRUMENT_LEGACY | INSTRUMENT_DO_NOT_AUTOSAMPLE)) + return + calculate_samples() + +/** + * Checks if this instrument is ready to play. + */ +/datum/instrument/proc/is_ready() + if(instrument_flags & INSTRUMENT_LEGACY) + return legacy_instrument_path && legacy_instrument_ext + else if(instrument_flags & INSTRUMENT_DO_NOT_AUTOSAMPLE) + return length(samples) + return length(samples) >= 128 + +/** + * For synthesized instruments, this is how the instrument generates the "keys" that a [/datum/song] uses to play notes. + * Calculating them on the fly would be unperformant, so we do it during init and keep it all cached in a list. + */ +/datum/instrument/proc/calculate_samples() + if(!length(real_samples)) + CRASH("No real samples defined for [id] [type] on calculate_samples() call.") + var/list/real_keys = list() + samples = list() + for(var/key in real_samples) + real_keys += text2num(key) + sortTim(real_keys, /proc/cmp_numeric_asc, associative = FALSE) + + for(var/i in 1 to (length(real_keys) - 1)) + var/from_key = real_keys[i] + var/to_key = real_keys[i + 1] + var/sample1 = real_samples[num2text(from_key)] + var/sample2 = real_samples[num2text(to_key)] + var/pivot = FLOOR((from_key + to_key) / 2, 1) //original code was a round but I replaced it because that's effectively a floor, thanks Baystation! who knows what was intended. + for(var/key in from_key to pivot) + samples[num2text(key)] = new /datum/instrument_key(sample1, key, key - from_key) + for(var/key in (pivot + 1) to to_key) + samples[num2text(key)] = new /datum/instrument_key(sample2, key, key - to_key) + + // Fill in 0 to first key and last key to 127 + var/first_key = real_keys[1] + var/last_key = real_keys[length(real_keys)] + var/first_sample = real_samples[num2text(first_key)] + var/last_sample = real_samples[num2text(last_key)] + for(var/key in LOWEST_KEY to (first_key - 1)) + samples[num2text(key)] = new /datum/instrument_key(first_sample, key, key - first_key) + for(var/key in last_key to HIGHEST_KEY) + samples[num2text(key)] = new /datum/instrument_key(last_sample, key, key - last_key) diff --git a/code/modules/instruments/_instrument_key.dm b/code/modules/instruments/_instrument_key.dm new file mode 100644 index 00000000000..5c7cc0ce372 --- /dev/null +++ b/code/modules/instruments/_instrument_key.dm @@ -0,0 +1,33 @@ +#define KEY_TWELTH (1/12) + +/** + * Instrument key datums contain everything needed to know how to play a specific + * note of an instrument.* + */ +/datum/instrument_key + /// The numerical key of what this is, from 1 to 127 on a standard piano keyboard. + var/key + /// The actual sample file that will be loaded when playing. + var/sample + /// The frequency to play the sample to get our desired note. + var/frequency + /// Deviation up/down from the pivot point that uses its sample. Used to calculate frequency. + var/deviation + +/datum/instrument_key/New(sample, key, deviation, frequency) + src.sample = sample + src.key = key + src.deviation = deviation + src.frequency = frequency + if(!frequency && deviation) + calculate() + +/** + * Calculates and stores our deviation. + */ +/datum/instrument_key/proc/calculate() + if(!deviation) + CRASH("Invalid calculate call: No deviation or sample in instrument_key") + frequency = 2 ** (KEY_TWELTH * deviation) + +#undef KEY_TWELTH diff --git a/code/modules/instruments/brass.dm b/code/modules/instruments/brass.dm new file mode 100644 index 00000000000..7f8f103831f --- /dev/null +++ b/code/modules/instruments/brass.dm @@ -0,0 +1,26 @@ +/datum/instrument/brass + name = "Generic brass instrument" + category = "Brass" + abstract_type = /datum/instrument/brass + +/datum/instrument/brass/crisis_section + name = "Crisis Brass Section" + id = "crbrass" + real_samples = list("36"='sound/instruments/synthesis_samples/brass/crisis_brass/c2.ogg', + "48"='sound/instruments/synthesis_samples/brass/crisis_brass/c3.ogg', + "60"='sound/instruments/synthesis_samples/brass/crisis_brass/c4.ogg', + "72"='sound/instruments/synthesis_samples/brass/crisis_brass/c5.ogg') + +/datum/instrument/brass/crisis_trombone + name = "Crisis Trombone" + id = "crtrombone" + real_samples = list("36"='sound/instruments/synthesis_samples/brass/crisis_trombone/c2.ogg', + "48"='sound/instruments/synthesis_samples/brass/crisis_trombone/c3.ogg', + "60"='sound/instruments/synthesis_samples/brass/crisis_trombone/c4.ogg', + "72"='sound/instruments/synthesis_samples/brass/crisis_trombone/c5.ogg') + +/datum/instrument/brass/crisis_trumpet + name = "Crisis Trumpet" + id = "crtrumpet" + real_samples = list("60"='sound/instruments/synthesis_samples/brass/crisis_trumpet/c4.ogg', + "72"='sound/instruments/synthesis_samples/brass/crisis_trumpet/c5.ogg') diff --git a/code/modules/instruments/chromatic_percussion.dm b/code/modules/instruments/chromatic_percussion.dm new file mode 100644 index 00000000000..cafa9e31edb --- /dev/null +++ b/code/modules/instruments/chromatic_percussion.dm @@ -0,0 +1,31 @@ +/datum/instrument/chromatic + name = "Generic chromatic percussion instrument" + category = "Chromatic percussion" + abstract_type = /datum/instrument/chromatic + +/datum/instrument/chromatic/vibraphone1 + name = "Crisis Vibraphone" + id = "crvibr" + real_samples = list("36"='sound/instruments/synthesis_samples/chromatic/vibraphone1/c2.ogg', + "48"='sound/instruments/synthesis_samples/chromatic/vibraphone1/c3.ogg', + "60"='sound/instruments/synthesis_samples/chromatic/vibraphone1/c4.ogg', + "72"='sound/instruments/synthesis_samples/chromatic/vibraphone1/c5.ogg') + +/datum/instrument/chromatic/musicbox1 + name = "SGM Music Box" + id = "sgmmbox" + real_samples = list("36"='sound/instruments/synthesis_samples/chromatic/sgmbox/c2.ogg', + "48"='sound/instruments/synthesis_samples/chromatic/sgmbox/c3.ogg', + "60"='sound/instruments/synthesis_samples/chromatic/sgmbox/c4.ogg', + "72"='sound/instruments/synthesis_samples/chromatic/sgmbox/c5.ogg') + +/datum/instrument/chromatic/fluid_celeste + name = "FluidR3 Celeste" + id = "r3celeste" + real_samples = list("36"='sound/instruments/synthesis_samples/chromatic/fluid_celeste/c2.ogg', + "48"='sound/instruments/synthesis_samples/chromatic/fluid_celeste/c3.ogg', + "60"='sound/instruments/synthesis_samples/chromatic/fluid_celeste/c4.ogg', + "72"='sound/instruments/synthesis_samples/chromatic/fluid_celeste/c5.ogg', + "84"='sound/instruments/synthesis_samples/chromatic/fluid_celeste/c6.ogg', + "96"='sound/instruments/synthesis_samples/chromatic/fluid_celeste/c7.ogg', + "108"='sound/instruments/synthesis_samples/chromatic/fluid_celeste/c8.ogg') diff --git a/code/modules/instruments/fun.dm b/code/modules/instruments/fun.dm new file mode 100644 index 00000000000..5a9b1292c42 --- /dev/null +++ b/code/modules/instruments/fun.dm @@ -0,0 +1,19 @@ +/datum/instrument/fun + name = "Generic Fun Instrument" + category = "Fun" + abstract_type = /datum/instrument/fun + +/datum/instrument/fun/honk + name = "!!HONK!!" + id = "honk" + real_samples = list("74"='sound/items/bikehorn.ogg') // Cluwne Heaven + +/datum/instrument/fun/signal + name = "Ping" + id = "ping" + real_samples = list("79"='sound/machines/ping.ogg') + +/datum/instrument/fun/chime + name = "Chime" + id = "chime" + real_samples = list("79"='sound/machines/chime.ogg') diff --git a/code/modules/instruments/guitar.dm b/code/modules/instruments/guitar.dm new file mode 100644 index 00000000000..be7cfbe467b --- /dev/null +++ b/code/modules/instruments/guitar.dm @@ -0,0 +1,36 @@ +/datum/instrument/guitar + name = "Generic guitar-like instrument" + category = "Guitar" + abstract_type = /datum/instrument/guitar + +/datum/instrument/guitar/steel_crisis + name = "Crisis Steel String Guitar" + id = "csteelgt" + real_samples = list("36"='sound/instruments/synthesis_samples/guitar/crisis_steel/c2.ogg', + "48"='sound/instruments/synthesis_samples/guitar/crisis_steel/c3.ogg', + "60"='sound/instruments/synthesis_samples/guitar/crisis_steel/c4.ogg', + "72"='sound/instruments/synthesis_samples/guitar/crisis_steel/c5.ogg') + +/datum/instrument/guitar/nylon_crisis + name = "Crisis Nylon String Guitar" + id = "cnylongt" + real_samples = list("36"='sound/instruments/synthesis_samples/guitar/crisis_nylon/c2.ogg', + "48"='sound/instruments/synthesis_samples/guitar/crisis_nylon/c3.ogg', + "60"='sound/instruments/synthesis_samples/guitar/crisis_nylon/c4.ogg', + "72"='sound/instruments/synthesis_samples/guitar/crisis_nylon/c5.ogg') + +/datum/instrument/guitar/clean_crisis + name = "Crisis Clean Guitar" + id = "ccleangt" + real_samples = list("36"='sound/instruments/synthesis_samples/guitar/crisis_clean/c2.ogg', + "48"='sound/instruments/synthesis_samples/guitar/crisis_clean/c3.ogg', + "60"='sound/instruments/synthesis_samples/guitar/crisis_clean/c4.ogg', + "72"='sound/instruments/synthesis_samples/guitar/crisis_clean/c5.ogg') + +/datum/instrument/guitar/muted_crisis + name = "Crisis Muted Guitar" + id = "cmutedgt" + real_samples = list("36"='sound/instruments/synthesis_samples/guitar/crisis_muted/c2.ogg', + "48"='sound/instruments/synthesis_samples/guitar/crisis_muted/c3.ogg', + "60"='sound/instruments/synthesis_samples/guitar/crisis_muted/c4.ogg', + "72"='sound/instruments/synthesis_samples/guitar/crisis_muted/c5.ogg') diff --git a/code/modules/instruments/hardcoded.dm b/code/modules/instruments/hardcoded.dm new file mode 100644 index 00000000000..5db7e8b3bb4 --- /dev/null +++ b/code/modules/instruments/hardcoded.dm @@ -0,0 +1,86 @@ +//THESE ARE HARDCODED INSTRUMENT SAMPLES. +//SONGS WILL BE AUTOMATICALLY SWITCHED TO LEGACY MODE IF THEY USE THIS KIND OF INSTRUMENT! +//I'd prefer these stayed. They sound different from the mechanical synthesis of synthed instruments, and I quite like them that way. It's not legacy, it's hardcoded, old style. - kevinz000 +/datum/instrument/hardcoded + abstract_type = /datum/instrument/hardcoded + category = "Non-Synthesized" + instrument_flags = INSTRUMENT_LEGACY + volume_multiplier = 1 //not as loud as synth'd + +/datum/instrument/hardcoded/accordion + name = "Accordion" + id = "accordion" + legacy_instrument_ext = "mid" + legacy_instrument_path = "accordion" + +/datum/instrument/hardcoded/bikehorn + name = "Bike Horn" + id = "bikehorn" + legacy_instrument_ext = "ogg" + legacy_instrument_path = "bikehorn" + +/datum/instrument/hardcoded/eguitar + name = "Electric Guitar" + id = "eguitar" + legacy_instrument_ext = "ogg" + legacy_instrument_path = "eguitar" + +/datum/instrument/hardcoded/glockenspiel + name = "Glockenspiel" + id = "glockenspiel" + legacy_instrument_ext = "mid" + legacy_instrument_path = "glockenspiel" + +/datum/instrument/hardcoded/guitar + name = "Guitar" + id = "guitar" + legacy_instrument_ext = "ogg" + legacy_instrument_path = "guitar" + +/datum/instrument/hardcoded/harmonica + name = "Harmonica" + id = "harmonica" + legacy_instrument_ext = "mid" + legacy_instrument_path = "harmonica" + +/datum/instrument/hardcoded/piano + name = "Piano" + id = "piano" + legacy_instrument_ext = "ogg" + legacy_instrument_path = "piano" + +/datum/instrument/hardcoded/recorder + name = "Recorder" + id = "recorder" + legacy_instrument_ext = "mid" + legacy_instrument_path = "recorder" + +/datum/instrument/hardcoded/saxophone + name = "Saxophone" + id = "saxophone" + legacy_instrument_ext = "mid" + legacy_instrument_path = "saxophone" + +/datum/instrument/hardcoded/trombone + name = "Trombone" + id = "trombone" + legacy_instrument_ext = "mid" + legacy_instrument_path = "trombone" + +/datum/instrument/hardcoded/violin + name = "Violin" + id = "violin" + legacy_instrument_ext = "mid" + legacy_instrument_path = "violin" + +/datum/instrument/hardcoded/xylophone + name = "Xylophone" + id = "xylophone" + legacy_instrument_ext = "mid" + legacy_instrument_path = "xylophone" + +/datum/instrument/hardcoded/banjo + name = "Banjo" + id = "banjo" + legacy_instrument_ext = "ogg" + legacy_instrument_path = "banjo" diff --git a/code/modules/instruments/objs/items/_instrument.dm b/code/modules/instruments/objs/items/_instrument.dm new file mode 100644 index 00000000000..936a9b4c85d --- /dev/null +++ b/code/modules/instruments/objs/items/_instrument.dm @@ -0,0 +1,53 @@ +//copy pasta of the space piano, don't hurt me -Pete +/obj/item/instrument + name = "generic instrument" + force = 10 + max_integrity = 100 + resistance_flags = FLAMMABLE + icon = 'icons/obj/musician.dmi' + lefthand_file = 'icons/mob/inhands/equipment/instruments_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/instruments_righthand.dmi' + /// Our song datum. + var/datum/song/handheld/song + /// Our allowed list of instrument ids. This is nulled on initialize. + var/list/allowed_instrument_ids + /// How far away our song datum can be heard. + var/instrument_range = 15 + +/obj/item/instrument/Initialize(mapload) + . = ..() + song = new(src, allowed_instrument_ids, instrument_range) + allowed_instrument_ids = null //We don't need this clogging memory after it's used. + +/obj/item/instrument/Destroy() + QDEL_NULL(song) + return ..() + +/obj/item/instrument/suicide_act(mob/user) + user.visible_message("[user] begins to play 'Gloomy Sunday'! It looks like [user.p_theyre()] trying to commit suicide!") + return BRUTELOSS + +/obj/item/instrument/attack_self(mob/user) + tgui_interact(user) + +/obj/item/instrument/tgui_data(mob/user) + return song.tgui_data(user) + +/obj/item/instrument/tgui_interact(mob/user) + if(!isliving(user) || user.incapacitated()) + return + song.tgui_interact(user) + +/obj/item/instrument/tgui_act(action, params) + if(..()) + return + return song.tgui_act(action, params) + +/** + * Whether the instrument should stop playing + * + * Arguments: + * * user - The user + */ +/obj/item/instrument/proc/should_stop_playing(mob/user) + return !(src in user) || !isliving(user) || user.incapacitated() diff --git a/code/modules/instruments/objs/items/headphones.dm b/code/modules/instruments/objs/items/headphones.dm new file mode 100644 index 00000000000..ef1a76bdc3b --- /dev/null +++ b/code/modules/instruments/objs/items/headphones.dm @@ -0,0 +1,80 @@ +/obj/item/clothing/ears/headphones + name = "headphones" + desc = "Unce unce unce unce." + icon_state = "headphones0" + item_state = "headphones0" + actions_types = list(/datum/action/item_action/change_headphones_song) + var/datum/song/headphones/song + +/obj/item/clothing/ears/headphones/Initialize(mapload) + . = ..() + song = new(src, "piano") // Piano is the default instrument but all instruments are allowed + song.instrument_range = 0 + song.allowed_instrument_ids = SSinstruments.synthesizer_instrument_ids + // To update the icon + RegisterSignal(src, COMSIG_SONG_START, .proc/start_playing) + RegisterSignal(src, COMSIG_SONG_END, .proc/stop_playing) + +/obj/item/clothing/ears/headphones/Destroy() + QDEL_NULL(song) + return ..() + +/obj/item/clothing/ears/headphones/attack_self(mob/user) + tgui_interact(user) + +/obj/item/clothing/ears/headphones/tgui_data(mob/user) + return song.tgui_data(user) + +/obj/item/clothing/ears/headphones/tgui_interact(mob/user) + if(should_stop_playing(user) || user.incapacitated()) + return + song.tgui_interact(user) + +/obj/item/clothing/ears/headphones/tgui_act(action, params) + if(..()) + return + return song.tgui_act(action, params) + +/obj/item/clothing/ears/headphones/update_icon() + var/mob/living/carbon/human/user = loc + if(istype(user)) + user.update_action_buttons_icon() + user.update_inv_ears() + ..() + +/obj/item/clothing/ears/headphones/item_action_slot_check(slot) + if(slot == slot_l_ear || slot == slot_r_ear) + return TRUE + +/** + * Called by a component signal when our song starts playing. + */ +/obj/item/clothing/ears/headphones/proc/start_playing() + icon_state = item_state = "headphones1" + update_icon() + +/** + * Called by a component signal when our song stops playing. + */ +/obj/item/clothing/ears/headphones/proc/stop_playing() + icon_state = item_state = "headphones0" + update_icon() + +/** + * Whether the headphone's song should stop playing + * + * Arguments: + * * user - The user + */ +/obj/item/clothing/ears/headphones/proc/should_stop_playing(mob/living/carbon/human/user) + return !(src in user) || !istype(user) || !((src == user.l_ear) || (src == user.r_ear)) + +// special subtype so it uses the correct item type +/datum/song/headphones + +/datum/song/headphones/should_stop_playing(mob/user) + . = ..() + if(.) + return TRUE + var/obj/item/clothing/ears/headphones/I = parent + return I.should_stop_playing(user) diff --git a/code/game/objects/items/devices/instruments.dm b/code/modules/instruments/objs/items/instruments.dm similarity index 54% rename from code/game/objects/items/devices/instruments.dm rename to code/modules/instruments/objs/items/instruments.dm index 53ead129836..2426de9a5c9 100644 --- a/code/game/objects/items/devices/instruments.dm +++ b/code/modules/instruments/objs/items/instruments.dm @@ -1,55 +1,10 @@ -//copy pasta of the space piano, don't hurt me -Pete -/obj/item/instrument - name = "generic instrument" - icon = 'icons/obj/musician.dmi' - lefthand_file = 'icons/mob/inhands/equipment/instruments_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/instruments_righthand.dmi' - resistance_flags = FLAMMABLE - max_integrity = 100 - var/datum/song/handheld/song - var/instrumentId = "generic" - var/instrumentExt = "mid" - -/obj/item/instrument/New() - song = new(instrumentId, src, instrumentExt) - ..() - -/obj/item/instrument/Destroy() - QDEL_NULL(song) - return ..() - -/obj/item/instrument/suicide_act(mob/user) - user.visible_message("[user] begins to play 'Gloomy Sunday'! It looks like [user.p_theyre()] trying to commit suicide!") - return BRUTELOSS - -/obj/item/instrument/Initialize(mapload) - song.tempo = song.sanitize_tempo(song.tempo) // tick_lag isn't set when the map is loaded - ..() - -/obj/item/instrument/attack_self(mob/user) - ui_interact(user) - -/obj/item/instrument/ui_interact(mob/user, ui_key = "main", datum/nanoui/ui = null, force_open = 1) - if(!isliving(user) || user.incapacitated()) - return - - song.ui_interact(user, ui_key, ui, force_open) - -/obj/item/instrument/ui_data(mob/user, ui_key = "main", datum/topic_state/state = GLOB.default_state) - return song.ui_data(user, ui_key, state) - -/obj/item/instrument/Topic(href, href_list) - song.Topic(href, href_list) - /obj/item/instrument/violin name = "space violin" desc = "A wooden musical instrument with four strings and a bow. \"The devil went down to space, he was looking for an assistant to grief.\"" icon_state = "violin" item_state = "violin" - instrumentExt = "ogg" - force = 10 hitsound = "swing_hit" - instrumentId = "violin" + allowed_instrument_ids = "violin" /obj/item/instrument/violin/golden name = "golden violin" @@ -63,87 +18,146 @@ desc = "An advanced electronic synthesizer that can be used as various instruments." icon_state = "synth" item_state = "synth" - instrumentId = "piano" - instrumentExt = "ogg" - var/static/list/insTypes = list("accordion" = "mid", "glockenspiel" = "mid", "guitar" = "ogg", "eguitar" = "ogg", "harmonica" = "mid", "piano" = "ogg", "recorder" = "mid", "saxophone" = "mid", "trombone" = "mid", "violin" = "ogg", "xylophone" = "mid") - actions_types = list(/datum/action/item_action/synthswitch) + allowed_instrument_ids = "piano" -/obj/item/instrument/piano_synth/proc/changeInstrument(name = "piano") - song.instrumentDir = name - song.instrumentExt = insTypes[name] +/obj/item/instrument/piano_synth/Initialize(mapload) + . = ..() + song.allowed_instrument_ids = SSinstruments.synthesizer_instrument_ids + +/obj/item/instrument/banjo + name = "banjo" + desc = "A 'Mura' brand banjo. It's pretty much just a drum with a neck and strings." + icon_state = "banjo" + item_state = "banjo" + attack_verb = list("scruggs-styled", "hum-diggitied", "shin-digged", "clawhammered") + hitsound = 'sound/weapons/banjoslap.ogg' + allowed_instrument_ids = "banjo" /obj/item/instrument/guitar name = "guitar" desc = "It's made of wood and has bronze strings." icon_state = "guitar" item_state = "guitar" - instrumentExt = "ogg" - force = 10 attack_verb = list("played metal on", "serenaded", "crashed", "smashed") - hitsound = 'sound/effects/guitarsmash.ogg' - instrumentId = "guitar" + hitsound = 'sound/weapons/guitarslam.ogg' + allowed_instrument_ids = "guitar" /obj/item/instrument/eguitar name = "electric guitar" desc = "Makes all your shredding needs possible." icon_state = "eguitar" item_state = "eguitar" - instrumentExt = "ogg" force = 12 attack_verb = list("played metal on", "shredded", "crashed", "smashed") hitsound = 'sound/weapons/stringsmash.ogg' - instrumentId = "eguitar" + allowed_instrument_ids = "eguitar" /obj/item/instrument/glockenspiel name = "glockenspiel" desc = "Smooth metal bars perfect for any marching band." icon_state = "glockenspiel" item_state = "glockenspiel" - instrumentId = "glockenspiel" + allowed_instrument_ids = "glockenspiel" /obj/item/instrument/accordion name = "accordion" desc = "Pun-Pun not included." icon_state = "accordion" item_state = "accordion" - instrumentId = "accordion" + allowed_instrument_ids = "accordion" + +/obj/item/instrument/trumpet + name = "trumpet" + desc = "To announce the arrival of the king!" + icon_state = "trumpet" + item_state = "trumpet" + allowed_instrument_ids = "trombone" + +/obj/item/instrument/trumpet/spectral + name = "spectral trumpet" + desc = "Things are about to get spooky!" + icon_state = "spectral_trumpet" + item_state = "spectral_trumpet" + force = 0 + attack_verb = list("played", "jazzed", "trumpeted", "mourned", "dooted", "spooked") + +/obj/item/instrument/trumpet/spectral/Initialize() + . = ..() + AddComponent(/datum/component/spooky) + +/obj/item/instrument/trumpet/spectral/attack(mob/living/carbon/C, mob/user) + playsound(src, 'sound/instruments/trombone/En4.mid', 100, 1, -1) + ..() /obj/item/instrument/saxophone name = "saxophone" desc = "This soothing sound will be sure to leave your audience in tears." icon_state = "saxophone" item_state = "saxophone" - instrumentId = "saxophone" + allowed_instrument_ids = "saxophone" + +/obj/item/instrument/saxophone/spectral + name = "spectral saxophone" + desc = "This spooky sound will be sure to leave mortals in bones." + icon_state = "saxophone" + item_state = "saxophone" + force = 0 + attack_verb = list("played", "jazzed", "saxxed", "mourned", "dooted", "spooked") + +/obj/item/instrument/saxophone/spectral/Initialize() + . = ..() + AddComponent(/datum/component/spooky) + +/obj/item/instrument/saxophone/spectral/attack(mob/living/carbon/C, mob/user) + playsound(src, 'sound/instruments/saxophone/En4.mid', 100,1,-1) + ..() /obj/item/instrument/trombone name = "trombone" desc = "How can any pool table ever hope to compete?" icon_state = "trombone" + allowed_instrument_ids = "trombone" item_state = "trombone" - instrumentId = "trombone" + +/obj/item/instrument/trombone/spectral + name = "spectral trombone" + desc = "A skeleton's favorite instrument. Apply directly on the mortals." + icon_state = "trombone" + item_state = "trombone" + force = 0 + attack_verb = list("played", "jazzed", "tromboned", "mourned", "dooted", "spooked") + +/obj/item/instrument/trombone/spectral/Initialize() + . = ..() + AddComponent(/datum/component/spooky) + +/obj/item/instrument/trombone/spectral/attack(mob/living/carbon/C, mob/user) + playsound (src, 'sound/instruments/trombone/Cn4.mid', 100,1,-1) + ..() /obj/item/instrument/recorder name = "recorder" desc = "Just like in school, playing ability and all." + force = 5 icon_state = "recorder" item_state = "recorder" - instrumentId = "recorder" + allowed_instrument_ids = "recorder" /obj/item/instrument/harmonica name = "harmonica" desc = "For when you get a bad case of the space blues." icon_state = "harmonica" item_state = "harmonica" - instrumentId = "harmonica" force = 5 w_class = WEIGHT_CLASS_SMALL + allowed_instrument_ids = "harmonica" /obj/item/instrument/xylophone name = "xylophone" - desc = "a percussion instrument with a bright tone." + desc = "A percussion instrument with a bright tone." icon_state = "xylophone" item_state = "xylophone" - instrumentId = "xylophone" + allowed_instrument_ids = "bikehorn" /obj/item/instrument/bikehorn name = "gilded bike horn" @@ -153,14 +167,14 @@ lefthand_file = 'icons/mob/inhands/items_lefthand.dmi' righthand_file = 'icons/mob/inhands/items_righthand.dmi' attack_verb = list("beautifully honks") - instrumentId = "bikehorn" - instrumentExt = "ogg" w_class = WEIGHT_CLASS_TINY force = 0 throw_speed = 3 throw_range = 7 hitsound = 'sound/items/bikehorn.ogg' + allowed_instrument_ids = "bikehorn" +// Crafting recipes /datum/crafting_recipe/violin name = "Violin" result = /obj/item/instrument/violin @@ -168,7 +182,7 @@ /obj/item/stack/cable_coil = 6, /obj/item/stack/tape_roll = 5) tools = list(TOOL_SCREWDRIVER, TOOL_WIRECUTTER) - time = 80 + time = 8 SECONDS category = CAT_MISC /datum/crafting_recipe/guitar @@ -178,7 +192,7 @@ /obj/item/stack/cable_coil = 6, /obj/item/stack/tape_roll = 5) tools = list(TOOL_SCREWDRIVER, TOOL_WIRECUTTER) - time = 80 + time = 8 SECONDS category = CAT_MISC /datum/crafting_recipe/eguitar @@ -188,5 +202,15 @@ /obj/item/stack/cable_coil = 6, /obj/item/stack/tape_roll = 5) tools = list(TOOL_SCREWDRIVER, TOOL_WIRECUTTER) - time = 80 + time = 8 SECONDS + category = CAT_MISC + +/datum/crafting_recipe/banjo + name = "Banjo" + result = /obj/item/instrument/banjo + reqs = list(/obj/item/stack/sheet/wood = 5, + /obj/item/stack/cable_coil = 6, + /obj/item/stack/tape_roll = 5) + tools = list(TOOL_SCREWDRIVER, TOOL_WIRECUTTER) + time = 8 SECONDS category = CAT_MISC diff --git a/code/modules/instruments/objs/structures/_musician.dm b/code/modules/instruments/objs/structures/_musician.dm new file mode 100644 index 00000000000..542859b4aef --- /dev/null +++ b/code/modules/instruments/objs/structures/_musician.dm @@ -0,0 +1,47 @@ +/obj/structure/musician + name = "Not A Piano" + desc = "Something broke!" + var/can_play_unanchored = FALSE + var/list/allowed_instrument_ids + var/datum/song/song + +/obj/structure/musician/Initialize(mapload) + . = ..() + song = new(src, allowed_instrument_ids) + allowed_instrument_ids = null + +/obj/structure/musician/Destroy() + QDEL_NULL(song) + return ..() + +/obj/structure/musician/attack_hand(mob/user) + add_fingerprint(user) + tgui_interact(user) + +/obj/structure/musician/tgui_data(mob/user) + return song.tgui_data(user) + +/obj/structure/musician/tgui_interact(mob/user) + song.tgui_interact(user) + +/obj/structure/musician/tgui_act(action, params) + if(..()) + return + return song.tgui_act(action, params) + +/obj/structure/musician/wrench_act(mob/living/user, obj/item/I) + default_unfasten_wrench(user, I, 40) + return TRUE + +/** + * Whether the instrument should stop playing + * + * Arguments: + * * user - The user + */ +/obj/structure/musician/proc/should_stop_playing(mob/user) + if(!(anchored || can_play_unanchored)) + return TRUE + if(!user) + return FALSE + return !CanUseTopic(user, GLOB.physical_state) diff --git a/code/modules/instruments/objs/structures/piano.dm b/code/modules/instruments/objs/structures/piano.dm new file mode 100644 index 00000000000..a68f07e53a3 --- /dev/null +++ b/code/modules/instruments/objs/structures/piano.dm @@ -0,0 +1,22 @@ +/obj/structure/piano + parent_type = /obj/structure/musician // TODO: Can't edit maps right now due to a freeze, remove and update path when it's done + name = "space minimoog" + icon = 'icons/obj/musician.dmi' + icon_state = "minimoog" + anchored = TRUE + density = TRUE + allowed_instrument_ids = "piano" + +/obj/structure/piano/unanchored + anchored = FALSE + +/obj/structure/piano/Initialize(mapload) + . = ..() + if(prob(50) && icon_state == initial(icon_state)) + name = "space minimoog" + desc = "This is a minimoog, like a space piano, but more spacey!" + icon_state = "minimoog" + else + name = "space piano" + desc = "This is a space piano, like a regular piano, but always in tune! Even if the musician isn't." + icon_state = "piano" diff --git a/code/modules/instruments/organ.dm b/code/modules/instruments/organ.dm new file mode 100644 index 00000000000..424f63d7104 --- /dev/null +++ b/code/modules/instruments/organ.dm @@ -0,0 +1,43 @@ +/datum/instrument/organ + name = "Generic organ" + category = "Organ" + abstract_type = /datum/instrument/organ + +/datum/instrument/organ/crisis_church + name = "Crisis Church Organ" + id = "crichugan" + real_samples = list("36"='sound/instruments/synthesis_samples/organ/crisis_church/c2.ogg', + "48"='sound/instruments/synthesis_samples/organ/crisis_church/c3.ogg', + "60"='sound/instruments/synthesis_samples/organ/crisis_church/c4.ogg', + "72"='sound/instruments/synthesis_samples/organ/crisis_church/c5.ogg') + +/datum/instrument/organ/crisis_hammond + name = "Crisis Hammond Organ" + id = "crihamgan" + real_samples = list("36"='sound/instruments/synthesis_samples/organ/crisis_hammond/c2.ogg', + "48"='sound/instruments/synthesis_samples/organ/crisis_hammond/c3.ogg', + "60"='sound/instruments/synthesis_samples/organ/crisis_hammond/c4.ogg', + "72"='sound/instruments/synthesis_samples/organ/crisis_hammond/c5.ogg') + +/datum/instrument/organ/crisis_accordian + name = "Crisis Accordion" + id = "crack" + real_samples = list("36"='sound/instruments/synthesis_samples/organ/crisis_accordian/c2.ogg', + "48"='sound/instruments/synthesis_samples/organ/crisis_accordian/c3.ogg', + "60"='sound/instruments/synthesis_samples/organ/crisis_accordian/c4.ogg', + "72"='sound/instruments/synthesis_samples/organ/crisis_accordian/c5.ogg') + +/datum/instrument/organ/crisis_harmonica + name = "Crisis Harmonica" + id = "crharmony" + real_samples = list("48"='sound/instruments/synthesis_samples/organ/crisis_harmonica/c3.ogg', + "60"='sound/instruments/synthesis_samples/organ/crisis_harmonica/c4.ogg', + "72"='sound/instruments/synthesis_samples/organ/crisis_harmonica/c5.ogg') + +/datum/instrument/organ/crisis_tango_accordian + name = "Crisis Tango Accordion" + id = "crtango" + real_samples = list("36"='sound/instruments/synthesis_samples/organ/crisis_tangaccordian/c2.ogg', + "48"='sound/instruments/synthesis_samples/organ/crisis_tangaccordian/c3.ogg', + "60"='sound/instruments/synthesis_samples/organ/crisis_tangaccordian/c4.ogg', + "72"='sound/instruments/synthesis_samples/organ/crisis_tangaccordian/c5.ogg') diff --git a/code/modules/instruments/piano.dm b/code/modules/instruments/piano.dm new file mode 100644 index 00000000000..fdd2f6e9382 --- /dev/null +++ b/code/modules/instruments/piano.dm @@ -0,0 +1,56 @@ +/datum/instrument/piano + name = "Generic piano" + category = "Piano" + abstract_type = /datum/instrument/piano + +/datum/instrument/piano/fluid_piano + name = "FluidR3 Grand Piano" + id = "r3grand" + real_samples = list("36"='sound/instruments/synthesis_samples/piano/fluid_piano/c2.ogg', + "48"='sound/instruments/synthesis_samples/piano/fluid_piano/c3.ogg', + "60"='sound/instruments/synthesis_samples/piano/fluid_piano/c4.ogg', + "72"='sound/instruments/synthesis_samples/piano/fluid_piano/c5.ogg', + "84"='sound/instruments/synthesis_samples/piano/fluid_piano/c6.ogg', + "96"='sound/instruments/synthesis_samples/piano/fluid_piano/c7.ogg', + "108"='sound/instruments/synthesis_samples/piano/fluid_piano/c8.ogg') + +/datum/instrument/piano/fluid_harpsichord + name = "FluidR3 Harpsichord" + id = "r3harpsi" + real_samples = list("36"='sound/instruments/synthesis_samples/piano/fluid_harpsi/c2.ogg', + "48"='sound/instruments/synthesis_samples/piano/fluid_harpsi/c3.ogg', + "60"='sound/instruments/synthesis_samples/piano/fluid_harpsi/c4.ogg', + "72"='sound/instruments/synthesis_samples/piano/fluid_harpsi/c5.ogg', + "84"='sound/instruments/synthesis_samples/piano/fluid_harpsi/c6.ogg', + "96"='sound/instruments/synthesis_samples/piano/fluid_harpsi/c7.ogg', + "108"='sound/instruments/synthesis_samples/piano/fluid_harpsi/c8.ogg') + +/datum/instrument/piano/crisis_harpsichord + name = "Crisis Harpsichord" + id = "crharpsi" + real_samples = list("36"='sound/instruments/synthesis_samples/piano/crisis_harpsichord/c2.ogg', + "48"='sound/instruments/synthesis_samples/piano/crisis_harpsichord/c3.ogg', + "60"='sound/instruments/synthesis_samples/piano/crisis_harpsichord/c4.ogg', + "72"='sound/instruments/synthesis_samples/piano/crisis_harpsichord/c5.ogg') + +/datum/instrument/piano/crisis_grandpiano_uni + name = "Crisis Grand Piano One" + id = "crgrand1" + real_samples = list("36"='sound/instruments/synthesis_samples/piano/crisis_grand_piano/c2.ogg', + "48"='sound/instruments/synthesis_samples/piano/crisis_grand_piano/c3.ogg', + "60"='sound/instruments/synthesis_samples/piano/crisis_grand_piano/c4.ogg', + "72"='sound/instruments/synthesis_samples/piano/crisis_grand_piano/c5.ogg', + "84"='sound/instruments/synthesis_samples/piano/crisis_grand_piano/c6.ogg', + "96"='sound/instruments/synthesis_samples/piano/crisis_grand_piano/c7.ogg', + "108"='sound/instruments/synthesis_samples/piano/crisis_grand_piano/c8.ogg') + +/datum/instrument/piano/crisis_brightpiano_uni + name = "Crisis Bright Piano One" + id = "crbright1" + real_samples = list("36"='sound/instruments/synthesis_samples/piano/crisis_bright_piano/c2.ogg', + "48"='sound/instruments/synthesis_samples/piano/crisis_bright_piano/c3.ogg', + "60"='sound/instruments/synthesis_samples/piano/crisis_bright_piano/c4.ogg', + "72"='sound/instruments/synthesis_samples/piano/crisis_bright_piano/c5.ogg', + "84"='sound/instruments/synthesis_samples/piano/crisis_bright_piano/c6.ogg', + "96"='sound/instruments/synthesis_samples/piano/crisis_bright_piano/c7.ogg', + "108"='sound/instruments/synthesis_samples/piano/crisis_bright_piano/c8.ogg') diff --git a/code/modules/instruments/songs/_song.dm b/code/modules/instruments/songs/_song.dm new file mode 100644 index 00000000000..142eab1fd2d --- /dev/null +++ b/code/modules/instruments/songs/_song.dm @@ -0,0 +1,410 @@ +#define MUSICIAN_HEARCHECK_MINDELAY 4 +#define MUSIC_MAXLINES 1000 +#define MUSIC_MAXLINECHARS 300 + +/** + * # Song datum + * + * These are the actual backend behind instruments. + * They attach to an atom and provide the editor + playback functionality. + */ +/datum/song + /// Name of the song + var/name = "Untitled" + + /// The atom we're attached to/playing from + var/atom/parent + + /// Our song lines + var/list/lines + + /// delay between notes in deciseconds + var/tempo = 5 + + /// How far we can be heard + var/instrument_range = 15 + + /// Are we currently playing? + var/playing = FALSE + + /// Are we currently editing? + var/editing = TRUE + /// Is the help screen open? + var/help = FALSE + + /// Repeats left + var/repeat = 0 + /// Maximum times we can repeat + var/max_repeats = 10 + + /// Our volume + var/volume = 75 + /// Max volume + var/max_volume = 75 + /// Min volume - This is so someone doesn't decide it's funny to set it to 0 and play invisible songs. + var/min_volume = 1 + + /// What instruments our built in picker can use. The picker won't show unless this is longer than one. + var/list/allowed_instrument_ids + + //////////// Cached instrument variables ///////////// + /// Instrument we are currently using + var/datum/instrument/using_instrument + /// Cached legacy ext for legacy instruments + var/cached_legacy_ext + /// Cached legacy dir for legacy instruments + var/cached_legacy_dir + /// Cached list of samples, referenced directly from the instrument for synthesized instruments + var/list/cached_samples + /// Are we operating in legacy mode (so if the instrument is a legacy instrument) + var/legacy = FALSE + ////////////////////////////////////////////////////// + + /////////////////// Playing variables //////////////// + /** + * Build by compile_chords() + * Must be rebuilt on instrument switch. + * Compilation happens when we start playing and is cleared after we finish playing. + * Format: list of chord lists, with chordlists having (key1, key2, key3, tempodiv) + */ + var/list/compiled_chords + /// Current section of a long chord we're on, so we don't need to make a billion chords, one for every unit ticklag. + var/elapsed_delay + /// Amount of delay to wait before playing the next chord + var/delay_by + /// Current chord we're on. + var/current_chord + /// Channel as text = current volume percentage but it's 0 to 100 instead of 0 to 1. + var/list/channels_playing + /// List of channels that aren't being used, as text. This is to prevent unnecessary freeing and reallocations from SSsounds/SSinstruments. + var/list/channels_idle + /// Person playing us + var/mob/user_playing + ////////////////////////////////////////////////////// + + /// Last world.time we checked for who can hear us + var/last_hearcheck = 0 + /// The list of mobs that can hear us + var/list/hearing_mobs + /// If this is enabled, some things won't be strictly cleared when they usually are (liked compiled_chords on play stop) + var/debug_mode = FALSE + /// Max sound channels to occupy + var/max_sound_channels = CHANNELS_PER_INSTRUMENT + /// Current channels, so we can save a length() call. + var/using_sound_channels = 0 + /// Last channel to play. text. + var/last_channel_played + /// Should we not decay our last played note? + var/full_sustain_held_note = TRUE + + /////////////////////// DO NOT TOUCH THESE /////////////////// + var/octave_min = INSTRUMENT_MIN_OCTAVE + var/octave_max = INSTRUMENT_MAX_OCTAVE + var/key_min = INSTRUMENT_MIN_KEY + var/key_max = INSTRUMENT_MAX_KEY + var/static/list/note_offset_lookup + var/static/list/accent_lookup + ////////////////////////////////////////////////////////////// + + ///////////// !!FUN!! - Only works in synthesized mode! ///////////////// + /// Note numbers to shift. + var/note_shift = 0 + var/note_shift_min = -100 + var/note_shift_max = 100 + var/can_noteshift = TRUE + /// The kind of sustain we're using + var/sustain_mode = SUSTAIN_LINEAR + /// When a note is considered dead if it is below this in volume + var/sustain_dropoff_volume = INSTRUMENT_MIN_SUSTAIN_DROPOFF + /// Total duration of linear sustain for 100 volume note to get to SUSTAIN_DROPOFF + var/sustain_linear_duration = 5 + /// Exponential sustain dropoff rate per decisecond + var/sustain_exponential_dropoff = 1.4 + ////////// DO NOT DIRECTLY SET THESE! + /// Do not directly set, use update_sustain() + var/cached_linear_dropoff = 10 + /// Do not directly set, use update_sustain() + var/cached_exponential_dropoff = 1.045 + ///////////////////////////////////////////////////////////////////////// + + var/static/list/valid_files[0] // Cache to avoid running fexists() every time + +/datum/song/New(atom/parent, list/instrument_ids, new_range) + SSinstruments.on_song_new(src) + lines = list() + tempo = sanitize_tempo(tempo) + src.parent = parent + if(instrument_ids) + allowed_instrument_ids = islist(instrument_ids) ? instrument_ids : list(instrument_ids) + if(length(allowed_instrument_ids)) + set_instrument(allowed_instrument_ids[1]) + hearing_mobs = list() + volume = clamp(volume, min_volume, max_volume) + channels_playing = list() + channels_idle = list() + if(!note_offset_lookup) + note_offset_lookup = list(9, 11, 0, 2, 4, 5, 7) + accent_lookup = list("b" = -1, "s" = 1, "#" = 1, "n" = 0) + update_sustain() + if(new_range) + instrument_range = new_range + +/datum/song/Destroy() + stop_playing() + SSinstruments.on_song_del(src) + lines = null + using_instrument = null + allowed_instrument_ids = null + parent = null + return ..() + +/** + * Checks and stores which mobs can hear us. Terminates sounds for mobs that leave our range. + */ +/datum/song/proc/do_hearcheck() + last_hearcheck = world.time + var/list/old = hearing_mobs.Copy() + hearing_mobs.len = 0 + var/turf/source = get_turf(parent) + for(var/mob/M in GLOB.player_list) + var/dist = get_dist(M, source) + if(dist > instrument_range) // Distance check + continue + if(!isInSight(M, source)) // Visibility check (direct line of sight) + continue + hearing_mobs[M] = dist + var/list/exited = old - hearing_mobs + for(var/i in exited) + terminate_sound_mob(i) + +/** + * Sets our instrument, caching anything necessary for faster accessing. Accepts an ID, typepath, or instantiated instrument datum. + */ +/datum/song/proc/set_instrument(datum/instrument/I) + terminate_all_sounds() + var/old_legacy + if(using_instrument) + using_instrument.songs_using -= src + old_legacy = (using_instrument.instrument_flags & INSTRUMENT_LEGACY) + using_instrument = null + cached_samples = null + cached_legacy_ext = null + cached_legacy_dir = null + legacy = null + if(istext(I) || ispath(I)) + I = SSinstruments.instrument_data[I] + if(istype(I)) + using_instrument = I + I.songs_using += src + var/instrument_legacy = (I.instrument_flags & INSTRUMENT_LEGACY) + if(instrument_legacy) + cached_legacy_ext = I.legacy_instrument_ext + cached_legacy_dir = I.legacy_instrument_path + legacy = TRUE + else + cached_samples = I.samples + legacy = FALSE + if(isnull(old_legacy) || (old_legacy != instrument_legacy)) + if(playing) + compile_chords() + +/** + * Attempts to start playing our song. + */ +/datum/song/proc/start_playing(mob/user) + if(playing) + return + if(!using_instrument?.is_ready()) + to_chat(user, "An error has occured with [src]. Please reset the instrument.") + return + compile_chords() + if(!length(compiled_chords)) + to_chat(user, "Song is empty.") + return + playing = TRUE + SStgui.update_uis(parent) + //we can not afford to runtime, since we are going to be doing sound channel reservations and if we runtime it means we have a channel allocation leak. + //wrap the rest of the stuff to ensure stop_playing() is called. + do_hearcheck() + SEND_SIGNAL(parent, COMSIG_SONG_START) + elapsed_delay = 0 + delay_by = 0 + current_chord = 1 + user_playing = user + START_PROCESSING(SSinstruments, src) + +/** + * Stops playing, terminating all sounds if in synthesized mode. Clears hearing_mobs. + */ +/datum/song/proc/stop_playing() + if(!playing) + return + playing = FALSE + if(!debug_mode) + compiled_chords = null + STOP_PROCESSING(SSinstruments, src) + SEND_SIGNAL(parent, COMSIG_SONG_END) + terminate_all_sounds(TRUE) + hearing_mobs.len = 0 + SStgui.update_uis(parent) + user_playing = null + +/** + * Processes our song. + */ +/datum/song/proc/process_song(wait) + if(!length(compiled_chords) || current_chord > length(compiled_chords) || should_stop_playing(user_playing)) + stop_playing() + return + var/list/chord = compiled_chords[current_chord] + if(++elapsed_delay >= delay_by) + play_chord(chord) + elapsed_delay = 0 + delay_by = tempodiv_to_delay(chord[length(chord)]) + current_chord++ + if(current_chord > length(compiled_chords)) + if(repeat) + repeat-- + current_chord = 1 + SStgui.update_uis(parent) + return + else + stop_playing() + return + +/** + * Converts a tempodiv to ticks to elapse before playing the next chord, taking into account our tempo. + */ +/datum/song/proc/tempodiv_to_delay(tempodiv) + tempodiv = tempodiv || world.tick_lag // Default to world.tick_lag in case someone's trying to be smart + return max(1, round((tempo / tempodiv) / world.tick_lag, 1)) + +/** + * Compiles chords. + */ +/datum/song/proc/compile_chords() + legacy ? compile_legacy() : compile_synthesized() + +/** + * Plays a chord. + */ +/datum/song/proc/play_chord(list/chord) + // last value is timing information + for(var/i in 1 to (length(chord) - 1)) + legacy? playkey_legacy(chord[i][1], chord[i][2], chord[i][3], user_playing) : playkey_synth(chord[i], user_playing) + +/** + * Checks if we should halt playback. + */ +/datum/song/proc/should_stop_playing(mob/user) + return QDELETED(parent) || !using_instrument || !playing + +/** + * Sanitizes tempo to a value that makes sense and fits the current world.tick_lag. + */ +/datum/song/proc/sanitize_tempo(new_tempo) + new_tempo = abs(new_tempo) + return clamp(round(new_tempo, world.tick_lag), world.tick_lag, 5 SECONDS) + +/** + * Gets our beats per minute based on our tempo. + */ +/datum/song/proc/get_bpm() + return 600 / tempo + +/** + * Sets our tempo from a beats-per-minute, sanitizing it to a valid number first. + */ +/datum/song/proc/set_bpm(bpm) + tempo = sanitize_tempo(600 / bpm) + +/datum/song/process(wait) + if(!playing) + return PROCESS_KILL + // it's expected this ticks at every world.tick_lag. if it lags, do not attempt to catch up. + process_song(world.tick_lag) + process_decay(world.tick_lag) + +/** + * Updates our cached linear/exponential falloff stuff, saving calculations down the line. + */ +/datum/song/proc/update_sustain() + // Exponential is easy + cached_exponential_dropoff = sustain_exponential_dropoff + // Linear, not so much, since it's a target duration from 100 volume rather than an exponential rate. + var/target_duration = sustain_linear_duration + var/volume_diff = max(0, 100 - sustain_dropoff_volume) + var/volume_decrease_per_decisecond = volume_diff / target_duration + cached_linear_dropoff = volume_decrease_per_decisecond + +/** + * Setter for setting output volume. + */ +/datum/song/proc/set_volume(volume) + src.volume = clamp(volume, max(0, min_volume), min(100, max_volume)) + update_sustain() + // We don't want to send the whole payload (song included) just for volume + var/datum/tgui/ui = SStgui.get_open_ui(usr, parent, "main") + if(ui) + ui.push_data(list("volume" = volume), force = TRUE) + +/** + * Setter for setting how low the volume has to get before a note is considered "dead" and dropped + */ +/datum/song/proc/set_dropoff_volume(volume, no_refresh = FALSE) + sustain_dropoff_volume = clamp(volume, INSTRUMENT_MIN_SUSTAIN_DROPOFF, 100) + update_sustain() + if(!no_refresh) + SStgui.update_uis(parent) + +/** + * Setter for setting exponential falloff factor. + */ +/datum/song/proc/set_exponential_drop_rate(drop, no_refresh = FALSE) + sustain_exponential_dropoff = clamp(drop, INSTRUMENT_EXP_FALLOFF_MIN, INSTRUMENT_EXP_FALLOFF_MAX) + update_sustain() + if(!no_refresh) + SStgui.update_uis(parent) + +/** + * Setter for setting linear falloff duration. + */ +/datum/song/proc/set_linear_falloff_duration(duration, no_refresh = FALSE) + sustain_linear_duration = clamp(duration, 0.1, INSTRUMENT_MAX_TOTAL_SUSTAIN) + update_sustain() + if(!no_refresh) + SStgui.update_uis(parent) + +/datum/song/vv_edit_var(var_name, var_value) + . = ..() + if(.) + switch(var_name) + if(NAMEOF(src, volume)) + set_volume(var_value) + if(NAMEOF(src, sustain_dropoff_volume)) + set_dropoff_volume(var_value) + if(NAMEOF(src, sustain_exponential_dropoff)) + set_exponential_drop_rate(var_value) + if(NAMEOF(src, sustain_linear_duration)) + set_linear_falloff_duration(var_value) + +// subtype for handheld instruments, like violin +/datum/song/handheld + +/datum/song/handheld/should_stop_playing(mob/user) + . = ..() + if(.) + return TRUE + var/obj/item/instrument/I = parent + return I.should_stop_playing(user) + +// subtype for stationary structures, like pianos +/datum/song/stationary + +/datum/song/stationary/should_stop_playing(mob/user) + . = ..() + if(.) + return TRUE + var/obj/structure/musician/M = parent + return M.should_stop_playing(user) + diff --git a/code/modules/instruments/songs/_song_ui.dm b/code/modules/instruments/songs/_song_ui.dm new file mode 100644 index 00000000000..cbd0f646f5c --- /dev/null +++ b/code/modules/instruments/songs/_song_ui.dm @@ -0,0 +1,179 @@ +/datum/song/tgui_data(mob/user) + var/data[0] + + // General + data["playing"] = playing + data["repeat"] = repeat + data["maxRepeats"] = max_repeats + data["editing"] = editing + data["lines"] = lines + data["tempo"] = tempo + data["minTempo"] = world.tick_lag + data["maxTempo"] = 5 SECONDS + data["tickLag"] = world.tick_lag + data["help"] = help + + // Status + var/list/allowed_instrument_names = list() + for(var/i in allowed_instrument_ids) + var/datum/instrument/I = SSinstruments.get_instrument(i) + if(I) + allowed_instrument_names += I.name + data["allowedInstrumentNames"] = allowed_instrument_names + data["instrumentLoaded"] = !isnull(using_instrument) + if(using_instrument) + data["instrument"] = using_instrument.name + data["canNoteShift"] = can_noteshift + if(can_noteshift) + data["noteShift"] = note_shift + data["noteShiftMin"] = note_shift_min + data["noteShiftMax"] = note_shift_max + data["sustainMode"] = sustain_mode + switch(sustain_mode) + if(SUSTAIN_LINEAR) + data["sustainLinearDuration"] = sustain_linear_duration + if(SUSTAIN_EXPONENTIAL) + data["sustainExponentialDropoff"] = sustain_exponential_dropoff + data["ready"] = using_instrument?.is_ready() + data["legacy"] = legacy + data["volume"] = volume + data["minVolume"] = min_volume + data["maxVolume"] = max_volume + data["sustainDropoffVolume"] = sustain_dropoff_volume + data["sustainHeldNote"] = full_sustain_held_note + + return data + +/datum/song/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, parent, ui_key, ui, force_open) + if(!ui) + ui = new(user, parent, ui_key, "Instrument", parent?.name || "Instrument", 700, 500) + ui.open() + ui.set_autoupdate(FALSE) // NO!!! Don't auto-update this!! + +/datum/song/tgui_act(action, params) + . = TRUE + switch(action) + if("newsong") + lines = new() + tempo = sanitize_tempo(5) // default 120 BPM + name = "" + if("import") + var/t = "" + do + t = html_encode(input(usr, "Please paste the entire song, formatted:", text("[]", name), t) as message) + if(!in_range(parent, usr)) + return + + if(length_char(t) >= MUSIC_MAXLINES * MUSIC_MAXLINECHARS) + var/cont = input(usr, "Your message is too long! Would you like to continue editing it?", "", "yes") in list("yes", "no") + if(cont == "no") + break + while(length_char(t) > MUSIC_MAXLINES * MUSIC_MAXLINECHARS) + parse_song(t) + return FALSE + if("help") + help = !help + if("edit") + editing = !editing + if("repeat") //Changing this from a toggle to a number of repeats to avoid infinite loops. + if(playing) + return //So that people cant keep adding to repeat. If the do it intentionally, it could result in the server crashing. + repeat = clamp(round(text2num(params["new"])), 0, max_repeats) + if("tempo") + tempo = sanitize_tempo(text2num(params["new"])) + if("play") + INVOKE_ASYNC(src, .proc/start_playing, usr) + if("newline") + var/newline = html_encode(input("Enter your line: ", parent.name) as text|null) + if(!newline || !in_range(parent, usr)) + return + if(length(lines) > MUSIC_MAXLINES) + return + if(length(newline) > MUSIC_MAXLINECHARS) + newline = copytext(newline, 1, MUSIC_MAXLINECHARS) + lines.Add(newline) + if("deleteline") + var/num = round(text2num(params["line"])) + if(num > length(lines) || num < 1) + return + lines.Cut(num, num + 1) + if("modifyline") + var/num = round(text2num(params["line"])) + var/content = stripped_input(usr, "Enter your line: ", parent.name, lines[num], MUSIC_MAXLINECHARS) + if(!content || !in_range(parent, usr)) + return + if(num > length(lines) || num < 1) + return + lines[num] = content + if("stop") + stop_playing() + if("setlinearfalloff") + set_linear_falloff_duration(round(text2num(params["new"]) * 10, world.tick_lag), TRUE) + if("setexpfalloff") + set_exponential_drop_rate(round(text2num(params["new"]), 0.00001), TRUE) + if("setvolume") + set_volume(round(text2num(params["new"]), 1)) + if("setdropoffvolume") + set_dropoff_volume(round(text2num(params["new"]), 0.01), TRUE) + if("switchinstrument") + if(!length(allowed_instrument_ids)) + return + else if(length(allowed_instrument_ids) == 1) + set_instrument(allowed_instrument_ids[1]) + return + var/choice = params["name"] + for(var/i in allowed_instrument_ids) + var/datum/instrument/I = SSinstruments.get_instrument(i) + if(I && I.name == choice) + set_instrument(I) + if("setnoteshift") + note_shift = clamp(round(text2num(params["new"])), note_shift_min, note_shift_max) + if("setsustainmode") + var/static/list/sustain_modes + if(!length(sustain_modes)) + sustain_modes = list("Linear" = SUSTAIN_LINEAR, "Exponential" = SUSTAIN_EXPONENTIAL) + var/choice = params["new"] + sustain_mode = sustain_modes[choice] || sustain_mode + if("togglesustainhold") + full_sustain_held_note = !full_sustain_held_note + if("reset") + var/default_instrument = allowed_instrument_ids[1] + if(using_instrument != SSinstruments.instrument_data[default_instrument]) + set_instrument(default_instrument) + note_shift = initial(note_shift) + sustain_mode = initial(sustain_mode) + set_linear_falloff_duration(initial(sustain_linear_duration), TRUE) + set_exponential_drop_rate(initial(sustain_exponential_dropoff), TRUE) + set_dropoff_volume(initial(sustain_dropoff_volume), TRUE) + else + return FALSE + parent.add_fingerprint(usr) + +/** + * Parses a song the user has input into lines and stores them. + */ +/datum/song/proc/parse_song(text) + set waitfor = FALSE + //split into lines + stop_playing() + lines = splittext(text, "\n") + if(length(lines)) + var/bpm_string = "BPM: " + if(findtext(lines[1], bpm_string, 1, length(bpm_string) + 1)) + var/divisor = text2num(copytext(lines[1], length(bpm_string) + 1)) || 120 // default + tempo = sanitize_tempo(600 / round(divisor, 1)) + lines.Cut(1, 2) + else + tempo = sanitize_tempo(5) // default 120 BPM + if(length(lines) > MUSIC_MAXLINES) + to_chat(usr, "Too many lines!") + lines.Cut(MUSIC_MAXLINES + 1) + var/linenum = 1 + for(var/l in lines) + if(length_char(l) > MUSIC_MAXLINECHARS) + to_chat(usr, "Line [linenum] too long!") + lines.Remove(l) + else + linenum++ + SStgui.update_uis(parent) diff --git a/code/modules/instruments/songs/play_legacy.dm b/code/modules/instruments/songs/play_legacy.dm new file mode 100644 index 00000000000..61d7964caf4 --- /dev/null +++ b/code/modules/instruments/songs/play_legacy.dm @@ -0,0 +1,95 @@ +/** + * Compiles our lines into "chords" with filenames for legacy playback. This makes there have to be a bit of lag at the beginning of the song, but repeats will not have to parse it again, and overall playback won't be impacted by as much lag. + */ +/datum/song/proc/compile_legacy() + if(!length(src.lines)) + return + var/list/lines = src.lines //cache for hyepr speed! + compiled_chords = list() + var/list/octaves = list(3, 3, 3, 3, 3, 3, 3) + var/list/accents = list("n", "n", "n", "n", "n", "n", "n") + for(var/line in lines) + var/list/chords = splittext(lowertext(line), ",") + for(var/chord in chords) + var/list/compiled_chord = list() + var/tempodiv = 1 + var/list/notes_tempodiv = splittext(chord, "/") + var/len = length(notes_tempodiv) + if(len >= 2) + tempodiv = text2num(notes_tempodiv[2]) + if(len) //some dunkass is going to do ,,,, to make 3 rests instead of ,/1 because there's no standardization so let's be prepared for that. + var/list/notes = splittext(notes_tempodiv[1], "-") + for(var/note in notes) + if(length(note) == 0) + continue + // 1-7, A-G + var/key = text2ascii(note) - 96 + if((key < 1) || (key > 7)) + continue + for(var/i in 2 to length(note)) + var/oct_acc = copytext(note, i, i + 1) + var/num = text2num(oct_acc) + if(!num) //it's an accidental + accents[key] = oct_acc //if they misspelled it/fucked up that's on them lmao, no safety checks. + else //octave + octaves[key] = clamp(num, octave_min, octave_max) + compiled_chord[++compiled_chord.len] = list(key, accents[key], octaves[key]) + compiled_chord += tempodiv //this goes last + if(length(compiled_chord)) + compiled_chords[++compiled_chords.len] = compiled_chord + +/** + * Proc to play a legacy note. Just plays the sound to hearing mobs (and does hearcheck if necessary), no fancy channel/sustain/management. + * + * Arguments: + * * note - number from 1-7 for A-G + * * acc - either "b", "n", or "#" + * * oct - 1-8 (or 9 for C) + */ +/datum/song/proc/playkey_legacy(note, acc as text, oct, mob/user) + // handle accidental -> B<>C of E<>F + if(acc == "b" && (note == 3 || note == 6)) // C or F + if(note == 3) + oct-- + note-- + acc = "n" + else if(acc == "#" && (note == 2 || note == 5)) // B or E + if(note == 2) + oct++ + note++ + acc = "n" + else if(acc == "#" && (note == 7)) //G# + note = 1 + acc = "b" + else if(acc == "#") // mass convert all sharps to flats, octave jump already handled + acc = "b" + note++ + + // check octave, C is allowed to go to 9 + if(oct < 1 || (note == 3 ? oct > 9 : oct > 8)) + return + + // now generate name + var/filename = "sound/instruments/[cached_legacy_dir]/[ascii2text(note + 64)][acc][oct].[cached_legacy_ext]" + var/soundfile = file(filename) + // make sure the note exists + var/cached_fexists = valid_files[filename] + if(!isnull(cached_fexists)) + if(!cached_fexists) + return + else if(!fexists(soundfile)) + valid_files[filename] = FALSE + return + else + valid_files[filename] = TRUE + // and play + var/turf/source = get_turf(parent) + if((world.time - MUSICIAN_HEARCHECK_MINDELAY) > last_hearcheck) + do_hearcheck() + var/sound/music_played = sound(soundfile) + for(var/i in hearing_mobs) + var/mob/M = i + if(!(M.client?.prefs?.sound & SOUND_INSTRUMENTS)) + continue + M.playsound_local(source, null, volume * using_instrument.volume_multiplier, falloff = 5, S = music_played) + // Could do environment and echo later but not for now diff --git a/code/modules/instruments/songs/play_synthesized.dm b/code/modules/instruments/songs/play_synthesized.dm new file mode 100644 index 00000000000..b2f16f6ab91 --- /dev/null +++ b/code/modules/instruments/songs/play_synthesized.dm @@ -0,0 +1,134 @@ +/** + * Compiles our lines into "chords" with numbers. This makes there have to be a bit of lag at the beginning of the song, but repeats will not have to parse it again, and overall playback won't be impacted by as much lag. + */ +/datum/song/proc/compile_synthesized() + if(!length(src.lines)) + return + var/list/lines = src.lines //cache for hyepr speed! + compiled_chords = list() + var/list/octaves = list(3, 3, 3, 3, 3, 3, 3) + var/list/accents = list("n", "n", "n", "n", "n", "n", "n") + for(var/line in lines) + var/list/chords = splittext(lowertext(line), ",") + for(var/chord in chords) + var/list/compiled_chord = list() + var/tempodiv = 1 + var/list/notes_tempodiv = splittext(chord, "/") + var/len = length(notes_tempodiv) + if(len >= 2) + tempodiv = text2num(notes_tempodiv[2]) + if(len) //some dunkass is going to do ,,,, to make 3 rests instead of ,/1 because there's no standardization so let's be prepared for that. + var/list/notes = splittext(notes_tempodiv[1], "-") + for(var/note in notes) + if(length(note) == 0) + continue + // 1-7, A-G + var/key = text2ascii(note) - 96 + if((key < 1) || (key > 7)) + continue + for(var/i in 2 to length(note)) + var/oct_acc = copytext(note, i, i + 1) + var/num = text2num(oct_acc) + if(!num) //it's an accidental + accents[key] = oct_acc //if they misspelled it/fucked up that's on them lmao, no safety checks. + else //octave + octaves[key] = clamp(num, octave_min, octave_max) + compiled_chord += clamp((note_offset_lookup[key] + octaves[key] * 12 + accent_lookup[accents[key]]), key_min, key_max) + compiled_chord += tempodiv //this goes last + if(length(compiled_chord)) + compiled_chords[++compiled_chords.len] = compiled_chord + +/** + * Plays a specific numerical key from our instrument to anyone who can hear us. + * Does a hearing check if enough time has passed. + */ +/datum/song/proc/playkey_synth(key, mob/user) + if(can_noteshift) + key = clamp(key + note_shift, key_min, key_max) + if((world.time - MUSICIAN_HEARCHECK_MINDELAY) > last_hearcheck) + do_hearcheck() + var/datum/instrument_key/K = using_instrument.samples[num2text(key)] //See how fucking easy it is to make a number text? You don't need a complicated 9 line proc! + //Should probably add channel limiters here at some point but I don't care right now. + var/channel = pop_channel() + if(isnull(channel)) + return FALSE + . = TRUE + var/sound/copy = sound(K.sample) + var/volume = src.volume * using_instrument.volume_multiplier + copy.frequency = K.frequency + copy.volume = volume + var/channel_text = num2text(channel) + channels_playing[channel_text] = 100 + last_channel_played = channel_text + for(var/i in hearing_mobs) + var/mob/M = i + if(!(M.client?.prefs?.sound & SOUND_INSTRUMENTS)) + continue + M.playsound_local(get_turf(parent), null, volume, FALSE, K.frequency, INSTRUMENT_DISTANCE_NO_FALLOFF, channel, null, copy, distance_multiplier = INSTRUMENT_DISTANCE_FALLOFF_BUFF) + // Could do environment and echo later but not for now + +/** + * Stops all sounds we are "responsible" for. Only works in synthesized mode. + */ +/datum/song/proc/terminate_all_sounds(clear_channels = TRUE) + for(var/i in hearing_mobs) + terminate_sound_mob(i) + if(clear_channels && channels_playing) + channels_playing.len = 0 + channels_idle.len = 0 + SSinstruments.current_instrument_channels -= using_sound_channels + using_sound_channels = 0 + SSsounds.free_datum_channels(src) + +/** + * Stops all sounds we are responsible for in a given person. Only works in synthesized mode. + */ +/datum/song/proc/terminate_sound_mob(mob/M) + for(var/channel in channels_playing) + M.stop_sound_channel(text2num(channel)) + +/** + * Pops a channel we have reserved so we don't have to release and re-request them from SSsounds every time we play a note. This is faster. + */ +/datum/song/proc/pop_channel() + if(length(channels_idle)) //just pop one off of here if we have one available + . = text2num(channels_idle[1]) + channels_idle.Cut(1, 2) + return + if(using_sound_channels >= max_sound_channels) + return + . = SSinstruments.reserve_instrument_channel(src) + if(!isnull(.)) + using_sound_channels++ + +/** + * Decays our channels and updates their volumes to mobs who can hear us. + * + * Arguments: + * * wait_ds - the deciseconds we should decay by. This is to compensate for any lag, as otherwise songs would get pretty nasty during high time dilation. + */ +/datum/song/proc/process_decay(wait_ds) + var/linear_dropoff = cached_linear_dropoff * wait_ds + var/exponential_dropoff = cached_exponential_dropoff ** wait_ds + for(var/channel in channels_playing) + if(full_sustain_held_note && (channel == last_channel_played)) + continue + var/current_volume = channels_playing[channel] + switch(sustain_mode) + if(SUSTAIN_LINEAR) + current_volume -= linear_dropoff + if(SUSTAIN_EXPONENTIAL) + current_volume /= exponential_dropoff + channels_playing[channel] = current_volume + var/dead = current_volume <= sustain_dropoff_volume + var/channelnumber = text2num(channel) + if(dead) + channels_playing -= channel + channels_idle += channel + for(var/i in hearing_mobs) + var/mob/M = i + M.stop_sound_channel(channelnumber) + else + for(var/i in hearing_mobs) + var/mob/M = i + M.set_sound_channel_volume(channelnumber, (current_volume * 0.01) * volume * using_instrument.volume_multiplier) diff --git a/code/modules/instruments/synth_tones.dm b/code/modules/instruments/synth_tones.dm new file mode 100644 index 00000000000..9ad9250f40d --- /dev/null +++ b/code/modules/instruments/synth_tones.dm @@ -0,0 +1,19 @@ +/datum/instrument/tones + name = "Ideal tone" + category = "Tones" + abstract_type = /datum/instrument/tones + +/datum/instrument/tones/square_wave + name = "Ideal square wave" + id = "square" + real_samples = list("81"='sound/instruments/synthesis_samples/tones/Square.ogg') + +/datum/instrument/tones/sine_wave + name = "Ideal sine wave" + id = "sine" + real_samples = list("81"='sound/instruments/synthesis_samples/tones/Sine.ogg') + +/datum/instrument/tones/saw_wave + name = "Ideal sawtooth wave" + id = "saw" + real_samples = list("81"='sound/instruments/synthesis_samples/tones/Sawtooth.ogg') diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm index b1b520b55ff..a1cb3a26270 100644 --- a/code/modules/mob/living/silicon/robot/robot.dm +++ b/code/modules/mob/living/silicon/robot/robot.dm @@ -74,7 +74,7 @@ GLOBAL_LIST_INIT(robot_verbs_default, list( var/viewalerts = 0 var/modtype = "Default" var/lower_mod = 0 - var/datum/effect_system/spark_spread/spark_system//So they can initialize sparks whenever/N + var/datum/effect_system/spark_spread/spark_system //So they can initialize sparks whenever/N var/jeton = 0 var/low_power_mode = 0 //whether the robot has no charge left. var/weapon_lock = 0 diff --git a/code/modules/mob/living/simple_animal/hostile/terror_spiders/purple.dm b/code/modules/mob/living/simple_animal/hostile/terror_spiders/purple.dm index 7e36b411bae..907e46b523e 100644 --- a/code/modules/mob/living/simple_animal/hostile/terror_spiders/purple.dm +++ b/code/modules/mob/living/simple_animal/hostile/terror_spiders/purple.dm @@ -47,7 +47,7 @@ /mob/living/simple_animal/hostile/poison/terror_spider/purple/Life(seconds, times_fired) . = ..() - if(.) // if mob is NOT dead + if(stat != DEAD) // Can't use if(.) for this due to the fact it can sometimes return FALSE even when mob is alive. if(!degenerate && spider_myqueen) if(dcheck_counter >= 10) dcheck_counter = 0 diff --git a/code/modules/mob/living/simple_animal/hostile/terror_spiders/queen.dm b/code/modules/mob/living/simple_animal/hostile/terror_spiders/queen.dm index 4b18626a687..737b6eba22a 100644 --- a/code/modules/mob/living/simple_animal/hostile/terror_spiders/queen.dm +++ b/code/modules/mob/living/simple_animal/hostile/terror_spiders/queen.dm @@ -66,7 +66,7 @@ /mob/living/simple_animal/hostile/poison/terror_spider/queen/Life(seconds, times_fired) . = ..() - if(.) // if mob is NOT dead + if(stat != DEAD) // Can't use if(.) for this due to the fact it can sometimes return FALSE even when mob is alive. if(ckey && canlay < 12 && hasnested) // max 12 eggs worth stored at any one time, realistically that's tons. if(world.time > (spider_lastspawn + spider_spawnfrequency)) if(eggslaid >= 20) diff --git a/code/modules/mob/living/simple_animal/hostile/terror_spiders/terror_spiders.dm b/code/modules/mob/living/simple_animal/hostile/terror_spiders/terror_spiders.dm index 520143d71d0..ab36c6ca313 100644 --- a/code/modules/mob/living/simple_animal/hostile/terror_spiders/terror_spiders.dm +++ b/code/modules/mob/living/simple_animal/hostile/terror_spiders/terror_spiders.dm @@ -298,7 +298,7 @@ GLOBAL_LIST_EMPTY(ts_spiderling_list) /mob/living/simple_animal/hostile/poison/terror_spider/Life(seconds, times_fired) . = ..() - if(!.) // if mob is dead + if(stat == DEAD) // Can't use if(.) for this due to the fact it can sometimes return FALSE even when mob is alive. if(prob(2)) // 2% chance every cycle to decompose visible_message("\The dead body of the [src] decomposes!") diff --git a/code/modules/power/apc.dm b/code/modules/power/apc.dm index cc3fccb2a01..4b64266f2b7 100644 --- a/code/modules/power/apc.dm +++ b/code/modules/power/apc.dm @@ -25,6 +25,11 @@ #define APC_UPDATE_ICON_COOLDOWN 200 // 20 seconds +// main_status var +#define APC_EXTERNAL_POWER_NOTCONNECTED 0 +#define APC_EXTERNAL_POWER_NOENERGY 1 +#define APC_EXTERNAL_POWER_GOOD 2 + // APC malf status #define APC_MALF_NOT_HACKED 1 #define APC_MALF_HACKED 2 // APC hacked by user, and user is in its core. @@ -75,7 +80,7 @@ var/lastused_equip = 0 var/lastused_environ = 0 var/lastused_total = 0 - var/main_status = 0 + var/main_status = APC_EXTERNAL_POWER_NOTCONNECTED powernet = 0 // set so that APCs aren't found as powernet nodes //Hackish, Horrible, was like this before I changed it :( var/malfhack = 0 //New var for my changes to AI malf. --NeoFite var/mob/living/silicon/ai/malfai = null //See above --NeoFite @@ -213,7 +218,7 @@ if(isarea(A)) area = A // no-op, keep the name - else if(isarea(A) && src.areastring == null) + else if(isarea(A) && !areastring) area = A name = "\improper [area.name] APC" else @@ -429,8 +434,8 @@ //attack with an item - open/close cover, insert cell, or (un)lock interface /obj/machinery/power/apc/attackby(obj/item/W, mob/living/user, params) - if(issilicon(user) && get_dist(src,user)>1) - return src.attack_hand(user) + if(issilicon(user) && get_dist(src, user) > 1) + return attack_hand(user) else if (istype(W, /obj/item/stock_parts/cell) && opened) // trying to put a cell inside if(cell) @@ -445,7 +450,7 @@ W.forceMove(src) cell = W user.visible_message(\ - "[user.name] has inserted the power cell to [src.name]!",\ + "[user.name] has inserted the power cell to [name]!",\ "You insert the power cell.") chargecount = 0 update_icon() @@ -474,7 +479,7 @@ return user.visible_message("[user.name] adds cables to the APC frame.", \ "You start adding cables to the APC frame...") - playsound(src.loc, 'sound/items/deconstruct.ogg', 50, 1) + playsound(loc, 'sound/items/deconstruct.ogg', 50, TRUE) if(do_after(user, 20, target = src)) if(C.get_amount() < 10 || !C) return @@ -499,10 +504,10 @@ user.visible_message("[user.name] inserts the power control board into [src].", \ "You start to insert the power control board into the frame...") - playsound(src.loc, 'sound/items/deconstruct.ogg', 50, 1) + playsound(loc, 'sound/items/deconstruct.ogg', 50, TRUE) if(do_after(user, 10, target = src)) - if(has_electronics==0) - has_electronics = 1 + if(!has_electronics) + has_electronics = TRUE locked = FALSE to_chat(user, "You place the power control board inside the frame.") qdel(W) @@ -549,11 +554,11 @@ return to_chat(user, "You are trying to remove the power control board..." ) if(I.use_tool(src, user, 50, volume = I.tool_volume)) - if(has_electronics==1) - has_electronics = 0 + if(has_electronics) + has_electronics = FALSE if(stat & BROKEN) user.visible_message(\ - "[user.name] has broken the power control board inside [src.name]!", + "[user.name] has broken the power control board inside [name]!", "You break the charred power control board and remove the remains.", "You hear a crack.") return @@ -561,19 +566,19 @@ else if(emagged) // We emag board, not APC's frame emagged = FALSE user.visible_message( - "[user.name] has discarded emaged power control board from [src.name]!", - "You discarded shorten board.") + "[user.name] has discarded the shorted power control board from [name]!", + "You discarded the shorted board.") return else if(malfhack) // AI hacks board, not APC's frame user.visible_message(\ - "[user.name] has discarded strangely programmed power control board from [src.name]!", - "You discarded strangely programmed board.") + "[user.name] has discarded strangely the programmed power control board from [name]!", + "You discarded the strangely programmed board.") malfai = null malfhack = 0 return else user.visible_message(\ - "[user.name] has removed the power control board from [src.name]!", + "[user.name] has removed the power control board from [name]!", "You remove the power control board.") new /obj/item/apc_electronics(loc) return @@ -711,36 +716,29 @@ // attack with hand - remove cell (if cover open) or interact with the APC /obj/machinery/power/apc/attack_hand(mob/user) -// if(!can_use(user)) This already gets called in interact() and in topic() -// return if(!user) return - src.add_fingerprint(user) + add_fingerprint(user) - if(usr == user && opened && (!issilicon(user))) + if(usr == user && opened && !issilicon(user)) if(cell) - if(issilicon(user)) - cell.loc=src.loc // Drop it, whoops. - else - user.put_in_hands(cell) + user.put_in_hands(cell) cell.add_fingerprint(user) cell.update_icon() - - src.cell = null - user.visible_message("[user.name] removes the power cell from [src.name]!", "You remove the power cell.") -// to_chat(user, "You remove the power cell.") - charging = 0 - src.update_icon() + cell = null + user.visible_message("[user.name] removes [cell] from [src]!", "You remove the [cell].") + charging = FALSE + update_icon() return if(stat & (BROKEN|MAINT)) return - src.interact(user) + interact(user) /obj/machinery/power/apc/attack_ghost(mob/user) if(panel_open) wires.Interact(user) - return ui_interact(user) + return tgui_interact(user) /obj/machinery/power/apc/interact(mob/user) if(!user) @@ -749,7 +747,7 @@ if(panel_open) wires.Interact(user) - return ui_interact(user) + return tgui_interact(user) /obj/machinery/power/apc/proc/get_malf_status(mob/living/silicon/ai/malf) @@ -770,22 +768,16 @@ else return APC_MALF_NOT_HACKED -/obj/machinery/power/apc/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) - if(!user) - 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) +/obj/machinery/power/apc/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) - // the ui does not exist, so we'll create a new one - ui = new(user, src, ui_key, "apc.tmpl", "[area.name] - APC", 510, issilicon(user) ? 535 : 460) + ui = new(user, src, ui_key, "APC", name, 510, 460, master_ui, state) ui.open() - // Auto update every Master Controller tick - ui.set_auto_update(1) -/obj/machinery/power/apc/ui_data(mob/user, ui_key = "main", datum/topic_state/state = GLOB.default_state) - var/data[0] +/obj/machinery/power/apc/tgui_data(mob/user) + var/list/data = list() data["locked"] = is_locked(user) + data["normallyLocked"] = locked data["isOperating"] = operating data["externalPower"] = main_status data["powerCellStatus"] = cell ? cell.percent() : null @@ -862,7 +854,7 @@ var/mob/living/silicon/ai/AI = user var/mob/living/silicon/robot/robot = user if( \ - src.aidisabled || \ + aidisabled || \ malfhack && istype(malfai) && \ ( \ (istype(AI) && (malfai!=AI && malfai != AI.parent)) || \ @@ -873,21 +865,21 @@ to_chat(user, "\The [src] has AI control disabled!") user << browse(null, "window=apc") user.unset_machine() - return 0 + return FALSE else - if((!in_range(src, user) || !istype(src.loc, /turf))) - return 0 + if((!in_range(src, user) || !istype(loc, /turf))) + return FALSE var/mob/living/carbon/human/H = user if(istype(H)) if(H.getBrainLoss() >= 60) for(var/mob/M in viewers(src, null)) to_chat(M, "[H] stares cluelessly at [src] and drools.") - return 0 + return FALSE else if(prob(H.getBrainLoss())) to_chat(user, "You momentarily forget how to use [src].") - return 0 - return 1 + return FALSE + return TRUE /obj/machinery/power/apc/proc/is_authenticated(mob/user as mob) if(user.can_admin_interact()) @@ -905,104 +897,59 @@ else return locked -/obj/machinery/power/apc/Topic(href, href_list, var/usingUI = 1) - if(..()) - return 1 - - if(!can_use(usr, 1)) - return 1 - - if(href_list["lock"]) - if(!is_authenticated(usr)) - return - - coverlocked = !coverlocked - - else if(href_list["breaker"]) - if(!is_authenticated(usr)) - return - - toggle_breaker() - - else if(href_list["toggle_nightshift"]) - if(!is_authenticated(usr)) - return - - if(last_nightshift_switch > world.time + 100) // don't spam... - to_chat(usr, "[src]'s night lighting circuit breaker is still cycling!") - return - last_nightshift_switch = world.time - set_nightshift(!nightshift_lights) - - else if(href_list["cmode"]) - if(!is_authenticated(usr)) - return - - chargemode = !chargemode - if(!chargemode) - charging = 0 - update_icon() - - else if(href_list["eqp"]) - if(!is_authenticated(usr)) - return - - var/val = text2num(href_list["eqp"]) - equipment = setsubsystem(val) - update_icon() - update() - - else if(href_list["lgt"]) - if(!is_authenticated(usr)) - return - - var/val = text2num(href_list["lgt"]) - lighting = setsubsystem(val) - update_icon() - update() - - else if(href_list["env"]) - if(!is_authenticated(usr)) - return - - var/val = text2num(href_list["env"]) - environ = setsubsystem(val) - update_icon() - update() - else if( href_list["close"] ) - SSnanoui.close_user_uis(usr, src) - - return 0 - else if(href_list["close2"]) - usr << browse(null, "window=apcwires") - - return 0 - - else if(href_list["overload"]) - if(issilicon(usr) && !aidisabled) - overload_lighting() - - else if(href_list["malfhack"]) - if(get_malf_status(usr)) - malfhack(usr) - - else if(href_list["occupyapc"]) - if(get_malf_status(usr)) - malfoccupy(usr) - - else if(href_list["deoccupyapc"]) - if(get_malf_status(usr)) - malfvacate() - - else if(href_list["toggleaccess"]) - if(istype(usr, /mob/living/silicon)) - if(emagged || aidisabled || (stat & (BROKEN|MAINT))) - to_chat(usr, "The APC does not respond to the command.") +/obj/machinery/power/apc/tgui_act(action, params) + if(..() || !can_use(usr, TRUE) || (locked && !usr.has_unlimited_silicon_privilege && (action != "toggle_nightshift") && !usr.can_admin_interact())) + return + . = TRUE + switch(action) + if("lock") + if(usr.has_unlimited_silicon_privilege) + if(emagged || stat & BROKEN) + to_chat(usr, "The APC does not respond to the command!") + return FALSE + else + locked = !locked + update_icon() else - locked = !locked + to_chat(usr, "Access Denied!") + return FALSE + if("cover") + coverlocked = !coverlocked + if("breaker") + toggle_breaker(usr) + if("toggle_nightshift") + if(last_nightshift_switch > world.time + 100) // don't spam... + to_chat(usr, "[src]'s night lighting circuit breaker is still cycling!") + return FALSE + last_nightshift_switch = world.time + set_nightshift(!nightshift_lights) + if("charge") + chargemode = !chargemode + if("channel") + if(params["eqp"]) + equipment = setsubsystem(text2num(params["eqp"])) update_icon() - - return 0 + update() + else if(params["lgt"]) + lighting = setsubsystem(text2num(params["lgt"])) + update_icon() + update() + else if(params["env"]) + environ = setsubsystem(text2num(params["env"])) + update_icon() + update() + if("overload") + if(usr.has_unlimited_silicon_privilege) + overload_lighting() + if("hack") + if(get_malf_status(usr)) + malfhack(usr) + if("occupy") + if(get_malf_status(usr)) + malfoccupy(usr) + if("deoccupy") + if(get_malf_status(usr)) + malfvacate() /obj/machinery/power/apc/proc/toggle_breaker() operating = !operating @@ -1033,7 +980,7 @@ if(!malf.can_shunt) to_chat(malf, "You cannot shunt!") return - if(!is_station_level(src.z)) + if(!is_station_level(z)) return occupier = new /mob/living/silicon/ai(src,malf.laws,null,1) occupier.adjustOxyLoss(malf.getOxyLoss()) @@ -1080,21 +1027,21 @@ /obj/machinery/power/apc/proc/ion_act() //intended to be exactly the same as an AI malf attack - if(!src.malfhack && is_station_level(src.z)) + if(!malfhack && is_station_level(z)) if(prob(3)) - src.locked = 1 - if(src.cell.charge > 0) - src.cell.charge = 0 + locked = TRUE + if(cell.charge > 0) + cell.charge = 0 cell.corrupt() - src.malfhack = 1 + malfhack = TRUE update_icon() var/datum/effect_system/smoke_spread/smoke = new - smoke.set_up(3, 0, src.loc) + smoke.set_up(3, 0, loc) smoke.attach(src) smoke.start() do_sparks(3, 1, src) for(var/mob/M in viewers(src)) - M.show_message("The [src.name] suddenly lets out a blast of smoke and some sparks!", 3, "You hear sizzling electronics.", 2) + M.show_message("The [name] suddenly lets out a blast of smoke and some sparks!", 3, "You hear sizzling electronics.", 2) /obj/machinery/power/apc/surplus() @@ -1137,12 +1084,12 @@ var/excess = surplus() - if(!src.avail()) - main_status = 0 + if(!avail()) + main_status = APC_EXTERNAL_POWER_NOTCONNECTED else if(excess < 0) - main_status = 1 + main_status = APC_EXTERNAL_POWER_NOENERGY else - main_status = 2 + main_status = APC_EXTERNAL_POWER_GOOD if(debug) log_debug("Status: [main_status] - Excess: [excess] - Last Equip: [lastused_equip] - Last Light: [lastused_light] - Longterm: [longtermpower]") @@ -1257,7 +1204,7 @@ if(shock_mobs.len) var/mob/living/L = pick(shock_mobs) L.electrocute_act(rand(5, 25), "electrical arc") - playsound(get_turf(L), 'sound/effects/eleczap.ogg', 75, 1) + playsound(get_turf(L), 'sound/effects/eleczap.ogg', 75, TRUE) Beam(L, icon_state = "lightning[rand(1, 12)]", icon = 'icons/effects/effects.dmi', time = 5) else // no cell, switch everything off @@ -1383,3 +1330,7 @@ updateDialog() #undef APC_UPDATE_ICON_COOLDOWN + +#undef APC_EXTERNAL_POWER_NOTCONNECTED +#undef APC_EXTERNAL_POWER_NOENERGY +#undef APC_EXTERNAL_POWER_GOOD diff --git a/code/modules/power/singularity/field_generator.dm b/code/modules/power/singularity/field_generator.dm index 046cf68a543..960340e90b6 100644 --- a/code/modules/power/singularity/field_generator.dm +++ b/code/modules/power/singularity/field_generator.dm @@ -310,16 +310,25 @@ field_generator power level display //This is here to help fight the "hurr durr, release singulo cos nobody will notice before the //singulo eats the evidence". It's not fool-proof but better than nothing. //I want to avoid using global variables. - spawn(1) - var/temp = 1 //stops spam - for(var/thing in GLOB.singularities) - var/obj/singularity/O = thing - if(O.last_warning && temp) - if((world.time - O.last_warning) > 50) //to stop message-spam - temp = 0 - message_admins("A singulo exists and a containment field has failed. Location: [get_area(src)] (JMP)",1) - investigate_log("has failed whilst a singulo exists.","singulo") - O.last_warning = world.time + INVOKE_ASYNC(src, .proc/admin_alert) + +/obj/machinery/field/generator/proc/admin_alert() + var/temp = TRUE //stops spam + for(var/thing in GLOB.singularities) + var/obj/singularity/O = thing + if(O.last_warning && temp && atoms_share_level(O, src)) + if((world.time - O.last_warning) > 50) //to stop message-spam + temp = FALSE + // To the person who asks "Hey affected, why are you using this massive operator when you can use AREACOORD?" Well, ill tell you + // get_area_name is fucking broken and uses a for(x in world) search + // It doesnt even work, is expensive, and returns 0 + // Im not refactoring one thing which could risk breaking all admin location logs + // Fight me + // [src ? "[get_location_name(src, TRUE)] [COORD(src)]" : "nonexistent location"] [ADMIN_JMP(src)] works much better and actually works at all + // Oh and yes, this exact comment was pasted from the exact same thing I did to tcomms code. Dont at me. + message_admins("A singularity exists and a containment field has failed on the same Z-Level. Singulo location: [O ? "[get_location_name(O, TRUE)] [COORD(O)]" : "nonexistent location"] [ADMIN_JMP(O)] | Field generator location: [src ? "[get_location_name(src, TRUE)] [COORD(src)]" : "nonexistent location"] [ADMIN_JMP(src)]") + investigate_log("has failed whilst a singulo exists.","singulo") + O.last_warning = world.time /obj/machinery/field/generator/shock_field(mob/living/user) if(fields.len) diff --git a/code/modules/projectiles/ammunition.dm b/code/modules/projectiles/ammunition.dm index 8536a0195e3..e6165103234 100644 --- a/code/modules/projectiles/ammunition.dm +++ b/code/modules/projectiles/ammunition.dm @@ -123,6 +123,7 @@ if(keep) stored_ammo.Insert(1,b) update_mat_value() + update_icon() return b /obj/item/ammo_box/proc/give_round(obj/item/ammo_casing/R, replace_spent = 0) diff --git a/code/modules/projectiles/ammunition/magazines.dm b/code/modules/projectiles/ammunition/magazines.dm index c59e5890331..5cc6a19b840 100644 --- a/code/modules/projectiles/ammunition/magazines.dm +++ b/code/modules/projectiles/ammunition/magazines.dm @@ -383,10 +383,7 @@ origin_tech = "combat=3;syndicate=1" caliber = "shotgun" max_ammo = 8 - -/obj/item/ammo_box/magazine/m12g/update_icon() - ..() - icon_state = "[initial(icon_state)]-[CEILING(ammo_count(0)/8, 1)*8]" + multiple_sprites = 2 /obj/item/ammo_box/magazine/m12g/buckshot name = "shotgun magazine (12g buckshot slugs)" @@ -414,6 +411,24 @@ icon_state = "m12gbc" ammo_type = /obj/item/ammo_casing/shotgun/breaching +/obj/item/ammo_box/magazine/m12g/XtrLrg + name = "\improper XL shotgun magazine (12g slugs)" + desc = "An extra large drum magazine." + icon_state = "m12gXlSl" + w_class = WEIGHT_CLASS_NORMAL + ammo_type = /obj/item/ammo_casing/shotgun + max_ammo = 16 + +/obj/item/ammo_box/magazine/m12g/XtrLrg/buckshot + name = "\improper XL shotgun magazine (12g buckshot)" + icon_state = "m12gXlBs" + ammo_type = /obj/item/ammo_casing/shotgun/buckshot + +/obj/item/ammo_box/magazine/m12g/XtrLrg/dragon + name = "\improper XL shotgun magazine (12g dragon's breath)" + icon_state = "m12gXlDb" + ammo_type = /obj/item/ammo_casing/shotgun/incendiary/dragonsbreath + /obj/item/ammo_box/magazine/toy name = "foam force META magazine" ammo_type = /obj/item/ammo_casing/caseless/foam_dart diff --git a/code/modules/projectiles/guns/projectile/automatic.dm b/code/modules/projectiles/guns/projectile/automatic.dm index 17a20f82d59..ea52a954a5b 100644 --- a/code/modules/projectiles/guns/projectile/automatic.dm +++ b/code/modules/projectiles/guns/projectile/automatic.dm @@ -275,13 +275,27 @@ if(magazine) overlays.Cut() overlays += "[magazine.icon_state]" - return + if(istype(magazine, /obj/item/ammo_box/magazine/m12g/XtrLrg)) + w_class = WEIGHT_CLASS_BULKY + else + w_class = WEIGHT_CLASS_NORMAL + else + w_class = WEIGHT_CLASS_NORMAL /obj/item/gun/projectile/automatic/shotgun/bulldog/update_icon() overlays.Cut() update_magazine() icon_state = "bulldog[chambered ? "" : "-e"]" +/obj/item/gun/projectile/automatic/shotgun/bulldog/attackby(var/obj/item/A as obj, mob/user as mob, params) + if(istype(A, /obj/item/ammo_box/magazine/m12g/XtrLrg)) + if(istype(loc, /obj/item/storage)) // To prevent inventory exploits + var/obj/item/storage/Strg = loc + if(Strg.max_w_class < WEIGHT_CLASS_BULKY) + to_chat(user, "You can't reload [src], with a XL mag, while it's in a normal bag.") + return + return ..() + /obj/item/gun/projectile/automatic/shotgun/bulldog/afterattack(atom/target as mob|obj|turf|area, mob/living/user as mob|obj, flag) ..() empty_alarm() diff --git a/code/modules/reagents/chemistry/machinery/chem_dispenser.dm b/code/modules/reagents/chemistry/machinery/chem_dispenser.dm index 91d5900610d..bacf2285c83 100644 --- a/code/modules/reagents/chemistry/machinery/chem_dispenser.dm +++ b/code/modules/reagents/chemistry/machinery/chem_dispenser.dm @@ -24,6 +24,7 @@ var/list/hacked_reagents = list("toxin") var/hack_message = "You disable the safety safeguards, enabling the \"Mad Scientist\" mode." var/unhack_message = "You re-enable the safety safeguards, enabling the \"NT Standard\" mode." + var/is_drink = FALSE /obj/machinery/chem_dispenser/get_cell() return cell @@ -120,7 +121,6 @@ var/usedpower = cell.give(recharge_amount) if(usedpower) use_power(15 * recharge_amount) - SSnanoui.update_uis(src) // update all UIs attached to src recharge_counter = 0 return recharge_counter++ @@ -131,7 +131,6 @@ else spawn(rand(0, 15)) stat |= NOPOWER - SSnanoui.update_uis(src) // update all UIs attached to src /obj/machinery/chem_dispenser/ex_act(severity) if(severity < 3) @@ -145,19 +144,17 @@ beaker = null overlays.Cut() -/obj/machinery/chem_dispenser/ui_interact(mob/user, ui_key = "main", datum/nanoui/ui = null, force_open = 1) +/obj/machinery/chem_dispenser/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) // 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, "chem_dispenser.tmpl", ui_title, 390, 655) - // open the new ui window + ui = new(user, src, ui_key, "ChemDispenser", ui_title, 390, 655) ui.open() -/obj/machinery/chem_dispenser/ui_data(mob/user, ui_key = "main", datum/topic_state/state = GLOB.default_state) +/obj/machinery/chem_dispenser/tgui_data(mob/user) var/data[0] + data["glass"] = is_drink data["amount"] = amount data["energy"] = cell.charge ? cell.charge * powerefficiency : "0" //To prevent NaN in the UI. data["maxEnergy"] = cell.maxcharge * powerefficiency @@ -187,63 +184,59 @@ return data -/obj/machinery/chem_dispenser/Topic(href, href_list) +/obj/machinery/chem_dispenser/tgui_act(actions, params) if(..()) - return TRUE + return + if(stat & (NOPOWER|BROKEN)) + return - if(href_list["amount"]) - amount = round(text2num(href_list["amount"]), 1) // round to nearest 1 - if(amount < 0) // Since the user can actually type the commands himself, some sanity checking - amount = 0 - if(amount > 100) - amount = 100 - - if(href_list["dispense"]) - if(!is_operational() || QDELETED(cell)) - return - if(beaker && dispensable_reagents.Find(href_list["dispense"])) + . = TRUE + switch(actions) + if("amount") + amount = clamp(round(text2num(params["amount"]), 1), 0, 50) // round to nearest 1 and clamp to 0 - 50 + if("dispense") + if(!is_operational() || QDELETED(cell)) + return + if(!beaker || !dispensable_reagents.Find(params["reagent"])) + return var/datum/reagents/R = beaker.reagents var/free = R.maximum_volume - R.total_volume var/actual = min(amount, (cell.charge * powerefficiency) * 10, free) - if(!cell.use(actual / powerefficiency)) atom_say("Not enough energy to complete operation!") return - - R.add_reagent(href_list["dispense"], actual) + R.add_reagent(params["reagent"], actual) overlays.Cut() if(!icon_beaker) - icon_beaker = image('icons/obj/chemical.dmi', src, "disp_beaker") //randomize beaker overlay position. + icon_beaker = mutable_appearance('icons/obj/chemical.dmi', "disp_beaker") //randomize beaker overlay position. icon_beaker.pixel_x = rand(-10, 5) overlays += icon_beaker - - if(href_list["remove"]) - if(beaker) - if(href_list["removeamount"]) - var/amount = text2num(href_list["removeamount"]) - if(isnum(amount) && (amount > 0)) - var/datum/reagents/R = beaker.reagents - var/id = href_list["remove"] - R.remove_reagent(id, amount) - else if(isnum(amount) && (amount == -1)) //Isolate instead - var/datum/reagents/R = beaker.reagents - var/id = href_list["remove"] - R.isolate_reagent(id) - - if(href_list["ejectBeaker"]) - if(beaker) + if("remove") + var/amount = text2num(params["amount"]) + if(!beaker || !amount) + return + var/datum/reagents/R = beaker.reagents + var/id = params["reagent"] + if(amount > 0) + R.remove_reagent(id, amount) + else if(amount == -1) //Isolate instead + R.isolate_reagent(id) + if("ejectBeaker") + if(!beaker) + return beaker.forceMove(loc) if(Adjacent(usr) && !issilicon(usr)) usr.put_in_hands(beaker) beaker = null overlays.Cut() + else + return FALSE add_fingerprint(usr) - return TRUE // update UIs attached to this object /obj/machinery/chem_dispenser/attackby(obj/item/I, mob/user, params) if(exchange_parts(user, I)) - SSnanoui.update_uis(src) + SStgui.update_uis(src) return if(isrobot(user)) @@ -263,9 +256,9 @@ beaker = I I.forceMove(src) to_chat(user, "You set [I] on the machine.") - SSnanoui.update_uis(src) // update all UIs attached to src + SStgui.update_uis(src) // update all UIs attached to src if(!icon_beaker) - icon_beaker = image('icons/obj/chemical.dmi', src, "disp_beaker") //randomize beaker overlay position. + icon_beaker = mutable_appearance('icons/obj/chemical.dmi', "disp_beaker") //randomize beaker overlay position. icon_beaker.pixel_x = rand(-10, 5) overlays += icon_beaker return @@ -299,8 +292,7 @@ to_chat(user, unhack_message) dispensable_reagents -= hacked_reagents hackedcheck = FALSE - SSnanoui.update_uis(src) - + SStgui.update_uis(src) /obj/machinery/chem_dispenser/screwdriver_act(mob/user, obj/item/I) if(default_deconstruction_screwdriver(user, "[initial(icon_state)]-o", "[initial(icon_state)]", I)) @@ -321,14 +313,14 @@ return attack_hand(user) /obj/machinery/chem_dispenser/attack_ghost(mob/user) - if(user.can_admin_interact()) - return attack_hand(user) + if(stat & BROKEN) + return + tgui_interact(user) /obj/machinery/chem_dispenser/attack_hand(mob/user) if(stat & BROKEN) return - - ui_interact(user) + tgui_interact(user) /obj/machinery/chem_dispenser/soda icon_state = "soda_dispenser" @@ -342,6 +334,7 @@ hacked_reagents = list("thirteenloko") hack_message = "You change the mode from 'McNano' to 'Pizza King'." unhack_message = "You change the mode from 'Pizza King' to 'McNano'." + is_drink = TRUE /obj/machinery/chem_dispenser/soda/New() ..() @@ -377,6 +370,7 @@ hacked_reagents = list("goldschlager", "patron", "absinthe", "ethanol", "nothing", "sake") hack_message = "You disable the 'nanotrasen-are-cheap-bastards' lock, enabling hidden and very expensive boozes." unhack_message = "You re-enable the 'nanotrasen-are-cheap-bastards' lock, disabling hidden and very expensive boozes." + is_drink = TRUE /obj/machinery/chem_dispenser/beer/New() ..() diff --git a/code/modules/reagents/chemistry/machinery/chem_heater.dm b/code/modules/reagents/chemistry/machinery/chem_heater.dm index 39aa91099c0..8fddc97537f 100644 --- a/code/modules/reagents/chemistry/machinery/chem_heater.dm +++ b/code/modules/reagents/chemistry/machinery/chem_heater.dm @@ -1,15 +1,19 @@ /obj/machinery/chem_heater name = "chemical heater" - density = 1 - anchored = 1 + density = TRUE + anchored = TRUE icon = 'icons/obj/chemical.dmi' icon_state = "mixer0b" use_power = IDLE_POWER_USE idle_power_usage = 40 - resistance_flags = FIRE_PROOF | ACID_PROOF + resistance_flags = FIRE_PROOF|ACID_PROOF var/obj/item/reagent_containers/beaker = null var/desired_temp = T0C var/on = FALSE + /// Whether this should auto-eject the beaker once done heating/cooling. + var/auto_eject = FALSE + /// The higher this number, the faster reagents will heat/cool. + var/speed_increase = 0 /obj/machinery/chem_heater/New() ..() @@ -19,35 +23,36 @@ component_parts += new /obj/item/stack/sheet/glass(null) RefreshParts() +/obj/machinery/chem_heater/RefreshParts() + speed_increase = initial(speed_increase) + for(var/obj/item/stock_parts/micro_laser/M in component_parts) + speed_increase += 5 * (M.rating - 1) + /obj/machinery/chem_heater/process() ..() - if(stat & NOPOWER) + if(stat & (NOPOWER|BROKEN)) return - var/state_change = FALSE if(on) if(beaker) if(!beaker.reagents.total_volume) on = FALSE - SSnanoui.update_uis(src) return - beaker.reagents.temperature_reagents(desired_temp) - beaker.reagents.temperature_reagents(desired_temp) - if(abs(beaker.reagents.chem_temp - desired_temp) <= 3) + beaker.reagents.temperature_reagents(desired_temp, max(1, 35 - speed_increase)) + if(round(beaker.reagents.chem_temp) == round(desired_temp)) + playsound(loc, 'sound/machines/ding.ogg', 50, 1) on = FALSE - state_change = TRUE - - if(state_change) - SSnanoui.update_uis(src) + if(auto_eject) + eject_beaker() /obj/machinery/chem_heater/proc/eject_beaker(mob/user) if(beaker) beaker.forceMove(get_turf(src)) - if(Adjacent(user) && !issilicon(user)) + if(user && Adjacent(user) && !issilicon(user)) user.put_in_hands(beaker) beaker = null icon_state = "mixer0b" on = FALSE - SSnanoui.update_uis(src) + SStgui.update_uis(src) /obj/machinery/chem_heater/power_change() if(powered()) @@ -55,7 +60,6 @@ else spawn(rand(0, 15)) stat |= NOPOWER - SSnanoui.update_uis(src) /obj/machinery/chem_heater/attackby(obj/item/I, mob/user) if(isrobot(user)) @@ -71,7 +75,7 @@ I.forceMove(src) to_chat(user, "You add the beaker to the machine!") icon_state = "mixer1b" - SSnanoui.update_uis(src) + SStgui.update_uis(src) return if(exchange_parts(user, I)) @@ -95,62 +99,62 @@ default_deconstruction_crowbar(user, I) /obj/machinery/chem_heater/attack_hand(mob/user) - ui_interact(user) + tgui_interact(user) /obj/machinery/chem_heater/attack_ghost(mob/user) - if(user.can_admin_interact()) - return attack_hand(user) + tgui_interact(user) /obj/machinery/chem_heater/attack_ai(mob/user) add_hiddenprint(user) return attack_hand(user) -/obj/machinery/chem_heater/Topic(href, href_list) +/obj/machinery/chem_heater/tgui_act(action, params) if(..()) - return FALSE + return + if(stat & (NOPOWER|BROKEN)) + return - if(href_list["toggle_on"]) - if(!beaker.reagents.total_volume) - return FALSE - on = !on - . = 1 - - if(href_list["adjust_temperature"]) - var/val = href_list["adjust_temperature"] - if(isnum(val)) - desired_temp = clamp(desired_temp+val, 0, 1000) - else if(val == "input") - var/target = input("Please input the target temperature", name) as num - desired_temp = clamp(target, 0, 1000) + . = TRUE + switch(action) + if("toggle_on") + on = !on + if("adjust_temperature") + desired_temp = clamp(text2num(params["target"]), 0, 1000) + if("eject_beaker") + eject_beaker(usr) + . = FALSE + if("toggle_autoeject") + auto_eject = !auto_eject else return FALSE - . = 1 + add_fingerprint(usr) - if(href_list["eject_beaker"]) - eject_beaker(usr) - . = 0 //updated in eject_beaker() already - -/obj/machinery/chem_heater/ui_interact(mob/user, ui_key = "main", datum/nanoui/ui = null) +/obj/machinery/chem_heater/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.stat || user.restrained()) 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) + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "chem_heater.tmpl", "ChemHeater", 350, 270) + ui = new(user, src, ui_key, "ChemHeater", "Chemical Heater", 350, 270, master_ui, state) ui.open() -/obj/machinery/chem_heater/ui_data(mob/user, ui_key = "main", datum/topic_state/state = GLOB.default_state) +/obj/machinery/chem_heater/tgui_data(mob/user) var/data[0] + var/cur_temp = beaker ? beaker.reagents.chem_temp : null data["targetTemp"] = desired_temp + data["targetTempReached"] = FALSE + data["autoEject"] = auto_eject data["isActive"] = on - data["isBeakerLoaded"] = beaker ? 1 : 0 + data["isBeakerLoaded"] = beaker ? TRUE : FALSE - data["currentTemp"] = beaker ? beaker.reagents.chem_temp : null + data["currentTemp"] = cur_temp data["beakerCurrentVolume"] = beaker ? beaker.reagents.total_volume : null data["beakerMaxVolume"] = beaker ? beaker.volume : null + if(cur_temp) + data["targetTempReached"] = round(cur_temp) == round(desired_temp) + //copy-pasted from chem dispenser var/beakerContents[0] if(beaker) diff --git a/code/modules/reagents/chemistry/machinery/chem_master.dm b/code/modules/reagents/chemistry/machinery/chem_master.dm index 4d1f577969c..f2b3247fe4b 100644 --- a/code/modules/reagents/chemistry/machinery/chem_master.dm +++ b/code/modules/reagents/chemistry/machinery/chem_master.dm @@ -1,3 +1,9 @@ +#define MAX_PILL_SPRITE 20 //max icon state of the pill sprites +#define MAX_MULTI_AMOUNT 20 // Max number of pills/patches that can be made at once +#define MAX_UNITS_PER_PILL 100 // Max amount of units in a pill +#define MAX_UNITS_PER_PATCH 30 // Max amount of units in a patch +#define MAX_CUSTOM_NAME_LEN 64 // Max length of a custom pill/condiment/whatever + /obj/machinery/chem_master name = "\improper ChemMaster 3000" density = TRUE @@ -14,10 +20,12 @@ var/useramount = 30 // Last used amount var/pillamount = 10 var/patchamount = 10 - var/bottlesprite = "bottle" - var/pillsprite = "1" + var/bottlesprite = 1 + var/pillsprite = 1 var/client/has_sprites = list() var/printing = FALSE + var/static/list/pill_bottle_wrappers + var/static/list/bottle_styles /obj/machinery/chem_master/New() ..() @@ -94,7 +102,7 @@ beaker = I I.forceMove(src) to_chat(user, "You add the beaker to the machine!") - SSnanoui.update_uis(src) + SStgui.update_uis(src) update_icon() else if(istype(I, /obj/item/storage/pill_bottle)) @@ -109,14 +117,10 @@ loaded_pill_bottle = I I.forceMove(src) to_chat(user, "You add [I] into the dispenser slot!") - SSnanoui.update_uis(src) + SStgui.update_uis(src) else return ..() - - - - /obj/machinery/chem_master/crowbar_act(mob/user, obj/item/I) if(!panel_open) return @@ -142,319 +146,127 @@ power_change() return TRUE -/obj/machinery/chem_master/Topic(href, href_list) +/obj/machinery/chem_master/tgui_act(action, params, datum/tgui/ui, datum/tgui_state/state) if(..()) + return + if(stat & (NOPOWER|BROKEN)) + return + + if(tgui_act_modal(action, params, ui, state)) return TRUE add_fingerprint(usr) usr.set_machine(src) - - if(href_list["ejectp"]) - if(loaded_pill_bottle) - loaded_pill_bottle.forceMove(loc) - loaded_pill_bottle = null - else if(href_list["change_pillbottle"]) - if(loaded_pill_bottle) - var/list/wrappers = list("Default wrapper", "Red wrapper", "Green wrapper", "Pale green wrapper", "Blue wrapper", "Light blue wrapper", "Teal wrapper", "Yellow wrapper", "Orange wrapper", "Pink wrapper", "Brown wrapper") - var/chosen = input(usr, "Select a pillbottle wrapper", "Pillbottle wrapper", wrappers[1]) as null|anything in wrappers - if(!chosen) + . = TRUE + switch(action) + if("toggle") + mode = !mode + if("ejectp") + if(loaded_pill_bottle) + loaded_pill_bottle.forceMove(loc) + loaded_pill_bottle = null + if("print") + if(printing || condi) return - var/color - switch(chosen) - if("Default wrapper") - loaded_pill_bottle.cut_overlays() - return - if("Red wrapper") - color = COLOR_RED - if("Green wrapper") - color = COLOR_GREEN - if("Pink wrapper") - color = COLOR_PINK - if("Teal wrapper") - color = COLOR_TEAL - if("Blue wrapper") - color = COLOR_BLUE - if("Brown wrapper") - color = COLOR_MAROON - if("Light blue wrapper") - color = COLOR_CYAN_BLUE - if("Yellow wrapper") - color = COLOR_YELLOW - if("Pale green wrapper") - color = COLOR_PALE_BTL_GREEN - if("Orange wrapper") - color = COLOR_ORANGE - loaded_pill_bottle.wrapper_color = color - loaded_pill_bottle.apply_wrap() - else if(href_list["close"]) - usr << browse(null, "window=chem_master") - onclose(usr, "chem_master") - usr.unset_machine() - return - if(href_list["print_p"]) - if(!printing) + var/idx = text2num(params["idx"]) || 0 + var/from_beaker = text2num(params["beaker"]) || FALSE + var/reagent_list = from_beaker ? beaker.reagents.reagent_list : reagents.reagent_list + if(idx < 1 || idx > length(reagent_list)) + return + + var/datum/reagent/R = reagent_list[idx] + printing = TRUE visible_message("[src] rattles and prints out a sheet of paper.") playsound(loc, 'sound/goonstation/machines/printer_dotmatrix.ogg', 50, 1) + var/obj/item/paper/P = new /obj/item/paper(loc) - P.info = "
Chemical Analysis

" + P.info = "
Chemical Analysis

" P.info += "Time of analysis: [station_time_timestamp()]

" - P.info += "Chemical name: [href_list["name"]]
" - if(href_list["name"] == "Blood") - var/datum/reagents/R = beaker.reagents - var/datum/reagent/blood/G - for(var/datum/reagent/F in R.reagent_list) - if(F.name == href_list["name"]) - G = F - break - var/B = G.data["blood_type"] - var/C = G.data["blood_DNA"] - P.info += "Description:
Blood Type: [B]
DNA: [C]" + P.info += "Chemical name: [R.name]
" + if(istype(R, /datum/reagent/blood)) + var/datum/reagent/blood/B = R + P.info += "Description: N/A
Blood Type: [B.data["blood_type"]]
DNA: [B.data["blood_DNA"]]" else - P.info += "Description: [href_list["desc"]]" + P.info += "Description: [R.description]" P.info += "

Notes:
" - P.name = "Chemical Analysis - [href_list["name"]]" - printing = FALSE + P.name = "Chemical Analysis - [R.name]" + spawn(50) + printing = FALSE + else + . = FALSE - if(beaker) - var/datum/reagents/R = beaker.reagents - if(href_list["analyze"]) - var/dat = "" - if(!condi) - if(href_list["name"] == "Blood") - var/datum/reagent/blood/G - for(var/datum/reagent/F in R.reagent_list) - if(F.name == href_list["name"]) - G = F - break - var/A = G.name - var/B = G.data["blood_type"] - var/C = G.data["blood_DNA"] - dat += "Chemmaster 3000Chemical infos:

Name:
[A]

Description:
Blood Type: [B]
DNA: [C]" - else - dat += "Chemmaster 3000Chemical infos:

Name:
[href_list["name"]]

Description:
[href_list["desc"]]" - dat += "

(Print Analysis)
" - dat += "(Back)" + if(. || !beaker) + return + + . = TRUE + var/datum/reagents/R = beaker.reagents + switch(action) + if("add") + var/id = params["id"] + var/amount = text2num(params["amount"]) + if(!id || !amount) + return + R.trans_id_to(src, id, amount) + if("remove") + var/id = params["id"] + var/amount = text2num(params["amount"]) + if(!id || !amount) + return + if(mode) + reagents.trans_id_to(beaker, id, amount) else - dat += "Condimaster 3000Condiment infos:

Name:
[href_list["name"]]

Description:
[href_list["desc"]]


(Back)" - usr << browse(dat, "window=chem_master;size=575x500") - return - - else if(href_list["add"]) - - if(href_list["amount"]) - var/id = href_list["add"] - var/amount = text2num(href_list["amount"]) - R.trans_id_to(src, id, amount) - - else if(href_list["addcustom"]) - - var/id = href_list["addcustom"] - useramount = input("Select the amount to transfer.", 30, useramount) as num - useramount = isgoodnumber(useramount) - Topic(null, list("amount" = "[useramount]", "add" = "[id]")) - - else if(href_list["remove"]) - - if(href_list["amount"]) - var/id = href_list["remove"] - var/amount = text2num(href_list["amount"]) - if(mode) - reagents.trans_id_to(beaker, id, amount) - else - reagents.remove_reagent(id, amount) - - - else if(href_list["removecustom"]) - - var/id = href_list["removecustom"] - useramount = input("Select the amount to transfer.", 30, useramount) as num - useramount = isgoodnumber(useramount) - Topic(null, list("amount" = "[useramount]", "remove" = "[id]")) - - else if(href_list["toggle"]) - mode = !mode - - else if(href_list["main"]) - attack_hand(usr) - return - else if(href_list["eject"]) - if(beaker) - beaker.forceMove(get_turf(src)) - if(Adjacent(usr) && !issilicon(usr)) - usr.put_in_hands(beaker) - beaker = null - reagents.clear_reagents() - update_icon() - else if(href_list["createpill"] || href_list["createpill_multiple"]) - if(!condi) - var/count = 1 - if(href_list["createpill_multiple"]) - count = input("Select the number of pills to make.", 10, pillamount) as num|null - if(count == null) - return - count = isgoodnumber(count) - if(count > 20) - count = 20 //Pevent people from creating huge stacks of pills easily. Maybe move the number to defines? - if(count <= 0) - return - var/amount_per_pill = reagents.total_volume / count - if(amount_per_pill > 100) - amount_per_pill = 100 - var/name = clean_input("Name:","Name your pill!","[reagents.get_master_reagent_name()] ([amount_per_pill]u)") - if(!name) - return - name = reject_bad_text(name) - while(count--) - if(reagents.total_volume <= 0) - to_chat(usr, "Not enough reagents to create these pills!") - return - var/obj/item/reagent_containers/food/pill/P = new/obj/item/reagent_containers/food/pill(loc) - if(!name) - name = reagents.get_master_reagent_name() - P.name = "[name] pill" - P.pixel_x = rand(-7, 7) //random position - P.pixel_y = rand(-7, 7) - P.icon_state = "pill"+pillsprite - reagents.trans_to(P, amount_per_pill) - if(loaded_pill_bottle && loaded_pill_bottle.type == /obj/item/storage/pill_bottle) - if(loaded_pill_bottle.contents.len < loaded_pill_bottle.storage_slots) - P.forceMove(loaded_pill_bottle) - updateUsrDialog() - else - var/name = clean_input("Name:", "Name your bag!", reagents.get_master_reagent_name()) - if(!name) - return - name = reject_bad_text(name) - var/obj/item/reagent_containers/food/condiment/pack/P = new/obj/item/reagent_containers/food/condiment/pack(loc) - if(!name) name = reagents.get_master_reagent_name() - P.originalname = name - P.name = "[name] pack" - P.desc = "A small condiment pack. The label says it contains [name]." - reagents.trans_to(P, 10) - else if(href_list["createpatch"] || href_list["createpatch_multiple"]) - if(!condi) - var/count = 1 - if(href_list["createpatch_multiple"]) - count = input("Select the number of patches to make.", 10, patchamount) as num|null - if(count == null) - return - count = isgoodnumber(count) - if(!count || count <= 0) - return - if(count > 20) - count = 20 //Pevent people from creating huge stacks of patches easily. Maybe move the number to defines? - var/amount_per_patch = reagents.total_volume/count - if(amount_per_patch > 30) - amount_per_patch = 30 - var/name = clean_input("Name:", "Name your patch!", "[reagents.get_master_reagent_name()] ([amount_per_patch]u)") - if(!name) - return - name = reject_bad_text(name) - var/is_medical_patch = chemical_safety_check(reagents) - while(count--) - var/obj/item/reagent_containers/food/pill/patch/P = new/obj/item/reagent_containers/food/pill/patch(loc) - if(!name) name = reagents.get_master_reagent_name() - P.name = "[name] patch" - P.pixel_x = rand(-7, 7) //random position - P.pixel_y = rand(-7, 7) - reagents.trans_to(P,amount_per_patch) - if(is_medical_patch) - P.instant_application = TRUE - P.icon_state = "bandaid_med" - if(loaded_pill_bottle && loaded_pill_bottle.type == /obj/item/storage/pill_bottle/patch_pack) - if(loaded_pill_bottle.contents.len < loaded_pill_bottle.storage_slots) - P.forceMove(loaded_pill_bottle) - updateUsrDialog() - - else if(href_list["createbottle"]) - if(!condi) - var/name = clean_input("Name:", "Name your bottle!", reagents.get_master_reagent_name()) - if(!name) - return - name = reject_bad_text(name) - var/obj/item/reagent_containers/glass/bottle/reagent/P = new/obj/item/reagent_containers/glass/bottle/reagent(loc) - if(!name) - name = reagents.get_master_reagent_name() - P.name = "[name] bottle" - P.pixel_x = rand(-7, 7) //random position - P.pixel_y = rand(-7, 7) - P.icon_state = bottlesprite - reagents.trans_to(P, 50) - else - var/obj/item/reagent_containers/food/condiment/P = new/obj/item/reagent_containers/food/condiment(loc) - reagents.trans_to(P, 50) - else if(href_list["change_pill"]) - #define MAX_PILL_SPRITE 20 //max icon state of the pill sprites - var/dat = "" - var/j = 0 - for(var/i = 1 to MAX_PILL_SPRITE) - j++ - if(j == 1) - dat += "" - dat += "" - if(j == 5) - dat += "" - j = 0 - dat += "
" - usr << browse(dat, "window=chem_master_iconsel;size=225x193") - return - else if(href_list["change_bottle"]) - var/dat = "" - var/j = 0 - for(var/i in list("bottle", "small_bottle", "wide_bottle", "round_bottle", "reagent_bottle")) - j++ - if(j == 1) - dat += "" - dat += "" - if(j == 5) - dat += "" - j = 0 - dat += "
" - usr << browse(dat, "window=chem_master_iconsel;size=225x193") - return - else if(href_list["pill_sprite"]) - pillsprite = href_list["pill_sprite"] - usr << browse(null, "window=chem_master_iconsel") - else if(href_list["bottle_sprite"]) - bottlesprite = href_list["bottle_sprite"] - usr << browse(null, "window=chem_master_iconsel") - - SSnanoui.update_uis(src) + reagents.remove_reagent(id, amount) + if("eject") + if(!beaker) + return + beaker.forceMove(get_turf(src)) + if(Adjacent(usr) && !issilicon(usr)) + usr.put_in_hands(beaker) + beaker = null + reagents.clear_reagents() + update_icon() + if("create_condi_bottle") + if(!condi || !reagents.total_volume) + return + var/obj/item/reagent_containers/food/condiment/P = new(loc) + reagents.trans_to(P, 50) + else + return FALSE /obj/machinery/chem_master/attack_ai(mob/user) return attack_hand(user) /obj/machinery/chem_master/attack_ghost(mob/user) - if(user.can_admin_interact()) - return attack_hand(user) + tgui_interact(user) /obj/machinery/chem_master/attack_hand(mob/user) if(..()) return TRUE - ui_interact(user) + tgui_interact(user) -/obj/machinery/chem_master/ui_interact(mob/user, ui_key = "main", datum/nanoui/ui = null, force_open = TRUE) +/obj/machinery/chem_master/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) var/datum/asset/chem_master/assets = get_asset_datum(/datum/asset/chem_master) assets.send(user) - 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) - ui = new(user, src, ui_key, "chem_master.tmpl", name, 575, 500) + ui = new(user, src, ui_key, "ChemMaster", name, 575, 500) ui.open() -/obj/machinery/chem_master/ui_data(mob/user, ui_key = "main", datum/topic_state/state = GLOB.default_state) +/obj/machinery/chem_master/tgui_data(mob/user) var/data[0] data["condi"] = condi - data["loaded_pill_bottle"] = (loaded_pill_bottle ? 1 : 0) + data["loaded_pill_bottle"] = loaded_pill_bottle ? TRUE : FALSE if(loaded_pill_bottle) + data["loaded_pill_bottle_name"] = loaded_pill_bottle.name data["loaded_pill_bottle_contents_len"] = loaded_pill_bottle.contents.len data["loaded_pill_bottle_storage_slots"] = loaded_pill_bottle.storage_slots - data["beaker"] = (beaker ? 1 : 0) + data["beaker"] = beaker ? TRUE : FALSE if(beaker) var/list/beaker_reagents_list = list() data["beaker_reagents"] = beaker_reagents_list @@ -464,13 +276,255 @@ data["buffer_reagents"] = buffer_reagents_list for(var/datum/reagent/R in reagents.reagent_list) buffer_reagents_list[++buffer_reagents_list.len] = list("name" = R.name, "volume" = R.volume, "id" = R.id, "description" = R.description) + else + data["beaker_reagents"] = list() + data["buffer_reagents"] = list() data["pillsprite"] = pillsprite data["bottlesprite"] = bottlesprite data["mode"] = mode + data["printing"] = printing + + // Transfer modal information if there is one + data["modal"] = tgui_modal_data(src) return data +/** + * Called in tgui_act() to process modal actions + * + * Arguments: + * * action - The action passed by tgui + * * params - The params passed by tgui + */ +/obj/machinery/chem_master/proc/tgui_act_modal(action, params, datum/tgui/ui, datum/tgui_state/state) + . = 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_OPEN) + switch(id) + if("analyze") + var/idx = text2num(arguments["idx"]) || 0 + var/from_beaker = text2num(arguments["beaker"]) || FALSE + var/reagent_list = from_beaker ? beaker.reagents.reagent_list : reagents.reagent_list + if(idx < 1 || idx > length(reagent_list)) + return + + var/datum/reagent/R = reagent_list[idx] + var/list/result = list("idx" = idx, "name" = R.name, "desc" = R.description) + if(!condi && istype(R, /datum/reagent/blood)) + var/datum/reagent/blood/B = R + result["blood_type"] = B.data["blood_type"] + result["blood_dna"] = B.data["blood_DNA"] + + arguments["analysis"] = result + tgui_modal_message(src, id, "", null, arguments) + if("change_pill_bottle_style") + if(!loaded_pill_bottle) + return + if(!pill_bottle_wrappers) + pill_bottle_wrappers = list( + "CLEAR" = "Default", + COLOR_RED = "Red", + COLOR_GREEN = "Green", + COLOR_PALE_BTL_GREEN = "Pale green", + COLOR_BLUE = "Blue", + COLOR_CYAN_BLUE = "Light blue", + COLOR_TEAL = "Teal", + COLOR_YELLOW = "Yellow", + COLOR_ORANGE = "Orange", + COLOR_PINK = "Pink", + COLOR_MAROON = "Brown" + ) + var/current = pill_bottle_wrappers[loaded_pill_bottle.wrapper_color] || "Default" + tgui_modal_choice(src, id, "Please select a pill bottle wrapper:", null, arguments, current, pill_bottle_wrappers) + if("addcustom") + if(!beaker || !beaker.reagents.total_volume) + return + tgui_modal_input(src, id, "Please enter the amount to transfer to buffer:", null, arguments, useramount) + if("removecustom") + if(!reagents.total_volume) + return + tgui_modal_input(src, id, "Please enter the amount to transfer to [mode ? "beaker" : "disposal"]:", null, arguments, useramount) + if("create_condi_pack") + if(!condi || !reagents.total_volume) + return + tgui_modal_input(src, id, "Please name your new condiment pack:", null, arguments, reagents.get_master_reagent_name(), MAX_CUSTOM_NAME_LEN) + if("create_pill") + if(condi || !reagents.total_volume) + return + var/num = round(text2num(arguments["num"] || 1)) + if(!num) + return + arguments["num"] = num + var/amount_per_pill = clamp(reagents.total_volume / num, 0, MAX_UNITS_PER_PILL) + var/default_name = "[reagents.get_master_reagent_name()] ([amount_per_pill]u)" + var/pills_text = num == 1 ? "new pill" : "[num] new pills" + tgui_modal_input(src, id, "Please name your [pills_text]:", null, arguments, default_name, MAX_CUSTOM_NAME_LEN) + if("create_pill_multiple") + if(condi || !reagents.total_volume) + return + tgui_modal_input(src, id, "Please enter the amount of pills to make (max [MAX_MULTI_AMOUNT] at a time):", null, arguments, pillamount, 5) + if("change_pill_style") + var/list/choices = list() + for(var/i = 1 to MAX_PILL_SPRITE) + choices += "pill[i].png" + tgui_modal_bento(src, id, "Please select the new style for pills:", null, arguments, pillsprite, choices) + if("create_patch") + if(condi || !reagents.total_volume) + return + var/num = round(text2num(arguments["num"] || 1)) + if(!num) + return + arguments["num"] = num + var/amount_per_patch = clamp(reagents.total_volume / num, 0, MAX_UNITS_PER_PATCH) + var/default_name = "[reagents.get_master_reagent_name()] ([amount_per_patch]u)" + var/patches_text = num == 1 ? "new patch" : "[num] new patches" + tgui_modal_input(src, id, "Please name your [patches_text]:", null, arguments, default_name, MAX_CUSTOM_NAME_LEN) + if("create_patch_multiple") + if(condi || !reagents.total_volume) + return + tgui_modal_input(src, id, "Please enter the amount of patches to make (max [MAX_MULTI_AMOUNT] at a time):", null, arguments, pillamount, 5) + if("create_bottle") + if(condi || !reagents.total_volume) + return + tgui_modal_input(src, id, "Please name your bottle:", null, arguments, reagents.get_master_reagent_name(), MAX_CUSTOM_NAME_LEN) + if("change_bottle_style") + if(!bottle_styles) + bottle_styles = list("bottle", "small_bottle", "wide_bottle", "round_bottle", "reagent_bottle") + var/list/bottle_styles_png = list() + for(var/style in bottle_styles) + bottle_styles_png += "[style].png" + tgui_modal_bento(src, id, "Please select the new style for bottles:", null, arguments, bottlesprite, bottle_styles_png) + else + return FALSE + if(TGUI_MODAL_ANSWER) + var/answer = params["answer"] + switch(id) + if("change_pill_bottle_style") + if(!pill_bottle_wrappers || !loaded_pill_bottle) // wat? + return + var/color = "CLEAR" + for(var/col in pill_bottle_wrappers) + var/col_name = pill_bottle_wrappers[col] + if(col_name == answer) + color = col + break + if(length(color) && color != "CLEAR") + loaded_pill_bottle.wrapper_color = color + loaded_pill_bottle.apply_wrap() + else + loaded_pill_bottle.wrapper_color = null + loaded_pill_bottle.cut_overlays() + if("addcustom") + var/amount = isgoodnumber(text2num(answer)) + if(!amount || !arguments["id"]) + return + tgui_act("add", list("id" = arguments["id"], "amount" = amount), ui, state) + if("removecustom") + var/amount = isgoodnumber(text2num(answer)) + if(!amount || !arguments["id"]) + return + tgui_act("remove", list("id" = arguments["id"], "amount" = amount), ui, state) + if("create_condi_pack") + if(!condi || !reagents.total_volume) + return + if(!length(answer)) + answer = reagents.get_master_reagent_name() + var/obj/item/reagent_containers/food/condiment/pack/P = new(loc) + P.originalname = answer + P.name = "[answer] pack" + P.desc = "A small condiment pack. The label says it contains [answer]." + reagents.trans_to(P, 10) + if("create_pill") + if(condi || !reagents.total_volume) + return + var/count = clamp(round(text2num(arguments["num"]) || 0), 0, MAX_MULTI_AMOUNT) + if(!count) + return + + if(!length(answer)) + answer = reagents.get_master_reagent_name() + var/amount_per_pill = clamp(reagents.total_volume / count, 0, MAX_UNITS_PER_PILL) + while(count--) + if(reagents.total_volume <= 0) + to_chat(usr, "Not enough reagents to create these pills!") + return + + var/obj/item/reagent_containers/food/pill/P = new(loc) + P.name = "[answer] pill" + P.pixel_x = rand(-7, 7) // Random position + P.pixel_y = rand(-7, 7) + P.icon_state = "pill[pillsprite]" + reagents.trans_to(P, amount_per_pill) + // Load the pills in the bottle if there's one loaded + if(istype(loaded_pill_bottle) && length(loaded_pill_bottle.contents) < loaded_pill_bottle.storage_slots) + P.forceMove(loaded_pill_bottle) + if("create_pill_multiple") + if(condi || !reagents.total_volume) + return + tgui_act("modal_open", list("id" = "create_pill", "arguments" = list("num" = answer)), ui, state) + if("change_pill_style") + var/new_style = clamp(text2num(answer) || 0, 0, MAX_PILL_SPRITE) + if(!new_style) + return + pillsprite = new_style + if("create_patch") + if(condi || !reagents.total_volume) + return + var/count = clamp(round(text2num(arguments["num"]) || 0), 0, MAX_MULTI_AMOUNT) + if(!count) + return + + if(!length(answer)) + answer = reagents.get_master_reagent_name() + var/amount_per_patch = clamp(reagents.total_volume / count, 0, MAX_UNITS_PER_PATCH) + var/is_medical_patch = chemical_safety_check(reagents) + while(count--) + if(reagents.total_volume <= 0) + to_chat(usr, "Not enough reagents to create these patches!") + return + + var/obj/item/reagent_containers/food/pill/patch/P = new(loc) + P.name = "[answer] patch" + P.pixel_x = rand(-7, 7) // random position + P.pixel_y = rand(-7, 7) + reagents.trans_to(P, amount_per_patch) + if(is_medical_patch) + P.instant_application = TRUE + P.icon_state = "bandaid_med" + // Load the patches in the bottle if there's one loaded + if(istype(loaded_pill_bottle, /obj/item/storage/pill_bottle/patch_pack) && length(loaded_pill_bottle.contents) < loaded_pill_bottle.storage_slots) + P.forceMove(loaded_pill_bottle) + if("create_patch_multiple") + if(condi || !reagents.total_volume) + return + tgui_act("modal_open", list("id" = "create_patch", "arguments" = list("num" = answer)), ui, state) + if("create_bottle") + if(condi || !reagents.total_volume) + return + + if(!length(answer)) + answer = reagents.get_master_reagent_name() + var/obj/item/reagent_containers/glass/bottle/reagent/P = new(loc) + P.name = "[answer] bottle" + P.pixel_x = rand(-7, 7) // random position + P.pixel_y = rand(-7, 7) + P.icon_state = length(bottle_styles) && bottle_styles[bottlesprite] || "bottle" + reagents.trans_to(P, 50) + if("change_bottle_style") + if(!bottle_styles) + return + var/new_sprite = text2num(answer) || 1 + if(new_sprite < 1 || new_sprite > length(bottle_styles)) + return + bottlesprite = new_sprite + else + return FALSE + else + return FALSE + /obj/machinery/chem_master/proc/isgoodnumber(num) if(isnum(num)) if(num > 200) @@ -481,7 +535,7 @@ num = round(num) return num else - return 0 + return FALSE /obj/machinery/chem_master/proc/chemical_safety_check(datum/reagents/R) var/all_safe = TRUE @@ -503,3 +557,9 @@ component_parts += new /obj/item/reagent_containers/glass/beaker(null) component_parts += new /obj/item/reagent_containers/glass/beaker(null) RefreshParts() + +#undef MAX_PILL_SPRITE +#undef MAX_MULTI_AMOUNT +#undef MAX_UNITS_PER_PILL +#undef MAX_UNITS_PER_PATCH +#undef MAX_CUSTOM_NAME_LEN diff --git a/code/modules/research/designs/comp_board_designs.dm b/code/modules/research/designs/comp_board_designs.dm index 826bdbf16aa..054dc2db810 100644 --- a/code/modules/research/designs/comp_board_designs.dm +++ b/code/modules/research/designs/comp_board_designs.dm @@ -272,6 +272,16 @@ build_path = /obj/item/circuitboard/solar_control category = list("Computer Boards") +/datum/design/sm_monitor + name = "Console Board (Supermatter Monitoring)" + desc = "Allows for the construction of circuit boards used to build a supermatter monitoring console" + id = "sm_monitor" + req_tech = list("programming" = 2, "powerstorage" = 2) + build_type = IMPRINTER + materials = list(MAT_GLASS = 1000) + build_path = /obj/item/circuitboard/sm_monitor + category = list("Computer Boards") + /datum/design/spacepodlocator name = "Console Board (Spacepod Locator)" desc = "Allows for the construction of circuit boards used to build a space-pod locating console" diff --git a/code/modules/spacepods/spacepod.dm b/code/modules/spacepods/spacepod.dm index 56e7f8f96ba..7d28be5c5cf 100644 --- a/code/modules/spacepods/spacepod.dm +++ b/code/modules/spacepods/spacepod.dm @@ -296,7 +296,7 @@ return var/sound/S = sound(mysound) S.wait = 0 //No queue - S.channel = open_sound_channel() + S.channel = SSsounds.random_available_channel() S.volume = 50 for(var/mob/M in passengers | pilot) M << S diff --git a/code/modules/store/items.dm b/code/modules/store/items.dm index 4c1610474ba..0a0f19fb0f7 100644 --- a/code/modules/store/items.dm +++ b/code/modules/store/items.dm @@ -111,6 +111,12 @@ typepath = /obj/item/instrument/eguitar cost = 500 +/datum/storeitem/banjo + name = "Banjo" + desc = "It's pretty much just a drum with a neck and strings." + typepath = /obj/item/instrument/banjo + cost = 500 + /datum/storeitem/piano_synth name = "Piano Synthesizer" desc = "An advanced electronic synthesizer that can emulate various instruments." diff --git a/code/modules/tgui/modal.dm b/code/modules/tgui/modal.dm new file mode 100644 index 00000000000..f09f54f627c --- /dev/null +++ b/code/modules/tgui/modal.dm @@ -0,0 +1,370 @@ +/** + * tgui modals + * + * Allows creation of modals within tgui. + */ + +GLOBAL_LIST(tgui_modals) + +/** + * Call this from a proc that is called in tgui_act() to process modal actions + * + * Example: /obj/machinery/chem_master/proc/tgui_act_modal + * You can then switch based on the return value and show different + * modals depending on the answer. + * Arguments: + * * source - The source datum + * * action - The called action + * * params - The params to the action + */ +/datum/proc/tgui_modal_act(datum/source = src, action = "", params) + ASSERT(istype(source)) + + . = null + switch(action) + if("modal_open") // Params: id, arguments + return TGUI_MODAL_OPEN + if("modal_answer") // Params: id, answer, arguments + params["answer"] = tgui_modal_preprocess_answer(source, params["answer"]) + if(tgui_modal_answer(source, params["id"], params["answer"])) // If there's a current modal with a delegate that returned TRUE, no need to continue + . = TGUI_MODAL_DELEGATE + else + . = TGUI_MODAL_ANSWER + tgui_modal_clear(source) + if("modal_close") // Params: id + tgui_modal_clear(source) + return TGUI_MODAL_CLOSE + +/** + * Call this from tgui_data() to return modal information if needed + + * Arguments: + * * source - The source datum + */ +/datum/proc/tgui_modal_data(datum/source = src) + ASSERT(istype(source)) + + var/datum/tgui_modal/current = LAZYACCESS(GLOB.tgui_modals, source.UID()) + if(!current) + return null + + return current.to_data() + +/** + * Clears the current modal for a given datum + * + * Arguments: + * * source - The source datum + */ +/datum/proc/tgui_modal_clear(datum/source = src) + ASSERT(istype(source)) + + LAZYINITLIST(GLOB.tgui_modals) + var/datum/tgui_modal/previous = GLOB.tgui_modals[source.UID()] + if(!previous) + return FALSE + + for(var/i in 1 to length(GLOB.tgui_modals)) + var/key = GLOB.tgui_modals[i] + if(previous == GLOB.tgui_modals[key]) + GLOB.tgui_modals.Cut(i, i + 1) + break + + SStgui.update_uis(source) + return TRUE + +/** + * Opens a message TGUI modal + * + * Arguments: + * * source - The source datum + * * id - The ID of the modal + * * text - The text to display above the answers + * * delegate - The proc to call when closed + * * arguments - List of arguments passed to and from JS (mostly useful for chaining modals) + */ +/datum/proc/tgui_modal_message(datum/source = src, id, text = "Default modal message", delegate, arguments) + ASSERT(length(id)) + + var/datum/tgui_modal/modal = new(id, text, delegate, arguments) + return tgui_modal_new(source, modal) + +/** + * Opens a text input TGUI modal + * + * Arguments: + * * source - The source datum + * * id - The ID of the modal + * * text - The text to display above the answers + * * delegate - The proc to call when submitted + * * arguments - List of arguments passed to and from JS (mostly useful for chaining modals) + * * value - The default value of the input + * * max_length - The maximum char length of the input + */ +/datum/proc/tgui_modal_input(datum/source = src, id, text = "Default modal message", delegate, arguments, value = "", max_length = TGUI_MODAL_INPUT_MAX_LENGTH) + ASSERT(length(id)) + ASSERT(max_length > 0) + + var/datum/tgui_modal/input/modal = new(id, text, delegate, arguments, value, max_length) + return tgui_modal_new(source, modal) + +/** + * Opens a dropdown input TGUI modal + * + * Internally checks if the answer is in the list of choices. + * Arguments: + * * source - The source datum + * * id - The ID of the modal + * * text - The text to display above the answers + * * delegate - The proc to call when submitted + * * arguments - List of arguments passed to and from JS (mostly useful for chaining modals) + * * value - The default value of the dropdown + * * choices - The list of available choices in the dropdown + */ +/datum/proc/tgui_modal_choice(datum/source = src, id, text = "Default modal message", delegate, arguments, value = "", choices) + ASSERT(length(id)) + + var/datum/tgui_modal/input/choice/modal = new(id, text, delegate, arguments, value, choices) + return tgui_modal_new(source, modal) + +/** + * Opens a bento input TGUI modal + * + * Internally checks if the answer is in the list of choices. + * Arguments: + * * source - The source datum + * * id - The ID of the modal + * * text - The text to display above the answers + * * delegate - The proc to call when submitted + * * arguments - List of arguments passed to and from JS (mostly useful for chaining modals) + * * value - The default value of the bento + * * choices - The list of available choices in the bento + */ +/datum/proc/tgui_modal_bento(datum/source = src, id, text = "Default modal message", delegate, arguments, value, choices) + ASSERT(length(id)) + + var/datum/tgui_modal/input/bento/modal = new(id, text, delegate, arguments, value, choices) + return tgui_modal_new(source, modal) + +/** + * Opens a yes/no TGUI modal + * + * Arguments: + * * source - The source datum + * * id - The ID of the modal + * * text - The text to display above the answers + * * delegate - The proc to call when "Yes" is pressed + * * delegate_no - The proc to call when "No" is pressed + * * arguments - List of arguments passed to and from JS (mostly useful for chaining modals) + * * yes_text - The text to show in the "Yes" button + * * no_text - The text to show in the "No" button + */ +/datum/proc/tgui_modal_boolean(datum/source = src, id, text = "Default modal message", delegate, delegate_no, arguments, yes_text = "Yes", no_text = "No") + ASSERT(length(id)) + + var/datum/tgui_modal/boolean/modal = new(id, text, delegate, delegate_no, arguments, yes_text, no_text) + return tgui_modal_new(source, modal) + +/** + * Registers a given modal to a source. Private. + * + * Arguments: + * * source - The source datum + * * modal - The datum/tgui_modal to register + * * replace_previous - Whether any modal currently assigned to source should be replaced + * * instant_update - Whether the changes should reflect immediately + */ +/datum/proc/tgui_modal_new(datum/source = src, datum/tgui_modal/modal = null, replace_previous = TRUE, instant_update = TRUE) + ASSERT(istype(source)) + ASSERT(istype(modal)) + + var/datum/tgui_modal/previous = LAZYACCESS(GLOB.tgui_modals, source.UID()) + if(previous && !replace_previous) + return FALSE + + modal.owning_source = source + + // Previous one should get GC'd + LAZYSET(GLOB.tgui_modals, source.UID(), modal) + if(instant_update) + SStgui.update_uis(source) + return TRUE + +/** + * Calls the source's currently assigned modal's (if there is one) on_answer() proc. Private. + * + * Arguments: + * * source - The source datum + * * id - The ID of the modal + * * answer - The provided answer + */ +/datum/proc/tgui_modal_answer(datum/source = src, id, answer = "") + ASSERT(istype(source)) + + var/datum/tgui_modal/current = LAZYACCESS(GLOB.tgui_modals, source.UID()) + if(!current) + return FALSE + + return current.on_answer(answer) + +/** + * Passes an answer from JS through the modal's proc. + * + * Used namely for cutting the text short if it's longer + * than an input modal's max_length. + * Arguments: + * * source - The source datum + * * answer - The provided answer + */ +/datum/proc/tgui_modal_preprocess_answer(datum/source = src, answer = "") + ASSERT(istype(source)) + + var/datum/tgui_modal/current = LAZYACCESS(GLOB.tgui_modals, source.UID()) + if(!current) + return answer + + return current.preprocess_answer(answer) + +/** + * Modal datum (contains base information for a modal) + */ +/datum/tgui_modal + var/datum/owning_source + var/id + var/text + var/delegate + var/list/arguments + var/modal_type = "message" + +/datum/tgui_modal/New(id, text, delegate, list/arguments) + src.id = id + src.text = text + src.delegate = delegate + src.arguments = arguments + +/** + * Called when it's time to pre-process the answer before using it + * + * Arguments: + * * answer - The answer, a nullable text + */ +/datum/tgui_modal/proc/preprocess_answer(answer) + return reject_bad_text(answer, TGUI_MODAL_INPUT_MAX_LENGTH) // bleh + +/** + * Called when a modal receives an answer + * + * Arguments: + * * answer - The answer, a nullable text + */ +/datum/tgui_modal/proc/on_answer(answer) + if(delegate) + return call(owning_source, delegate)(answer, arguments) + return FALSE + +/** + * Creates a list that describes a modal visually to be passed to JS + */ +/datum/tgui_modal/proc/to_data() + . = list() + .["id"] = id + .["text"] = text + .["args"] = arguments || list() + .["type"] = modal_type + +/** + * Input modal - has a text entry that can be used to enter an answer + */ +/datum/tgui_modal/input + modal_type = "input" + var/value + var/max_length + +/datum/tgui_modal/input/New(id, text, delegate, list/arguments, value, max_length) + ..(id, text, delegate, arguments) + src.value = value + src.max_length = max_length + +/datum/tgui_modal/input/preprocess_answer(answer) + . = ..(answer) + if(length(answer) > max_length) + . = copytext(., 1, max_length + 1) + +/datum/tgui_modal/input/to_data() + . = ..() + .["value"] = value + +/** + * Choice modal - has a dropdown menu that can be used to select an answer + */ +/datum/tgui_modal/input/choice + modal_type = "choice" + var/choices + +/datum/tgui_modal/input/choice/New(id, text, delegate, list/arguments, value, choices) + ..(id, text, delegate, arguments, value, TGUI_MODAL_INPUT_MAX_LENGTH) // Max length doesn't really matter in dropdowns, but whatever + src.choices = choices + +/datum/tgui_modal/input/choice/on_answer(answer) + if(answer in choices) // Make sure the answer is actually in our choices! + return ..(answer, arguments) + return FALSE + +/datum/tgui_modal/input/choice/to_data() + . = ..() + .["choices"] = choices + +/** + * Bento modal - Similar to choice, it displays the choices in a grid of images + * + * The returned answer is the index of the choice. + */ +/datum/tgui_modal/input/bento + modal_type = "bento" + var/choices + +/datum/tgui_modal/input/bento/New(id, text, delegate, list/arguments, value, choices) + ..(id, text, delegate, arguments, text2num(value), TGUI_MODAL_INPUT_MAX_LENGTH) // Max length doesn't really matter in here, but whatever + src.choices = choices + +/datum/tgui_modal/input/bento/preprocess_answer(answer) + return text2num(answer) || 0 + +/datum/tgui_modal/input/bento/on_answer(answer) + if(answer >= 1 && answer <= length(choices)) // Make sure the answer index is actually in our indexes! + return ..(answer, arguments) + return FALSE + +/datum/tgui_modal/input/bento/to_data() + . = ..() + .["choices"] = choices + +/** + * Boolean modal - has yes/no buttons that do different actions depending on which is pressed + */ +/datum/tgui_modal/boolean + modal_type = "boolean" + var/delegate_no + var/yes_text + var/no_text + +/datum/tgui_modal/boolean/New(id, text, delegate, delegate_no, list/arguments, yes_text, no_text) + ..(id, text, delegate, arguments) + src.delegate_no = delegate_no + src.yes_text = yes_text + src.no_text = no_text + +/datum/tgui_modal/boolean/preprocess_answer(answer) + return text2num(answer) || FALSE + +/datum/tgui_modal/boolean/on_answer(answer) + if(answer) + return ..(answer, arguments) + else if(delegate_no) + return call(owning_source, delegate_no)(arguments) + return FALSE + +/datum/tgui_modal/boolean/to_data() + . = ..() + .["yes_text"] = yes_text + .["no_text"] = no_text diff --git a/icons/mob/inhands/clothing_lefthand.dmi b/icons/mob/inhands/clothing_lefthand.dmi index 589135b8701..379c727930f 100644 Binary files a/icons/mob/inhands/clothing_lefthand.dmi and b/icons/mob/inhands/clothing_lefthand.dmi differ diff --git a/icons/mob/inhands/clothing_righthand.dmi b/icons/mob/inhands/clothing_righthand.dmi index 8fd5f7d71b8..2fc6aa9c4c1 100644 Binary files a/icons/mob/inhands/clothing_righthand.dmi and b/icons/mob/inhands/clothing_righthand.dmi differ diff --git a/icons/mob/inhands/equipment/instruments_lefthand.dmi b/icons/mob/inhands/equipment/instruments_lefthand.dmi index 225f1768d99..aa3608fec28 100644 Binary files a/icons/mob/inhands/equipment/instruments_lefthand.dmi and b/icons/mob/inhands/equipment/instruments_lefthand.dmi differ diff --git a/icons/mob/inhands/equipment/instruments_righthand.dmi b/icons/mob/inhands/equipment/instruments_righthand.dmi index cc1df97183a..28085ff746d 100644 Binary files a/icons/mob/inhands/equipment/instruments_righthand.dmi and b/icons/mob/inhands/equipment/instruments_righthand.dmi differ diff --git a/icons/obj/ammo.dmi b/icons/obj/ammo.dmi index 4a250a1fffc..b1876a7d063 100644 Binary files a/icons/obj/ammo.dmi and b/icons/obj/ammo.dmi differ diff --git a/icons/obj/atmos.dmi b/icons/obj/atmos.dmi index 2209f3bd769..b2668980aec 100644 Binary files a/icons/obj/atmos.dmi and b/icons/obj/atmos.dmi differ diff --git a/icons/obj/computer.dmi b/icons/obj/computer.dmi index ac416274ebe..042b5fe7f38 100644 Binary files a/icons/obj/computer.dmi and b/icons/obj/computer.dmi differ diff --git a/icons/obj/guns/projectile.dmi b/icons/obj/guns/projectile.dmi index f1d09247bbd..1e61cebb799 100644 Binary files a/icons/obj/guns/projectile.dmi and b/icons/obj/guns/projectile.dmi differ diff --git a/icons/obj/musician.dmi b/icons/obj/musician.dmi index 21078c58969..d55ecb7ebc0 100644 Binary files a/icons/obj/musician.dmi and b/icons/obj/musician.dmi differ diff --git a/nano/templates/adv_med.tmpl b/nano/templates/adv_med.tmpl deleted file mode 100644 index a1ca2815679..00000000000 --- a/nano/templates/adv_med.tmpl +++ /dev/null @@ -1,231 +0,0 @@ - - -{{if !data.occupied}} -

No occupant detected.

-{{else}} -

Occupant Data:

-
-
- Name: -
-
- {{:data.occupant.name}} -
-
-
-
- Health: -
- {{:helper.displayBar(data.occupant.health, -100, 100, (data.occupant.health >= 50) ? 'good' : (data.occupant.health >= 0) ? 'average' : 'bad')}} -
- {{:helper.smoothRound(data.occupant.health)}}% -
-
-
-
- Status: -
-
- {{if data.occupant.stat==0}} - Alive - {{else data.occupant.stat==1}} - Critical - {{else}} - Dead - {{/if}} -
-
- -
-
- Temperature: -
-
- {{:helper.smoothRound(data.occupant.bodyTempC)}}°C, {{:helper.smoothRound(data.occupant.bodyTempF)}}°F -
-
- {{if data.occupant.hasBorer}} - - Large growth detected in frontal lobe, possibly cancerous. Surgical removal is recommended.
- {{/if}} - {{if data.occupant.blind}} - Cataracts detected.
- {{/if}} - {{if data.occupant.colourblind}} - Photoreceptor abnormalities detected.
- {{/if}} - {{if data.occupant.nearsighted}} - Retinal misalignment detected.
- {{/if}} - -
- {{:helper.link('Print', 'print', {'print_p' : 1, 'name' : data.occupant.name})}} - {{:helper.link('Eject Occupant', 'arrow-circle-o-down', {'ejectify' : 1}, data.occupied ? null : 'disabled')}} -

- - Damage: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
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)}}
- {{if data.occupant.blood.hasBlood}} -
- Blood: -
-
Pulse:
{{:data.occupant.blood.pulse}} bpm
-
-
-
Blood Level:
- {{:helper.displayBar(data.occupant.blood.bloodLevel, 0, data.occupant.blood.bloodMax, (data.occupant.blood.percent <= 60) ? 'bad' : (data.occupant.blood.percent <= 90) ? 'average' : 'good' )}} -
{{:helper.smoothRound(data.occupant.blood.percent)}}%, {{:helper.smoothRound(data.occupant.blood.bloodLevel)}}cl
-
- {{/if}} -
- {{if data.occupant.hasVirus}} - - Viral pathogen detected in blood stream. -
- {{/if}} - {{if data.occupant.implant_len}} -
- Implants - - - {{for data.occupant.implant}} - - {{/for}} - -
{{:value.name}}
-
- {{/if}} - External Organs - - - - - - - - - - - {{for data.occupant.extOrgan}} - - - - - - - - {{/for}} - -
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}} -
-
- Internal Organs - - - - - - - - - {{for data.occupant.intOrgan}} - - - - - - {{/for}} - -
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}} -
-{{/if}} diff --git a/nano/templates/apc.tmpl b/nano/templates/apc.tmpl deleted file mode 100644 index 234074fe577..00000000000 --- a/nano/templates/apc.tmpl +++ /dev/null @@ -1,197 +0,0 @@ -
- {{if data.siliconUser}} -
- Interface Lock: -
-
- {{:helper.link('Engaged', 'lock', {'toggleaccess' : 1}, data.siliconLock ? 'selected' : null)}}{{:helper.link('Disengaged', 'unlock', {'toggleaccess' : 1}, data.malfStatus >= 2 ? 'linkOff' : (data.siliconLock ? null : 'selected'))}} -
-
- {{else}} - {{if data.locked}} - Swipe an ID card to unlock this interface - {{else}} - Swipe an ID card to lock this interface - {{/if}} - {{/if}} -
- -
- -

Power Status

- -
-
- Main Breaker: -
-
- {{if data.locked && !data.siliconUser}} - {{if data.isOperating}} - On - {{else}} - Off - {{/if}} - {{else}} - {{:helper.link('On', 'power-off', {'breaker' : 1}, data.isOperating ? 'selected' : null)}}{{:helper.link('Off', 'close', {'breaker' : 1}, data.isOperating ? null : 'selected')}} - {{/if}} -
-
- -
-
- External Power: -
-
- {{if data.externalPower == 2}} - Good - {{else data.externalPower == 1}} - Low - {{else}} - None - {{/if}} -
-
- -
-
- Power Cell: -
- {{if data.powerCellStatus == null}} -
- Power cell removed. -
- {{else}} - - {{:helper.displayBar(data.powerCellStatus, 0, 100, (data.powerCellStatus >= 50) ? 'good' : (data.powerCellStatus >= 25) ? 'average' : 'bad')}} -
- {{:helper.smoothRound(data.powerCellStatus, 1)}}% -
- {{/if}} -
- - {{if data.powerCellStatus != null}} -
-
- Charge Mode: -
-
- {{if data.locked && !data.siliconUser}} - {{if data.chargeMode}} - Auto - {{else}} - Off - {{/if}} - {{else}} - {{:helper.link('Auto', 'refresh', {'cmode' : 1}, data.chargeMode ? 'selected' : null)}}{{:helper.link('Off', 'close', {'cmode' : 1}, data.chargeMode ? null : 'selected')}} - {{/if}} -   - {{if data.chargingStatus > 1}} - [Fully Charged] - {{else data.chargingStatus == 1}} - [Charging] - {{else}} - [Not Charging] - {{/if}} -
-
- {{/if}} - - -

Power Channels

- - {{for data.powerChannels}} -
-
- {{:value.title}}: -
-
- {{:helper.smoothRound(value.powerLoad)}} W -
-
-    - {{if value.status <= 1}} - Off - {{else value.status >= 2}} - On - {{/if}} - {{if data.locked}} - {{if value.status == 1 || value.status == 3}} -   Auto - {{else}} -   Manual - {{/if}} - {{/if}} -
- {{if !data.locked || data.siliconUser}} -
- {{:helper.link('Auto', 'refresh', value.topicParams.auto, (value.status == 1 || value.status == 3) ? 'selected' : null)}} - {{:helper.link('On', 'power-off', value.topicParams.on, (value.status == 2) ? 'selected' : null)}} - {{:helper.link('Off', 'close', value.topicParams.off, (value.status == 0) ? 'selected' : null)}} -
- {{/if}} -
- {{/for}} - -
-
- Total Load: -
-
- {{:helper.smoothRound(data.totalLoad)}} W -
-
- -
 
- -
-
- Cover Lock: -
-
- {{if data.locked && !data.siliconUser}} - {{if data.coverLocked}} - Engaged - {{else}} - Disengaged - {{/if}} - {{else}} - {{:helper.link('Engaged', 'lock', {'lock' : 1}, data.coverLocked ? 'selected' : null)}}{{:helper.link('Disengaged', 'unlock', {'lock' : 1}, data.coverLocked ? null : 'selected')}} - {{/if}} -
-
- - {{if data.siliconUser}} -

System Overrides

- -
- {{:helper.link('Overload Lighting Circuit', 'lightbulb-o', {'overload' : 1})}} - {{if data.malfStatus == 1}} - {{:helper.link('Override Programming', 'file-text', {'malfhack' : 1})}} - {{else data.malfStatus == 2}} - {{:helper.link('Shunt Core Processes', 'arrow-down', {'occupyapc' : 1})}} - {{else data.malfStatus == 3}} - {{:helper.link('Return to Main Core', 'arrow-left', {'deoccupyapc' : 1})}} - {{else data.malfStatus == 4}} - {{:helper.link('Shunt Core Processes', 'arrow-down', {'occupyapc' : 1}, 'linkOff')}} - {{/if}} -
- {{/if}} - -
-
- Night Shift Lighting: -
-
- {{if data.locked}} - {{if data.nightshiftLights}} - On - {{else}} - Off - {{/if}} - {{else}} - {{:helper.link('Enabled', 'lightbulb-o', {'toggle_nightshift' : 1}, data.nightshiftLights ? 'selected' : null)}}{{:helper.link('Disabled', 'lightbulb-o', {'toggle_nightshift' : 1}, data.nightshiftLights ? null : 'selected')}} - {{/if}} -
-
- -
diff --git a/nano/templates/canister.tmpl b/nano/templates/canister.tmpl deleted file mode 100644 index 5aaa15d4fe3..00000000000 --- a/nano/templates/canister.tmpl +++ /dev/null @@ -1,158 +0,0 @@ -{{if data.menu == 0}} -

Tank Status

-
-
- Tank Label: -
-
-
{{:data.name}}
{{:helper.link('Relabel', 'pencil', {'choice' : 'menu', 'mode_target' : 1}, data.canLabel ? null : 'disabled')}} -
-
- -
-
- Tank Pressure: -
-
- {{:helper.smoothRound(data.tankPressure)}} kPa -
-
- -
-
- Port Status: -
-
- {{:data.portConnected ? 'Connected' : 'Disconnected'}} -
-
- -

Holding Tank Status

- {{if data.hasHoldingTank}} -
-
- Tank Label: -
-
-
{{:data.holdingTank.name}}
{{:helper.link('Eject', 'eject', {'remove_tank' : 1})}} -
-
- -
-
- Tank Pressure: -
-
- {{:helper.smoothRound(data.holdingTank.tankPressure)}} kPa -
-
- {{else}} -
No holding tank inserted.
-
 
- {{/if}} - - -

Release Valve Status

-
-
- Release Pressure: -
-
- {{:helper.displayBar(data.releasePressure, data.minReleasePressure, data.maxReleasePressure)}} -
- {{:helper.link('-', null, {'pressure_adj' : -1000}, (data.releasePressure > data.minReleasePressure) ? null : 'disabled')}} - {{:helper.link('-', null, {'pressure_adj' : -100}, (data.releasePressure > data.minReleasePressure) ? null : 'disabled')}} - {{:helper.link('-', null, {'pressure_adj' : -10}, (data.releasePressure > data.minReleasePressure) ? null : 'disabled')}} - {{:helper.link('-', null, {'pressure_adj' : -1}, (data.releasePressure > data.minReleasePressure) ? null : 'disabled')}} -
 {{:data.releasePressure}} kPa 
- {{:helper.link('+', null, {'pressure_adj' : 1}, (data.releasePressure < data.maxReleasePressure) ? null : 'disabled')}} - {{:helper.link('+', null, {'pressure_adj' : 10}, (data.releasePressure < data.maxReleasePressure) ? null : 'disabled')}} - {{:helper.link('+', null, {'pressure_adj' : 100}, (data.releasePressure < data.maxReleasePressure) ? null : 'disabled')}} - {{:helper.link('+', null, {'pressure_adj' : 1000}, (data.releasePressure < data.maxReleasePressure) ? null : 'disabled')}} -
-
-
- -
-
- Release Valve: -
-
- {{:helper.link('Open', 'unlock', {'toggle' : 1}, data.valveOpen ? 'selected' : null)}}{{:helper.link('Close', 'lock', {'toggle' : 1}, data.valveOpen ? null : 'selected')}} -
-
-{{else}} - {{:helper.link('Go back', 'home', {'choice' : 'menu', 'mode_target' : 0}, null)}} -

Relabel

-
- Name: -
-
-
{{:data.name}}
{{:helper.link('Rename', 'pencil', {'rename' : 1}, data.canLabel ? null : 'disabled')}} -
- - -

Colors

-
-
-
{{:data.colorContainer.prim.name}}
- {{for data.colorContainer.prim.options}} -
- {{if value.icon == data.canister_color.prim}} - {{:helper.link(value.name, '', {'choice' : data.colorContainer.prim.name, 'icon' : value.icon}, data.canLabel ? null : 'disabled', 'selected')}} - {{else}} - {{:helper.link(value.name, '', {'choice' : data.colorContainer.prim.name, 'icon' : value.icon}, data.canLabel ? null : 'disabled', null)}} - {{/if}} -
- {{/for}} -
-
-
{{:data.colorContainer.sec.name}}
- {{:helper.link("None", '', {'choice' : data.colorContainer.sec.name, 'icon' : "none"}, data.canLabel ? null : 'disabled', data.colorContainer.sec.anycolor ? null : 'selected')}} - {{for data.colorContainer.sec.options}} -
- {{if value.icon == data.canister_color.sec}} - {{:helper.link(value.name, '', {'choice' : data.colorContainer.sec.name, 'icon' : value.icon}, data.canLabel ? null : 'disabled', 'selected')}} - {{else}} - {{:helper.link(value.name, '', {'choice' : data.colorContainer.sec.name, 'icon' : value.icon}, data.canLabel ? null : 'disabled', null)}} - {{/if}} -
- {{/for}} -
-
-
{{:data.colorContainer.ter.name}}
- {{:helper.link("None", '', {'choice' : data.colorContainer.ter.name, 'icon' : "none"}, data.canLabel ? null : 'disabled', data.colorContainer.ter.anycolor ? null : 'selected')}} - {{for data.colorContainer.ter.options}} -
- {{if value.icon == data.canister_color.ter}} - {{:helper.link(value.name, '', {'choice' : data.colorContainer.ter.name, 'icon' : value.icon}, data.canLabel ? null : 'disabled', 'selected')}} - {{else}} - {{:helper.link(value.name, '', {'choice' : data.colorContainer.ter.name, 'icon' : value.icon}, data.canLabel ? null : 'disabled', null)}} - {{/if}} -
- {{/for}} -
-
-
{{:data.colorContainer.quart.name}}
- {{:helper.link("None", '', {'choice' : data.colorContainer.quart.name, 'icon' : "none"}, data.canLabel ? null : 'disabled', data.colorContainer.quart.anycolor ? null : 'selected')}} - {{for data.colorContainer.quart.options}} -
- {{if value.icon == data.canister_color.quart}} - {{:helper.link(value.name, '', {'choice' : data.colorContainer.quart.name, 'icon' : value.icon}, data.canLabel ? null : 'disabled', 'selected')}} - {{else}} - {{:helper.link(value.name, '', {'choice' : data.colorContainer.quart.name, 'icon' : value.icon}, data.canLabel ? null : 'disabled', null)}} - {{/if}} -
- {{/for}} -
-
-

Decals

-
-
Pick and choose:
-
- {{for data.possibleDecals}} - {{:helper.link(value.name, '', {'choice' : 'decals', 'icon' : value.icon}, data.canLabel ? null : 'disabled', value.active ? 'selected' : null)}} - {{/for}} -
-
-{{/if}} \ No newline at end of file diff --git a/nano/templates/chem_dispenser.tmpl b/nano/templates/chem_dispenser.tmpl deleted file mode 100644 index 74d694530c9..00000000000 --- a/nano/templates/chem_dispenser.tmpl +++ /dev/null @@ -1,93 +0,0 @@ - -
-
- Energy: -
-
- {{:helper.displayBar(data.energy, 0, data.maxEnergy, 'good', data.energy + ' Units')}} -
-
- -
-
- Dispense: -
-
- {{:helper.link('1', 'gear', {'amount' : 1}, (data.amount == 1) ? 'selected' : null)}} - {{:helper.link('5', 'gear', {'amount' : 5}, (data.amount == 5) ? 'selected' : null)}} - {{:helper.link('10', 'gear', {'amount' : 10}, (data.amount == 10) ? 'selected' : null)}} - {{:helper.link('20', 'gear', {'amount' : 20}, (data.amount == 20) ? 'selected' : null)}} - {{:helper.link('30', 'gear', {'amount' : 30}, (data.amount == 30) ? 'selected' : null)}} - {{:helper.link('50', 'gear', {'amount' : 50}, (data.amount == 50) ? 'selected' : null)}} -
-
-
 
-
-
- {{if data.glass}} - Drink Dispenser - {{else}} - Chemical Dispenser - {{/if}} -
-
-
-
- {{for data.chemicals}} - {{:helper.link(value.title, 'arrow-circle-down', value.commands, null, data.glass ? 'fixedLeftWide' : 'fixedLeft')}} - {{/for}} - -
-
-
 
-
-
- {{if data.glass}} - Glass - {{else}} - Beaker - {{/if}} Contents -
-
- {{:helper.link(data.glass ? 'Eject Glass' : 'Eject Beaker', 'eject', {'ejectBeaker' : 1}, data.isBeakerLoaded ? null : 'disabled', 'floatRight')}} -
-
-
-
-
- {{if data.isBeakerLoaded}} - {{if data.beakerCurrentVolume}} - Dispose: - {{/if}} - Volume: {{:data.beakerCurrentVolume}} / {{:data.beakerMaxVolume}}
- {{for data.beakerContents}} -
-
- - {{:value.volume}} units of {{:value.name}} - -
-
- {{:helper.link('Isolate', null, {'remove' : value.id, 'removeamount' : -1})}} - {{:helper.link('1', null, {'remove' : value.id, 'removeamount' : 1})}} - {{:helper.link('5', null, {'remove' : value.id, 'removeamount' : 5})}} - {{:helper.link('10', null, {'remove' : value.id, 'removeamount' : 10})}} - {{:helper.link('ALL', null, {'remove' : value.id, 'removeamount' : value.volume})}} -
-
-
- {{empty}} - {{if data.glass}}Glass{{else}}Beaker{{/if}} is empty - - {{/for}} - {{else}} - - No {{if data.glass}}Glass{{else}}Beaker{{/if}} loaded - - {{/if}} -
-
-
diff --git a/nano/templates/chem_heater.tmpl b/nano/templates/chem_heater.tmpl deleted file mode 100644 index 9d9e16fa7e1..00000000000 --- a/nano/templates/chem_heater.tmpl +++ /dev/null @@ -1,47 +0,0 @@ - -
-
- Status: -
-
- {{:helper.link(data.isActive ? 'On' : 'Off', 'power-off', {'toggle_on' : 1}, data.isBeakerLoaded ? null : 'disabled')}} -
-
-
-
- Target: -
-
- {{:helper.link(data.targetTemp + 'K', 'gear', {'adjust_temperature' : 'input'}, null)}} -
-
-
-
- Beaker -
-
- {{:helper.link('Eject', 'eject', {'eject_beaker' : 1}, data.isBeakerLoaded ? null : 'disabled', 'floatRight')}} -
-
-
-
-
-
-
- {{if data.isBeakerLoaded}} - Volume: {{:helper.smoothRound(data.beakerCurrentVolume)}} / {{:data.beakerMaxVolume}}
- Temperature: {{:helper.smoothRound(data.currentTemp)}} Kelvin
- {{for data.beakerContents}} - {{:value.volume}} units of {{:value.name}}
- {{/for}} - {{else}} - No beaker loaded - {{/if}} -
-
-
-
-
\ No newline at end of file diff --git a/nano/templates/chem_master.tmpl b/nano/templates/chem_master.tmpl deleted file mode 100644 index e91a43d357b..00000000000 --- a/nano/templates/chem_master.tmpl +++ /dev/null @@ -1,106 +0,0 @@ -{{if data.beaker}} -
{{:helper.link('Eject Beaker and Clear Buffer', 'eject', {'eject': 1})}}
-
-

Add to buffer:

- {{for data.beaker_reagents}} -
-
{{:value.name}}, {{:value.volume}} units
- {{:helper.link('Analyze', 'search', {'analyze': 1, 'desc': value.description, 'name': value.name})}} - {{:helper.link('1', 'plus', {'add': value.id, 'amount': 1})}} - {{:helper.link('5', 'plus', {'add': value.id, 'amount': 5})}} - {{:helper.link('10', 'plus', {'add': value.id, 'amount': 10})}} - {{:helper.link('All', 'plus', {'add': value.id, 'amount': value.volume})}} - {{:helper.link('Custom', 'plus', {'addcustom': value.id})}} -
- {{/for}} -
-
-

-
- Transfer to {{:helper.link(data.mode ? 'beaker' : 'disposal', 'arrows-h', {'toggle': 1})}} -
-

- {{for data.buffer_reagents}} -
-
{{:value.name}}, {{:value.volume}} units
- {{:helper.link('Analyze', 'search', {'analyze': 1, 'desc': value.description, 'name': value.name})}} - {{:helper.link('1', 'plus', {'remove': value.id, 'amount': 1})}} - {{:helper.link('5', 'plus', {'remove': value.id, 'amount': 5})}} - {{:helper.link('10', 'plus', {'remove': value.id, 'amount': 10})}} - {{:helper.link('All', 'plus', {'remove': value.id, 'amount': value.volume})}} - {{:helper.link('Custom', 'plus', {'removecustom': value.id})}} -
- {{/for}} -
-
-

Containers:

-
- {{if data.condi}} - {{:helper.link('Create pack (10u max)', 'arrow-right', {'createpill': 1})}} - {{:helper.link('Create bottle (50u max)', 'arrow-right', {'createbottle': 1})}} - {{else}} - - - - - - - - - - - - - - - - -
- {{: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})}} - - -
- {{/if}} -
-
-{{else}} - No beaker loaded
-{{/if}} -
- - {{if data.loaded_pill_bottle}} - - - - - -
- {{: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})}} -
- {{else}} - - - - -
- {{:helper.link('No Pill Bottle', 'eject', {}, 'disabled')}} -
- {{/if}} -
-
\ No newline at end of file diff --git a/nano/templates/cloning_console.tmpl b/nano/templates/cloning_console.tmpl deleted file mode 100644 index c541ca6d6b4..00000000000 --- a/nano/templates/cloning_console.tmpl +++ /dev/null @@ -1,148 +0,0 @@ - -
-
-
Menu
-
- {{:helper.link('Main', 'home', {'menu' : 1}, data.menu == 1 ? 'selected' : '')}}{{:helper.link('Records', 'folder-open', {'menu' : 2}, data.menu == 2 ? 'selected' : '')}} -
-
-
-
Refresh
-
- {{:helper.link('Refresh', 'refresh', {'refresh' : 1})}} -
-
-
-
Autoprocessing
-
- {{if data.autoallowed}} - {{:helper.link('Enabled', 'check', {'task' : 'autoprocess'}, data.autoprocess ? 'selected' : '')}}{{:helper.link('Disabled', 'close', {'task' : 'stopautoprocess'}, !data.autoprocess ? 'selected' : '')}} - {{else}} - {{:helper.link('Enabled', 'check', null, 'disabled')}}{{:helper.link('Disabled', 'close', null, 'selected')}} - {{/if}} -
-
- {{if data.disk}} -
-
Disk
-
{{:helper.link('Eject', 'eject', {'disk' : 'eject'})}}
-
- {{/if}} -
-{{if data.menu == 1}} -

Modules

-
-
Scanner
-
- {{if data.scanner}} Found!{{else}} Error: Not detected!{{/if}} -
-
-
-
Pods
-
- {{if data.numberofpods}} Found {{:data.numberofpods}}!{{else}} Error: None detected!{{/if}} -
-
- {{if data.scanner}} -

Scanner Functions

- {{if data.loading}} - Scanning... - {{else}} - {{:data.scantemp}} - {{/if}} -
-
Scan Occupant
-
{{:helper.link('Scan', 'search', {'scan' : 1}, !data.occupant ? 'disabled' : '')}}
-
-
-
Scanner Lock
-
{{if data.occupant}}{{:helper.link('Engaged', 'lock', {'lock' : 1}, data.locked ? 'selected' : '')}}{{else}} {{:helper.link('Engaged', 'lock', null, 'disabled')}}{{/if}}{{:helper.link('Disengaged', 'unlock', {'lock' : 1}, !data.locked ? 'selected' : '')}}
-
- {{if data.can_brainscan}} -
-
Scan Mode
-
- {{:helper.link('Brain', null, {'toggle_mode' : 1}, !data.scan_mode ? '' : 'selected')}}{{:helper.link('Body', null, {'toggle_mode' : 1}, data.scan_mode ? '' : 'selected')}} -
-
- {{/if}} - {{/if}} - {{if data.numberofpods}} -

Pods

- {{for data.pods}} - {{:value.name}}
-
-
Biomass
-
- {{:value.biomass}} -
-
- -
-
Select
-
- {{:helper.link('Select', 'check', {'selectpod' : value.pod}, value.pod == data.selected_pod ? 'selected' : '')}} -
-
- - {{/for}} - {{/if}} -{{/if}} -{{if data.menu == 2}} -

Current Records

- {{if data.temp}} -
- {{:data.temp}} -
- {{/if}} - {{for data.records}} -
{{:helper.link(value.realname, 'arrow-circle-down', {'view_rec' : value.record})}}
- {{empty}} - No records found! - {{/for}} -{{/if}} -{{if data.menu == 3}} -

Selected Record

- {{if data.temp}} -
- {{:data.temp}} -
- {{/if}} - {{if !data.activerecord}} -
Error: Record not found!
- {{else}} -
- Real Name: {{:data.realname}}
- Health: {{:data.health}} (OXY - BURN - TOX - BRUTE) -
-
- UI: {{:data.unidentity}}
- SE:
{{:data.strucenzymes}}
-
- {{if data.disk}} -
-
Disk
-
{{:helper.link('Load from Disk', 'floppy-o', {'disk' : 'load'})}}{{:helper.link('Save UI + UE', 'arrow-circle-up', {'save_disk' : 'ue'})}}{{:helper.link('Save UI', 'arrow-circle-up', {'save_disk' : 'ui'})}}{{:helper.link('Save SE', 'arrow-circle-up', {'save_disk' : 'se'})}}
-
- {{/if}} -
-
Actions
-
{{:helper.link('Clone', 'arrow-right', {'clone' : data.activerecord}, !data.podready ? 'disabled' : '')}}{{:helper.link('Delete', 'trash', {'del_rec' : 1})}}
-
- {{/if}} -{{/if}} -{{if data.menu == 4}} -

Confirm Record Deletion

- {{if data.temp}} -
- {{:data.temp}} -
- {{/if}} -
- {{:helper.link('Scan Card and Confirm', 'trash', {'del_rec' : 1})}}{{:helper.link('Cancel', 'close', {'menu' : 3})}} -
-{{/if}} - diff --git a/nano/templates/cryo.tmpl b/nano/templates/cryo.tmpl deleted file mode 100644 index de949c855f2..00000000000 --- a/nano/templates/cryo.tmpl +++ /dev/null @@ -1,117 +0,0 @@ - -

Cryo Cell Status

- -
- {{if !data.hasOccupant}} -
Cell Unoccupied
- {{else}} -
- {{:data.occupant.name}}   - {{if data.occupant.stat == 0}} - Conscious - {{else data.occupant.stat == 1}} - Unconscious - {{else}} - DEAD - {{/if}} -
- - {{if data.occupant.stat < 2}} -
-
Health:
- {{if data.occupant.health >= 0}} - {{:helper.displayBar(data.occupant.health, 0, data.occupant.maxHealth, 'good')}} - {{else}} - {{:helper.displayBar(data.occupant.health, 0, data.occupant.minHealth, 'average alignRight')}} - {{/if}} -
{{:helper.smoothRound(data.occupant.health)}}
-
- -
-
Brute Damage:
- {{:helper.displayBar(data.occupant.bruteLoss, 0, data.occupant.maxHealth, 'bad')}} -
{{:helper.smoothRound(data.occupant.bruteLoss)}}
-
- -
-
Resp. Damage:
- {{:helper.displayBar(data.occupant.oxyLoss, 0, data.occupant.maxHealth, 'bad')}} -
{{:helper.smoothRound(data.occupant.oxyLoss)}}
-
- -
-
Toxin Damage:
- {{:helper.displayBar(data.occupant.toxLoss, 0, data.occupant.maxHealth, 'bad')}} -
{{:helper.smoothRound(data.occupant.toxLoss)}}
-
- -
-
Burn Severity:
- {{:helper.displayBar(data.occupant.fireLoss, 0, data.occupant.maxHealth, 'bad')}} -
{{:helper.smoothRound(data.occupant.fireLoss)}}
-
- {{/if}} - {{/if}} -
-
-
-
Cell Temperature:
- {{:helper.smoothRound(data.cellTemperature)}} K -
-
-
- -

Cryo Cell Operation

-
-
- Cryo Cell Status: -
-
- {{:helper.link('On', 'power-off', {'switchOn' : 1}, data.isOperating ? 'selected' : null)}}{{:helper.link('Off', 'close', {'switchOff' : 1}, data.isOperating ? null : 'selected')}} -
-
- {{:helper.link('Eject Occupant', 'arrow-circle-o-down', {'ejectOccupant' : 1}, data.hasOccupant ? null : 'disabled')}} -
-
-
 
-
-
- Beaker: -
-
- {{if data.isBeakerLoaded}} - {{:data.beakerLabel ? data.beakerLabel : 'No label'}}
- {{if data.beakerVolume}} - {{:helper.smoothRound(data.beakerVolume)}} units remaining
- {{else}} - Beaker is empty - {{/if}} - {{else}} - No beaker loaded - {{/if}} -
-
- {{:helper.link('Eject Beaker', 'eject', {'ejectBeaker' : 1}, data.isBeakerLoaded ? null : 'disabled')}} -
-
- -
 
-
-
- Auto-eject healthy patients: -
-
- {{:helper.link('On', 'power-off', {'auto_eject_healthy_on' : 1}, data.auto_eject_healthy ? 'selected' : null)}}{{:helper.link('Off', 'close', {'auto_eject_healthy_off' : 0}, data.auto_eject_healthy ? null : 'selected')}} -
-
-
-
- Auto-eject dead patients: -
-
- {{:helper.link('On', 'power-off', {'auto_eject_dead_on' : 1}, data.auto_eject_dead ? 'selected' : null)}}{{:helper.link('Off', 'close', {'auto_eject_dead_off' : 0}, data.auto_eject_dead ? null : 'selected')}} -
-
diff --git a/nano/templates/dna_modifier.tmpl b/nano/templates/dna_modifier.tmpl deleted file mode 100644 index 2789a4a7881..00000000000 --- a/nano/templates/dna_modifier.tmpl +++ /dev/null @@ -1,313 +0,0 @@ - -

Status

- -
- {{if !data.hasOccupant}} -
Cell Unoccupied
- {{else}} -
- {{:data.occupant.name}} =>  - {{if data.occupant.stat == 0}} - Conscious - {{else data.occupant.stat == 1}} - Unconscious - {{else}} - DEAD - {{/if}} -
- - {{if !data.occupant.isViableSubject || !data.occupant.uniqueIdentity || !data.occupant.structuralEnzymes}} -
- The occupant's DNA structure is ruined beyond recognition, please insert a subject with an intact DNA structure. -
- {{else data.occupant.stat < 2}} -
-
Health:
- {{if data.occupant.health >= 0}} - {{:helper.displayBar(data.occupant.health, 0, data.occupant.maxHealth, 'good')}} - {{else}} - {{:helper.displayBar(data.occupant.health, 0, data.occupant.minHealth, 'average alignRight')}} - {{/if}} -
{{:helper.smoothRound(data.occupant.health)}}
-
- -
-
Radiation:
- {{:helper.displayBar(data.occupant.radiationLevel, 0, 100, 'average')}} -
{{:helper.smoothRound(data.occupant.radiationLevel)}}
-
- -
-
Unique Enzymes:
-
{{:data.occupant.uniqueEnzymes ? data.occupant.uniqueEnzymes : 'Unknown'}}
-
- - {{/if}} - {{/if}} -
-
- -

Operations

- {{if !data.occupant.isViableSubject || !data.occupant.uniqueIdentity || !data.occupant.structuralEnzymes}} -
- No operation possible on this subject -
- {{else}} -
- {{:helper.link('Modify U.I.', 'link', {'selectMenuKey' : 'ui'}, data.selectedMenuKey == 'ui' ? 'selected' : null)}} - {{:helper.link('Modify S.E.', 'link', {'selectMenuKey' : 'se'}, data.selectedMenuKey == 'se' ? 'selected' : null)}} - {{:helper.link('Transfer Buffers', 'floppy-o', {'selectMenuKey' : 'buffer'}, data.selectedMenuKey == 'buffer' ? 'selected' : null)}} - {{:helper.link('Rejuvenators', 'plus', {'selectMenuKey' : 'rejuvenators'}, data.selectedMenuKey == 'rejuvenators' ? 'selected' : null)}} -
- -
 
- - {{if !data.selectedMenuKey || data.selectedMenuKey == 'ui'}} -

Modify Unique Identifier

- {{:helper.displayDNABlocks(data.occupant.uniqueIdentity, data.selectedUIBlock, data.selectedUISubBlock, data.dnaBlockSize, 'UI')}} -
-
-
- Target: -
-
- {{:helper.link('-', null, {'changeUITarget' : 0}, (data.selectedUITarget > 0) ? null : 'disabled')}} -
 {{:data.selectedUITargetHex}} 
- {{:helper.link('+', null, {'changeUITarget' : 1}, (data.selectedUITarget < 15) ? null : 'disabled')}} -
-
-
-
- {{:helper.link('Irradiate Block', 'exclamation-circle', {'pulseUIRadiation' : 1}, !data.occupant.isViableSubject ? 'disabled' : null)}} -
-
- {{else data.selectedMenuKey == 'se'}} -

Modify Structural Enzymes

- {{:helper.displayDNABlocks(data.occupant.structuralEnzymes, data.selectedSEBlock, data.selectedSESubBlock, data.dnaBlockSize, 'SE')}} -
-
-
- {{:helper.link('Irradiate Block', 'exclamation-circle', {'pulseSERadiation' : 1}, !data.occupant.isViableSubject ? 'disabled' : null)}} -
-
- {{else data.selectedMenuKey == 'buffer'}} -

Transfer Buffers

- {{for data.buffers}} -

Buffer {{:(index + 1)}}

-
-
-
- Load Data: -
-
- {{:helper.link('Subject U.I.', 'link', {'bufferOption' : 'saveUI', 'bufferId' : (index + 1)}, !data.hasOccupant ? 'disabled' : null)}} - {{:helper.link('Subject U.I. + U.E.', 'link', {'bufferOption' : 'saveUIAndUE', 'bufferId' : (index + 1)}, !data.hasOccupant ? 'disabled' : null)}} - {{:helper.link('Subject S.E.', 'link', {'bufferOption' : 'saveSE', 'bufferId' : (index + 1)}, !data.hasOccupant ? 'disabled' : null)}} - {{:helper.link('From Disk', 'floppy-o', {'bufferOption' : 'loadDisk', 'bufferId' : (index + 1)}, (!data.hasDisk || !data.disk.data) ? 'disabled' : null)}} -
-
- {{if value.data}} -
-
- Label: -
-
- {{:helper.link(value.label, 'file', {'bufferOption' : 'changeLabel', 'bufferId' : (index + 1)})}} -
-
-
-
- Subject: -
-
- {{:value.owner ? value.owner : 'Unknown'}} -
-
-
-
- Stored Data: -
-
- {{:value.data == 'ui' ? 'Unique Identifiers' : 'Structural Enzymes'}} - {{:value.ue ? ' + Unique Enzymes' : ''}} -
-
- {{else}} -
-
- This buffer is empty. -
-
- {{/if}} -
-
- Options: -
-
- {{:helper.link('Clear', 'trash', {'bufferOption' : 'clear', 'bufferId' : (index + 1)}, !value.data ? 'disabled' : null)}} - {{:helper.link('Injector', data.isInjectorReady ? 'pencil' : 'clock-o', {'bufferOption' : 'createInjector', 'bufferId' : (index + 1)}, (!data.isInjectorReady || !value.data) ? 'disabled' : null)}} - {{:helper.link('Block Injector', data.isInjectorReady ? 'pencil' : 'clock-o', {'bufferOption' : 'createInjector', 'bufferId' : (index + 1), 'createBlockInjector' : 1}, (!data.isInjectorReady || !value.data) ? 'disabled' : null)}} - {{:helper.link('Transfer', 'exclamation-circle', {'bufferOption' : 'transfer', 'bufferId' : (index + 1)}, (!data.hasOccupant || !value.data) ? 'disabled' : null)}} - {{:helper.link('Save To Disk', 'floppy-o', {'bufferOption' : 'saveDisk', 'bufferId' : (index + 1)}, (!value.data || !data.hasDisk) ? 'disabled' : null)}} -
-
-
- {{/for}} - -

Data Disk

-
- {{if data.hasDisk}} - {{if data.disk.data}} -
-
- Label: -
-
- {{:data.disk.label ? data.disk.label : 'No Label'}} -
-
-
-
- Subject: -
-
- {{:data.disk.owner ? data.disk.owner : 'Unknown'}} -
-
-
-
- Stored Data: -
-
- {{:data.disk.data == 'ui' ? 'Unique Identifiers' : 'Structural Enzymes'}} - {{:data.disk.ue ? ' + Unique Enzymes' : ''}} -
-
- {{else}} -
-
- Disk is blank. -
-
- {{/if}} - {{else}} -
-
- No disk inserted. -
-
- {{/if}} -
-
- Options: -
-
- {{:helper.link('Wipe Disk', 'trash', {'bufferOption' : 'wipeDisk'}, (!data.hasDisk || !data.disk.data) ? 'disabled' : null)}} - {{:helper.link('Eject Disk', 'eject', {'bufferOption' : 'ejectDisk'}, !data.hasDisk ? 'disabled' : null)}} -
-
-
- {{else data.selectedMenuKey == 'rejuvenators'}} -

Rejuvenators

-
-
- Inject: -
-
- {{:helper.link('5', 'pencil', {'injectRejuvenators' : 5}, (!data.hasOccupant || !data.beakerVolume) ? 'disabled' : null)}} - {{:helper.link('10', 'pencil', {'injectRejuvenators' : 10}, (!data.hasOccupant || !data.beakerVolume) ? 'disabled' : null)}} - {{:helper.link('20', 'pencil', {'injectRejuvenators' : 20}, (!data.hasOccupant || !data.beakerVolume) ? 'disabled' : null)}} - {{:helper.link('30', 'pencil', {'injectRejuvenators' : 30}, (!data.hasOccupant || !data.beakerVolume) ? 'disabled' : null)}} - {{:helper.link('50', 'pencil', {'injectRejuvenators' : 50}, (!data.hasOccupant || !data.beakerVolume) ? 'disabled' : null)}} -
-
-
 
-
-
- Beaker: -
-
- {{if data.isBeakerLoaded}} - {{:data.beakerLabel ? data.beakerLabel : 'No label'}}
- {{if data.beakerVolume}} - {{:data.beakerVolume}} units remaining
- {{else}} - Beaker is empty - {{/if}} - {{else}} - No beaker loaded - {{/if}} -
-
- {{:helper.link('Eject Beaker', 'eject', {'ejectBeaker' : 1}, data.isBeakerLoaded ? null : 'disabled')}} -
-
- {{/if}} - -
 
- - {{if !data.selectedMenuKey || data.selectedMenuKey == 'ui' || data.selectedMenuKey == 'se'}} -

Radiation Emitter Settings

-
-
- Intensity: -
-
- {{:helper.link('-', null, {'radiationIntensity' : 0}, (data.radiationIntensity > 1) ? null : 'disabled')}} -
 {{:data.radiationIntensity}} 
- {{:helper.link('+', null, {'radiationIntensity' : 1}, (data.radiationIntensity < 10) ? null : 'disabled')}} -
-
-
-
- Duration: -
-
- {{:helper.link('-', null, {'radiationDuration' : 0}, (data.radiationDuration > 2) ? null : 'disabled')}} -
 {{:data.radiationDuration}} 
- {{:helper.link('+', null, {'radiationDuration' : 1}, (data.radiationDuration < 20) ? null : 'disabled')}} -
-
-
-
-   -
-
- {{:helper.link('Pulse Radiation', 'exclamation-circle', {'pulseRadiation' : 1}, !data.hasOccupant ? 'disabled' : null)}} -
-
- {{/if}} -{{/if}} -
 
- -
- -
-
- Occupant: -
-
- {{:helper.link('Eject Occupant', 'eject', {'ejectOccupant' : 1}, data.locked || !data.hasOccupant || data.irradiating ? 'disabled' : null)}} -
-
-
-
- Door Lock: -
-
- {{:helper.link('Engaged', 'lock', {'toggleLock' : 1}, data.locked ? 'selected' : !data.hasOccupant ? 'disabled' : null)}} - {{:helper.link('Disengaged', 'unlock', {'toggleLock' : 1}, !data.locked ? 'selected' : data.irradiating ? 'disabled' : null)}} -
-
- -{{if data.irradiating}} -
-
-

Irradiating Subject

-

For {{:data.irradiating}} seconds.

-
-
-{{/if}} diff --git a/nano/templates/door_control.tmpl b/nano/templates/door_control.tmpl deleted file mode 100644 index 3c8dc66afb7..00000000000 --- a/nano/templates/door_control.tmpl +++ /dev/null @@ -1,69 +0,0 @@ - - -
-
- Main power is - {{if data.main_power_loss == 0}} - online - {{else data.main_power_loss == -1}} - offline - {{else}} - offline for {{:data.main_power_loss}} second{{:data.main_power_loss == 1 ? '' : 's'}} - {{/if}}. -
-
- {{:helper.link(data.main_power_loss ? 'Disabled' : 'Disable', null, {'command' : 'main_power'}, data.main_power_loss == 0 ? null : 'disabled', data.main_power_loss == 0 ? 'redButton' : null)}} -
-
- -
-
- Backup power is - {{if data.backup_power_loss == 0}} - online - {{else data.backup_power_loss == -1}} - offline - {{else}} - offline for {{:data.backup_power_loss}} second{{:data.backup_power_loss == 1 ? '' : 's'}} - {{/if}}. -
-
- {{:helper.link(data.backup_power_loss ? 'Disabled' : 'Disable', null, {'command' : 'backup_power'}, data.backup_power_loss == 0 ? null : 'disabled', data.backup_power_loss == 0 ? 'redButton' : null)}} -
-
- -
-
- Electrified Status: -
-
- {{:helper.link('Offline' , null, {'command' : 'electrify_permanently', 'activate' : "0" }, data.electrified == 0 ? 'selected' : null)}} - {{:helper.link(data.electrified <= 0 ? 'Temporary (30s)' : 'Temporary (' + data.electrified +'s)', null, {'command' : 'electrify_temporary', 'activate' : "1"}, null, data.electrified > 0 ? 'redButton' : null)}} - {{:helper.link('Permanent', null, {'command' : 'electrify_permanently', 'activate' : "1"}, data.electrified == -1 ? 'redButton' : null)}} -
-
- -
- - - {{for data.commands}} - - - - - - {{/for}} -
{{: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)}}
diff --git a/nano/templates/med_data.tmpl b/nano/templates/med_data.tmpl deleted file mode 100644 index f47e13c6885..00000000000 --- a/nano/templates/med_data.tmpl +++ /dev/null @@ -1,171 +0,0 @@ - - - - -
-
- Confirm Identity: -
-
- {{:helper.link(data.scan ? data.scan : "----------", 'eject', {'scan' : 1}, null, data.scan ? 'itemContentWide' : 'fixedLeft')}} -
-
-
-{{if data.authenticated}} - {{if data.screen == 1}} -

Menu

-
-
{{:helper.link('Search Records', 'search', {'search' : 1})}}
-
{{:helper.link('List Records', 'list', {'screen' : 2})}}
-
{{:helper.link('Virus Database', 'database', {'screen' : 5})}}
-
{{:helper.link('Medbot Tracking', 'plus-square', {'screen' : 6})}}
-
{{:helper.link('Record Maintenance', 'wrench', {'screen' : 3})}}
-
{{:helper.link('Logout', 'lock', {'logout' : 1})}}
-
- {{else data.screen == 2}} -

Record List

- {{for data.records}} -
{{:helper.link(value.id + ': ' + value.name, 'user', {'d_rec' : value.ref})}}
- {{/for}} -

-
{{:helper.link('Back', 'arrow-left', {'screen' : 1})}}
- {{else data.screen == 3}} -

Records Maintenance

-
{{:helper.link('Backup To Disk', 'download', {'back' : 1}, 'disabled')}}
-
{{:helper.link('Upload From disk', 'upload', {'u_load' : 1}, 'disabled')}}
-
{{:helper.link('Delete All Records', 'trash', {'del_all' : 1})}}
-
{{:helper.link('Back', 'arrow-left', {'screen' : 1})}}
- {{else data.screen == 4}} -

Medical Record

-

General Data

- {{if data.general.empty}} -
General Record Lost!
- {{else}} -
- - - -
- {{for data.general.fields}} -
-
{{:value.field}}
-
{{:helper.link(value.value, value.edit ? 'pencil' : 'user-times', {'field' : value.edit}, value.edit ? null : 'disabled')}}
-
- {{/for}} -
- {{if data.general.has_photos}} - {{for data.general.photos}} - {{if value.photo}} - - {{/if}} - {{/for}} - {{/if}} -
-
- {{/if}} - -

Medical Data

- {{if data.medical.empty}} -
Medical Record Lost!
-
-
{{:helper.link('New Record', 'plus', {'new' : 1})}}
-
-

Menu

- {{else}} - {{for data.medical.fields}} -
-
{{:value.field}}
-
{{:helper.link(value.value, 'pencil', {'field' : value.edit})}}
-
- {{if value.line_break}} -
- {{/if}} - {{/for}} - -

Comments/Log

- {{for data.medical.comments}} -
{{:value}}
-
{{:helper.link('Remove entry', 'trash', {'del_c' : index})}}
-

- {{/for}} - -
-
{{:helper.link('Add Entry', 'plus', {'add_c' : 1})}}
-
- -

Menu

-
{{:helper.link('Delete Record (Medical Only)', 'trash', {'del_r' : 1})}}
- {{/if}} - -
{{:helper.link('Print Record', 'print', {'print_p' : 1})}}
-
{{:helper.link('Back', 'arrow-left', {'screen' : 2})}}
- {{else data.screen == 5}} -

Virus Database

- {{for data.virus}} -
{{:helper.link(value.name, 'arrow-right', {'vir' : value.D})}}
- {{/for}} -

- {{:helper.link('Back', 'arrow-left', {'screen' : 1})}} - {{else data.screen == 6}} -

Medical Robot Monitor

- {{for data.medbots}} -

{{:value.name}}

-
-
Location:
-
{{:value.x}}, {{:value.y}}
-
-
-
Status:
-
- {{:value.on ? "Online" : "Offline"}}. {{if value.use_beaker}}Reservoir: [{{:value.total_volume}}/{{:value.maximum_volume}}]{{else}}Using internal synthesizer.{{/if}} -
-
-
- {{empty}} -

None detected!

- {{/for}} -
{{:helper.link('Back', 'arrow-left', {'screen' : 1})}}
- {{/if}} -{{else}} -

Menu

- {{:helper.link('Login', 'unlock', {'login' : 1})}} -{{/if}} - -{{if data.temp}} -
-
- {{if data.temp.notice}} -
{{:data.temp.text}}
- {{else}} -
{{:data.temp.text}}
- {{/if}} - - - {{if data.temp.has_buttons}} -
-
-
- {{for data.temp.buttons}} - {{:helper.link(value.name, value.icon, {'temp' : 1, 'temp_action' : value.href}, value.status)}} - {{/for}} -
-
-
- {{/if}} - -
-
{{:helper.link('Clear screen', 'home', {'temp' : 1})}}
-
-
-
-{{/if}} \ No newline at end of file diff --git a/nano/templates/op_computer.tmpl b/nano/templates/op_computer.tmpl deleted file mode 100644 index 77894bcba16..00000000000 --- a/nano/templates/op_computer.tmpl +++ /dev/null @@ -1,215 +0,0 @@ - -

Patient Monitor

- -
- {{if data.choice==0}} - {{if !data.hasOccupant}} -
No Patient Detected
- {{else}} -
- {{:data.occupant.name}} =>  - {{if data.occupant.stat == 0}} - Conscious - {{else data.occupant.stat == 1}} - Unconscious - {{else}} - DEAD - {{/if}} -
- -
-
Health:
- {{if data.occupant.health >= data.occupant.maxHealth}} - {{:helper.displayBar(data.occupant.health, 0, data.occupant.maxHealth, 'good')}} - {{else data.occupant.health > 0}} - {{:helper.displayBar(data.occupant.health, 0, data.occupant.maxHealth, 'average')}} - {{else data.occupant.health >= data.minhealth}} - {{:helper.displayBar(data.occupant.health, 0, data.occupant.minHealth, 'average alignRight')}} - {{else}} - {{:helper.displayBar(data.occupant.health, 0, data.occupant.minHealth, 'bad alignRight')}} - {{/if}} -
{{:helper.smoothRound(data.occupant.health)}}
-
- -
-
Brute Damage:
- {{:helper.displayBar(data.occupant.bruteLoss, 0, data.occupant.maxHealth, 'bad')}} -
{{:helper.smoothRound(data.occupant.bruteLoss)}}
-
- -
-
Resp. Damage:
- {{:helper.displayBar(data.occupant.oxyLoss, 0, data.occupant.maxHealth, 'bad')}} -
{{:helper.smoothRound(data.occupant.oxyLoss)}}
-
- -
-
Toxin Damage:
- {{:helper.displayBar(data.occupant.toxLoss, 0, data.occupant.maxHealth, 'bad')}} -
{{:helper.smoothRound(data.occupant.toxLoss)}}
-
- -
-
Burn Severity:
- {{:helper.displayBar(data.occupant.fireLoss, 0, data.occupant.maxHealth, 'bad')}} -
{{:helper.smoothRound(data.occupant.fireLoss)}}
-
- -
-
Patient Temperature:
- {{if data.occupant.temperatureSuitability == -3}} - {{:helper.displayBar(data.occupant.bodyTemperature, 0, data.occupant.maxTemp, 'bad')}} -
{{:helper.smoothRound(data.occupant.btCelsius)}}°C, {{:helper.smoothRound(data.occupant.btFaren)}}°F
- {{else data.occupant.temperatureSuitability == -2}} - {{:helper.displayBar(data.occupant.bodyTemperature, 0, data.occupant.maxTemp, 'average')}} -
{{:helper.smoothRound(data.occupant.btCelsius)}}°C, {{:helper.smoothRound(data.occupant.btFaren)}}°F
- {{else data.occupant.temperatureSuitability == -1}} - {{:helper.displayBar(data.occupant.bodyTemperature, 0, data.occupant.maxTemp, 'average')}} -
{{:helper.smoothRound(data.occupant.btCelsius)}}°C, {{:helper.helper.smoothRound(data.occupant.btFaren)}}°F
- {{else data.occupant.temperatureSuitability == 0}} - {{:helper.displayBar(data.occupant.bodyTemperature, 0, data.occupant.maxTemp, 'good')}} -
{{:helper.smoothRound(data.occupant.btCelsius)}}°C, {{:helper.smoothRound(data.occupant.btFaren)}}°F
- {{else data.occupant.temperatureSuitability == 1}} - {{:helper.displayBar(data.occupant.bodyTemperature, 0, data.occupant.maxTemp, 'average')}} -
{{:helper.smoothRound(data.occupant.btCelsius)}}°C, {{:helper.smoothRound(data.occupant.btFaren)}}°F
- {{else data.occupant.temperatureSuitability == 2}} - {{:helper.displayBar(data.occupant.bodyTemperature, 0, data.occupant.maxTemp, 'average')}} -
{{:helper.smoothRound(data.occupant.btCelsius)}}°C, {{:helper.smoothRound(data.occupant.btFaren)}}°F
- {{else data.occupant.temperatureSuitability == 3}} - {{:helper.displayBar(data.occupant.bodyTemperature, 0, data.occupant.maxTemp, 'bad')}} -
{{:helper.smoothRound(data.occupant.btCelsius)}}°C, {{:helper.smoothRound(data.occupant.btFaren)}}°F
- {{/if}} -
- {{if data.occupant.hasBlood}} -
-
-
Blood Level:
- {{if data.occupant.bloodPercent <= 60}} - {{:helper.displayBar(data.occupant.bloodLevel, 0, data.occupant.bloodMax, 'bad')}} -
- {{:helper.smoothRound(data.occupant.bloodPercent)}}%, {{:helper.smoothRound(data.occupant.bloodLevel)}}cl -
- {{else data.occupant.bloodPercent <= 90}} - {{:helper.displayBar(data.occupant.bloodLevel, 0, data.occupant.bloodMax, 'average')}} -
- {{:helper.smoothRound(data.occupant.bloodPercent)}}%, {{:helper.smoothRound(data.occupant.bloodLevel)}}cl -
- {{else}} - {{:helper.displayBar(data.occupant.bloodLevel, 0, data.occupant.bloodMax, 'good')}} -
- {{:helper.smoothRound(data.occupant.bloodPercent)}}%, {{:helper.smoothRound(data.occupant.bloodLevel)}}cl -
- {{/if}} -
-
Blood Type:
{{:data.occupant.bloodType}}
-
-
-
-
Pulse:
{{:data.occupant.pulse}} bpm
-
- {{/if}} - {{if data.occupant.inSurgery}} -
-
Initiated Surgery Procedure:
{{:data.occupant.surgeryName}}
-
-
Next Step:
{{:data.occupant.stepName}}
- -
- {{/if}} -
-
- {{/if}} -
-
- {{:helper.link('Options', 'gear', {'choiceOn' : 1})}} -
-
- {{else}} -
-
- Loudspeaker: -
-
- {{:helper.link('On', 'power-off', {'verboseOn' : 1}, data.verbose ? 'selected' : null)}} - {{:helper.link('Off', 'close', {'verboseOff' : 1}, data.verbose ? null : 'selected')}} -
-
-
-
-
- Health Announcer: -
-
- {{:helper.link('On', 'power-off', {'healthOn' : 1}, data.health ? 'selected' : null)}} - {{:helper.link('Off', 'close', {'healthOff' : 1}, data.health ? null : 'selected')}} -
-
- -
-
-
Health Announcer Threshold:
- {{:helper.link('-', null, {'health_adj' : -100}, (data.healthAlarm >= 0) ? null : 'disabled')}} - {{:helper.link('-', null, {'health_adj' : -10}, (data.healthAlarm >= -90) ? null : 'disabled')}} - {{:helper.link('-', null, {'health_adj' : -1}, (data.healthAlarm > -100) ? null : 'disabled')}} - - {{if data.healthAlarm >= 100}} - {{:helper.displayBar(data.healthAlarm, 0, 100, 'good')}} - {{else data.healthAlarm > 0}} - {{:helper.displayBar(data.healthAlarm, 0, 100, 'average')}} - {{else data.healthAlarm >= -100}} - {{:helper.displayBar(data.healthAlarm, 0, -100, 'average alignRight')}} - {{else}} - {{:helper.displayBar(data.healthAlarm, 0, -100, 'bad alignRight')}} - {{/if}} - - {{:helper.link('+', null, {'health_adj' : 1}, (data.healthAlarm < 100) ? null : 'disabled')}} - {{:helper.link('+', null, {'health_adj' : 10}, (data.healthAlarm <= 90) ? null : 'disabled')}} - {{:helper.link('+', null, {'health_adj' : 100}, (data.healthAlarm <= 0) ? null : 'disabled')}} -
 {{: data.healthAlarm }}  
-
-
-
-
-
- Oxygen Alarm: -
-
- {{:helper.link('On', 'power-off', {'oxyOn' : 1}, data.oxy ? 'selected' : null)}} - {{:helper.link('Off', 'close', {'oxyOff' : 1}, data.oxy ? null : 'selected')}} -
-
- -
-
-
Oxygen Alert Threshold:
- {{:helper.link('-', null, {'oxy_adj' : -100}, (data.oxyAlarm >= 100) ? null : 'disabled')}} - {{:helper.link('-', null, {'oxy_adj' : -10}, (data.oxyAlarm >= 10) ? null : 'disabled')}} - {{:helper.link('-', null, {'oxy_adj' : -1}, (data.oxyAlarm >= 0) ? null : 'disabled')}} - {{:helper.displayBar( data.oxyAlarm, 0, 200, 'good')}} - {{:helper.link('+', null, {'oxy_adj' : 1}, (data.oxyAlarm < 200) ? null : 'disabled')}} - {{:helper.link('+', null, {'oxy_adj' : 10}, (data.oxyAlarm <= 190) ? null : 'disabled')}} - {{:helper.link('+', null, {'oxy_adj' : 100}, (data.oxyAlarm <= 100) ? null : 'disabled')}} -
 {{: data.oxyAlarm }}  
-
-
-
-
-
- Critical Alert: -
-
- {{:helper.link('On', 'power-off', {'critOn' : 1}, data.crit ? 'selected' : null)}} - {{:helper.link('Off', 'close', {'critOff' : 1}, data.crit ? null : 'selected')}} -
-
-
-
-
- {{:helper.link('Return', 'arrow-left', {'choiceOff' : 1})}} -
-
- {{/if}} -
diff --git a/nano/templates/sleeper.tmpl b/nano/templates/sleeper.tmpl deleted file mode 100644 index cacf6c83ff5..00000000000 --- a/nano/templates/sleeper.tmpl +++ /dev/null @@ -1,211 +0,0 @@ - -

Sleeper Status

- -
- {{if !data.hasOccupant}} -
Sleeper Unoccupied
- {{else}} -
- {{:data.occupant.name}} =>  - {{if data.occupant.stat == 0}} - Conscious - {{else data.occupant.stat == 1}} - Unconscious - {{else}} - DEAD - {{/if}} -
- -
-
Health:
- {{if data.occupant.health >= data.occupant.maxHealth}} - {{:helper.displayBar(data.occupant.health, 0, data.occupant.maxHealth, 'good')}} - {{else data.occupant.health > 0}} - {{:helper.displayBar(data.occupant.health, 0, data.occupant.maxHealth, 'average')}} - {{else data.occupant.health >= data.minhealth}} - {{:helper.displayBar(data.occupant.health, 0, data.occupant.minHealth, 'average alignRight')}} - {{else}} - {{:helper.displayBar(data.occupant.health, 0, data.occupant.minHealth, 'bad alignRight')}} - {{/if}} -
{{:helper.smoothRound(data.occupant.health)}}
-
- -
-
=> Brute Damage:
- {{:helper.displayBar(data.occupant.bruteLoss, 0, data.occupant.maxHealth, 'bad')}} -
{{:helper.smoothRound(data.occupant.bruteLoss)}}
-
- -
-
=> Resp. Damage:
- {{:helper.displayBar(data.occupant.oxyLoss, 0, data.occupant.maxHealth, 'bad')}} -
{{:helper.smoothRound(data.occupant.oxyLoss)}}
-
- -
-
=> Toxin Damage:
- {{:helper.displayBar(data.occupant.toxLoss, 0, data.occupant.maxHealth, 'bad')}} -
{{:helper.smoothRound(data.occupant.toxLoss)}}
-
- -
-
=> Burn Severity:
- {{:helper.displayBar(data.occupant.fireLoss, 0, data.occupant.maxHealth, 'bad')}} -
{{:helper.smoothRound(data.occupant.fireLoss)}}
-
- -
-
- -
Patient Temperature:
- {{if data.occupant.temperatureSuitability == -3}} - {{:helper.displayBar(data.occupant.bodyTemperature, 0, data.occupant.maxTemp, 'bad')}} -
{{:helper.smoothRound(data.occupant.btCelsius)}}°C, {{:helper.smoothRound(data.occupant.btFaren)}}°F
- {{else data.occupant.temperatureSuitability == -2}} - {{:helper.displayBar(data.occupant.bodyTemperature, 0, data.occupant.maxTemp, 'average')}} -
{{:helper.smoothRound(data.occupant.btCelsius)}}°C, {{:helper.smoothRound(data.occupant.btFaren)}}°F
- {{else data.occupant.temperatureSuitability == -1}} - {{:helper.displayBar(data.occupant.bodyTemperature, 0, data.occupant.maxTemp, 'average')}} -
{{:helper.smoothRound(data.occupant.btCelsius)}}°C, {{:helper.smoothRound(data.occupant.btFaren)}}°F
- {{else data.occupant.temperatureSuitability == 0}} - {{:helper.displayBar(data.occupant.bodyTemperature, 0, data.occupant.maxTemp, 'good')}} -
{{:helper.smoothRound(data.occupant.btCelsius)}}°C, {{:helper.smoothRound(data.occupant.btFaren)}}°F
- {{else data.occupant.temperatureSuitability == 1}} - {{:helper.displayBar(data.occupant.bodyTemperature, 0, data.occupant.maxTemp, 'average')}} -
{{:helper.smoothRound(data.occupant.btCelsius)}}°C, {{:helper.smoothRound(data.occupant.btFaren)}}°F
- {{else data.occupant.temperatureSuitability == 2}} - {{:helper.displayBar(data.occupant.bodyTemperature, 0, data.occupant.maxTemp, 'average')}} -
{{:helper.smoothRound(data.occupant.btCelsius)}}°C, {{:helper.smoothRound(data.occupant.btFaren)}}°F
- {{else data.occupant.temperatureSuitability == 3}} - {{:helper.displayBar(data.occupant.bodyTemperature, 0, data.occupant.maxTemp, 'bad')}} -
{{:helper.smoothRound(data.occupant.btCelsius)}}°C, {{:helper.smoothRound(data.occupant.btFaren)}}°F
- {{/if}} -
- - - {{if data.occupant.hasBlood}} - -
-
-
Pulse:
{{:data.occupant.pulse}} bpm
-
-
-
Blood Level:
- {{if data.occupant.bloodPercent <= 60}} - {{:helper.displayBar(data.occupant.bloodLevel, 0, data.occupant.bloodMax, 'bad')}} -
- {{:helper.smoothRound(data.occupant.bloodPercent)}}%, {{:helper.smoothRound(data.occupant.bloodLevel)}}cl -
- {{else data.occupant.bloodPercent <= 90}} - {{:helper.displayBar(data.occupant.bloodLevel, 0, data.occupant.bloodMax, 'average')}} -
- {{:helper.smoothRound(data.occupant.bloodPercent)}}%, {{:helper.smoothRound(data.occupant.bloodLevel)}}cl -
- {{else}} - {{:helper.displayBar(data.occupant.bloodLevel, 0, data.occupant.bloodMax, 'good')}} -
- {{:helper.smoothRound(data.occupant.bloodPercent)}}%, {{:helper.smoothRound(data.occupant.bloodLevel)}}cl -
- {{/if}} -
- {{/if}} - {{/if}} -
- -

Sleeper Operation

-
-
- Sleeper Status: -
-
- {{:helper.link('Eject Occupant', 'arrow-circle-o-down', {'ejectify' : 1}, data.hasOccupant ? null : 'disabled')}} -
-
-
 
-
-
- Dialysis Beaker: -
-
- {{if data.isBeakerLoaded}} - {{:helper.smoothRound(data.beakerFreeSpace)}} units of space remaining
- {{else}} - No Dialysis Output Beaker Loaded - {{/if}} -
-
- {{:helper.link('Eject Beaker', 'eject', {'removebeaker' : 1}, data.isBeakerLoaded ? null : 'disabled')}} - {{if data.isBeakerLoaded}} -
-
- {{:helper.link('On', 'power-off', {'togglefilter' : 1}, data.occupant.hasBlood ? (data.dialysis ? 'selected' : null) : 'disabled')}}{{:helper.link('Off', 'close', {'togglefilter' : 1}, data.dialysis ? null : 'selected')}} -
-
- {{/if}} -
-
-
-
- Chemicals: -
- - {{for data.chemicals}} -
{{:value.title}}
-
- {{if value.overdosing}} - {{:helper.displayBar(value.occ_amount, 0, data.maxchem, 'bad')}} - {{else value.od_warning}} - {{:helper.displayBar(value.occ_amount, 0, data.maxchem, 'average')}} - {{else}} - {{:helper.displayBar(value.occ_amount, 0, data.maxchem, 'good')}} - {{/if}} -
{{:helper.smoothRound(value.pretty_amount)}}/{{:data.maxchem}}
-
-
-
-
- {{:helper.link('5', 'gear', {'chemical' : value.id, 'amount' : 5}, (!value.injectable || ((value.occ_amount + 5) > data.maxchem)) ? 'disabled' : null)}} - {{:helper.link('10', 'gear', {'chemical' : value.id, 'amount' : 10}, (!value.injectable || ((value.occ_amount + 10) > data.maxchem)) ? 'disabled' : null)}} -
-
- {{/for}} - -
 
-
-
- Auto-eject dead patients: -
-
- {{:helper.link('On', 'power-off', {'auto_eject_dead_on' : 1}, data.auto_eject_dead ? 'selected' : null)}}{{:helper.link('Off', 'close', {'auto_eject_dead_off' : 0}, data.auto_eject_dead ? null : 'selected')}} -
-
-
- diff --git a/nano/templates/slotmachine.tmpl b/nano/templates/slotmachine.tmpl deleted file mode 100644 index 29545450a14..00000000000 --- a/nano/templates/slotmachine.tmpl +++ /dev/null @@ -1,34 +0,0 @@ - -{{if data.money != null}} -
- {{:data.plays}} players have tried their luck today! -
-
-
Credits Remaining:
- {{:helper.string("
{1}
", data.money >= 10 ? "" : "bad", helper.smoothRound(data.money))}} -
-
-
- Ten credits to play! -
-
- {{:helper.link('SPIN!', 'refresh', {'ops' : 1}, data.money >= 10 && !data.working ? null : 'disabled')}} -
-
- {{if data.result}} -
- {{:data.result}} -
- {{/if}} - {{if data.working}} -
Spinning!
- {{/if}} -{{else}} -
- Could not scan your card or could not find account!
- Please wear or hold your ID and try again. -
-{{/if}} diff --git a/nano/templates/song.tmpl b/nano/templates/song.tmpl deleted file mode 100644 index 28ccae0c923..00000000000 --- a/nano/templates/song.tmpl +++ /dev/null @@ -1,89 +0,0 @@ - - -
-
-
Playback
-
- {{:helper.link('New', 'file', {'newsong': 1})}} - {{:helper.link('Import', 'folder-open', {'import': 1})}} - {{:helper.link('Play', 'play', {'play': 1}, data.lines.length > 0 ? (data.playing ? 'selected' : null) : 'disabled')}} - {{:helper.link('Stop', 'stop', {'stop': 1}, !data.playing ? 'selected' : null)}} -
-
-
-
Repeat Song:
- {{:helper.link('-', null, {'repeat' : -10}, data.repeat > 0 && !data.playing ? null : 'disabled')}} - {{:helper.link('-', null, {'repeat' : -1}, data.repeat > 0 && !data.playing ? null : 'disabled')}} - {{:data.repeat}} - {{:helper.link('+', null, {'repeat' : 1}, data.repeat < data.maxRepeat && !data.playing ? null : 'disabled')}} - {{:helper.link('+', null, {'repeat' : 10}, data.repeat < data.maxRepeat && !data.playing ? null : 'disabled')}} -
-
-
Tempo:
- {{:helper.link('-', null, {'tempo' : 10}, data.tempo < data.maxTempo ? null : 'disabled')}} - {{:helper.link('-', null, {'tempo' : 1}, data.tempo < data.maxTempo ? null : 'disabled')}} - {{:helper.round(600 / data.tempo)}} BPM - {{:helper.link('+', null, {'tempo' : -1}, data.tempo > data.minTempo ? null : 'disabled')}} - {{:helper.link('+', null, {'tempo' : -10}, data.tempo > data.minTempo ? null : 'disabled')}} -
-
- -

Editor

-
- {{for data.lines}} -
-
{{:index + 1}}: 
-
- {{:helper.link('Edit', 'pencil', {'modifyline': index+1}, data.playing ? 'disabled' : null)}} - {{:helper.link('Insert', 'arrow-up', {'insertline': index+1}, data.playing ? 'disabled' : null)}} - {{:helper.link('Delete', 'trash', {'deleteline': index+1}, data.playing ? 'disabled' : null)}} -
-
{{:value}}
-
- {{/for}} -
-
- {{:helper.link('Add Line', 'arrow-down', {'insertline': data.lines.length + 1}, data.playing ? 'disabled' : null)}} -
-
-
- -
-
- {{:helper.link('Help', data.help ? 'close' : 'question', {'help': 1})}} -
-
- {{if data.help}} -

- 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. -

    -
  • Example: C,D,E,F,G,A,B will play a C major scale.
  • -
  • After a note has an accidental or octave placed, it will be remembered: C,C4,C#,C3 is C3,C4,C4#,C3#
  • -
-

-

- 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 may be up to 50 characters.
  • -
  • A song may only contain up to 50 lines.
  • -
-

- {{/if}} -
-
\ No newline at end of file diff --git a/nano/templates/turret_control.tmpl b/nano/templates/turret_control.tmpl deleted file mode 100644 index 50f733ea80f..00000000000 --- a/nano/templates/turret_control.tmpl +++ /dev/null @@ -1,72 +0,0 @@ -
- Behaviour controls are {{:data.locked ? "locked" : "unlocked"}}. -
-
-{{if data.access}} - {{if !data.screen}} -
-
- Turret Status: -
-
- {{:helper.link('Enabled', null, {'command' : 'enable', 'value' : 1}, data.enabled ?'redButton' : null)}} - {{:helper.link('Disabled',null, {'command' : 'enable', 'value' : 0}, !data.enabled ? 'selected' : null)}} -
-
- {{if data.accesses}} -
-
- Required Access: -
-
- {{:helper.link('Modify', null, {'command' : 'screen', 'value' : 1})}} -
-
- {{/if}} - {{if data.lethal_control}} -
-
- Lethal Mode: -
-
- {{:helper.link('On', null, {'command' : 'lethal', 'value' : 1}, data.lethal ?'redButton' : null)}} - {{:helper.link('Off',null, {'command' : 'lethal', 'value' : 0}, !data.lethal ? 'selected' : null)}} -
-
- {{/if}} - - {{for data.settings}} -
-
- {{:value.category}} -
-
- {{:helper.link('On', null, {'command' : value.setting, 'value' : 1}, value.value ? 'selected' : null)}} - {{:helper.link('Off',null, {'command' : value.setting, 'value' : 0}, !value.value ? 'selected' : null)}} -
-
- {{/for}} - {{else}} -
-
- Turret Settings: -
-
- {{:helper.link('Modify', null, {'command' : 'screen', 'value' : 0})}} -
-
-
-
- Access Type: -
-
- {{:helper.link('All', null, {'one_access' : 0}, !data.one_access ? 'selected' : null)}} - {{:helper.link('One', null, {'one_access' : 1}, data.one_access ? 'selected' : null)}} -
-
-

Select Access

- {{for data.accesses}} - {{:helper.link(value.name, null, {'access' : value.number}, null, value.active ? 'selected' : null)}} - {{/for}} - {{/if}} -{{/if}} diff --git a/paradise.dme b/paradise.dme index 644b3583414..6e1235bfc86 100644 --- a/paradise.dme +++ b/paradise.dme @@ -40,6 +40,7 @@ #include "code\__DEFINES\genetics.dm" #include "code\__DEFINES\hud.dm" #include "code\__DEFINES\hydroponics.dm" +#include "code\__DEFINES\instruments.dm" #include "code\__DEFINES\inventory.dm" #include "code\__DEFINES\is_helpers.dm" #include "code\__DEFINES\job.dm" @@ -71,6 +72,7 @@ #include "code\__DEFINES\station_goals.dm" #include "code\__DEFINES\status_effects.dm" #include "code\__DEFINES\subsystems.dm" +#include "code\__DEFINES\tgui.dm" #include "code\__DEFINES\tools.dm" #include "code\__DEFINES\typeids.dm" #include "code\__DEFINES\vv.dm" @@ -239,6 +241,7 @@ #include "code\controllers\subsystem\parallax.dm" #include "code\controllers\subsystem\radio.dm" #include "code\controllers\subsystem\shuttles.dm" +#include "code\controllers\subsystem\sounds.dm" #include "code\controllers\subsystem\spacedrift.dm" #include "code\controllers\subsystem\statistics.dm" #include "code\controllers\subsystem\sun.dm" @@ -251,6 +254,7 @@ #include "code\controllers\subsystem\weather.dm" #include "code\controllers\subsystem\processing\dcs.dm" #include "code\controllers\subsystem\processing\fastprocess.dm" +#include "code\controllers\subsystem\processing\instruments.dm" #include "code\controllers\subsystem\processing\obj.dm" #include "code\controllers\subsystem\processing\processing.dm" #include "code\controllers\subsystem\tickets\mentor_tickets.dm" @@ -305,6 +309,7 @@ #include "code\datums\components\paintable.dm" #include "code\datums\components\slippery.dm" #include "code\datums\components\spawner.dm" +#include "code\datums\components\spooky.dm" #include "code\datums\components\squeak.dm" #include "code\datums\components\swarming.dm" #include "code\datums\diseases\_disease.dm" @@ -729,6 +734,7 @@ #include "code\game\machinery\computer\salvage_ship.dm" #include "code\game\machinery\computer\security.dm" #include "code\game\machinery\computer\skills.dm" +#include "code\game\machinery\computer\sm_monitor.dm" #include "code\game\machinery\computer\specops_shuttle.dm" #include "code\game\machinery\computer\station_alert.dm" #include "code\game\machinery\computer\store.dm" @@ -878,7 +884,6 @@ #include "code\game\objects\items\devices\flashlight.dm" #include "code\game\objects\items\devices\floor_painter.dm" #include "code\game\objects\items\devices\handheld_defib.dm" -#include "code\game\objects\items\devices\instruments.dm" #include "code\game\objects\items\devices\laserpointer.dm" #include "code\game\objects\items\devices\lightreplacer.dm" #include "code\game\objects\items\devices\machineprototype.dm" @@ -1083,7 +1088,6 @@ #include "code\game\objects\structures\misc.dm" #include "code\game\objects\structures\mop_bucket.dm" #include "code\game\objects\structures\morgue.dm" -#include "code\game\objects\structures\musician.dm" #include "code\game\objects\structures\noticeboard.dm" #include "code\game\objects\structures\plasticflaps.dm" #include "code\game\objects\structures\reflector.dm" @@ -1603,6 +1607,25 @@ #include "code\modules\hydroponics\grown\tobacco.dm" #include "code\modules\hydroponics\grown\tomato.dm" #include "code\modules\hydroponics\grown\towercap.dm" +#include "code\modules\instruments\_instrument_data.dm" +#include "code\modules\instruments\_instrument_key.dm" +#include "code\modules\instruments\brass.dm" +#include "code\modules\instruments\chromatic_percussion.dm" +#include "code\modules\instruments\fun.dm" +#include "code\modules\instruments\guitar.dm" +#include "code\modules\instruments\hardcoded.dm" +#include "code\modules\instruments\organ.dm" +#include "code\modules\instruments\piano.dm" +#include "code\modules\instruments\synth_tones.dm" +#include "code\modules\instruments\objs\items\_instrument.dm" +#include "code\modules\instruments\objs\items\headphones.dm" +#include "code\modules\instruments\objs\items\instruments.dm" +#include "code\modules\instruments\objs\structures\_musician.dm" +#include "code\modules\instruments\objs\structures\piano.dm" +#include "code\modules\instruments\songs\_song.dm" +#include "code\modules\instruments\songs\_song_ui.dm" +#include "code\modules\instruments\songs\play_legacy.dm" +#include "code\modules\instruments\songs\play_synthesized.dm" #include "code\modules\karma\karma.dm" #include "code\modules\keybindings\bindings_admin.dm" #include "code\modules\keybindings\bindings_ai.dm" @@ -2433,6 +2456,7 @@ #include "code\modules\telesci\telepad.dm" #include "code\modules\telesci\telesci_computer.dm" #include "code\modules\tgui\external.dm" +#include "code\modules\tgui\modal.dm" #include "code\modules\tgui\states.dm" #include "code\modules\tgui\tgui.dm" #include "code\modules\tgui\modules\_base.dm" diff --git a/sound/instruments/banjo/Ab3.ogg b/sound/instruments/banjo/Ab3.ogg new file mode 100644 index 00000000000..66e263bd615 Binary files /dev/null and b/sound/instruments/banjo/Ab3.ogg differ diff --git a/sound/instruments/banjo/Ab4.ogg b/sound/instruments/banjo/Ab4.ogg new file mode 100644 index 00000000000..f003e03233a Binary files /dev/null and b/sound/instruments/banjo/Ab4.ogg differ diff --git a/sound/instruments/banjo/Ab5.ogg b/sound/instruments/banjo/Ab5.ogg new file mode 100644 index 00000000000..c405725208e Binary files /dev/null and b/sound/instruments/banjo/Ab5.ogg differ diff --git a/sound/instruments/banjo/An3.ogg b/sound/instruments/banjo/An3.ogg new file mode 100644 index 00000000000..1700704c9c1 Binary files /dev/null and b/sound/instruments/banjo/An3.ogg differ diff --git a/sound/instruments/banjo/An4.ogg b/sound/instruments/banjo/An4.ogg new file mode 100644 index 00000000000..eb7279f869e Binary files /dev/null and b/sound/instruments/banjo/An4.ogg differ diff --git a/sound/instruments/banjo/An5.ogg b/sound/instruments/banjo/An5.ogg new file mode 100644 index 00000000000..d9cf57c0feb Binary files /dev/null and b/sound/instruments/banjo/An5.ogg differ diff --git a/sound/instruments/banjo/Bb3.ogg b/sound/instruments/banjo/Bb3.ogg new file mode 100644 index 00000000000..d3f757c0ace Binary files /dev/null and b/sound/instruments/banjo/Bb3.ogg differ diff --git a/sound/instruments/banjo/Bb4.ogg b/sound/instruments/banjo/Bb4.ogg new file mode 100644 index 00000000000..a9d869091bf Binary files /dev/null and b/sound/instruments/banjo/Bb4.ogg differ diff --git a/sound/instruments/banjo/Bb5.ogg b/sound/instruments/banjo/Bb5.ogg new file mode 100644 index 00000000000..a56e6c25005 Binary files /dev/null and b/sound/instruments/banjo/Bb5.ogg differ diff --git a/sound/instruments/banjo/Bn2.ogg b/sound/instruments/banjo/Bn2.ogg new file mode 100644 index 00000000000..3154f974193 Binary files /dev/null and b/sound/instruments/banjo/Bn2.ogg differ diff --git a/sound/instruments/banjo/Bn3.ogg b/sound/instruments/banjo/Bn3.ogg new file mode 100644 index 00000000000..6c72ec2fd5a Binary files /dev/null and b/sound/instruments/banjo/Bn3.ogg differ diff --git a/sound/instruments/banjo/Bn4.ogg b/sound/instruments/banjo/Bn4.ogg new file mode 100644 index 00000000000..b0e9a2b3b2f Binary files /dev/null and b/sound/instruments/banjo/Bn4.ogg differ diff --git a/sound/instruments/banjo/Bn5.ogg b/sound/instruments/banjo/Bn5.ogg new file mode 100644 index 00000000000..1b002140b87 Binary files /dev/null and b/sound/instruments/banjo/Bn5.ogg differ diff --git a/sound/instruments/banjo/Cn3.ogg b/sound/instruments/banjo/Cn3.ogg new file mode 100644 index 00000000000..6ef414d9d01 Binary files /dev/null and b/sound/instruments/banjo/Cn3.ogg differ diff --git a/sound/instruments/banjo/Cn4.ogg b/sound/instruments/banjo/Cn4.ogg new file mode 100644 index 00000000000..4a26a6741db Binary files /dev/null and b/sound/instruments/banjo/Cn4.ogg differ diff --git a/sound/instruments/banjo/Cn5.ogg b/sound/instruments/banjo/Cn5.ogg new file mode 100644 index 00000000000..901ed3bc08b Binary files /dev/null and b/sound/instruments/banjo/Cn5.ogg differ diff --git a/sound/instruments/banjo/Cn6.ogg b/sound/instruments/banjo/Cn6.ogg new file mode 100644 index 00000000000..5cdbbb17cea Binary files /dev/null and b/sound/instruments/banjo/Cn6.ogg differ diff --git a/sound/instruments/banjo/Db3.ogg b/sound/instruments/banjo/Db3.ogg new file mode 100644 index 00000000000..1ebffdf5025 Binary files /dev/null and b/sound/instruments/banjo/Db3.ogg differ diff --git a/sound/instruments/banjo/Db4.ogg b/sound/instruments/banjo/Db4.ogg new file mode 100644 index 00000000000..5b939365086 Binary files /dev/null and b/sound/instruments/banjo/Db4.ogg differ diff --git a/sound/instruments/banjo/Db5.ogg b/sound/instruments/banjo/Db5.ogg new file mode 100644 index 00000000000..6ee4dde9479 Binary files /dev/null and b/sound/instruments/banjo/Db5.ogg differ diff --git a/sound/instruments/banjo/Db6.ogg b/sound/instruments/banjo/Db6.ogg new file mode 100644 index 00000000000..fd73894fda6 Binary files /dev/null and b/sound/instruments/banjo/Db6.ogg differ diff --git a/sound/instruments/banjo/Dn3.ogg b/sound/instruments/banjo/Dn3.ogg new file mode 100644 index 00000000000..77491b01b8c Binary files /dev/null and b/sound/instruments/banjo/Dn3.ogg differ diff --git a/sound/instruments/banjo/Dn4.ogg b/sound/instruments/banjo/Dn4.ogg new file mode 100644 index 00000000000..11f68b5a157 Binary files /dev/null and b/sound/instruments/banjo/Dn4.ogg differ diff --git a/sound/instruments/banjo/Dn5.ogg b/sound/instruments/banjo/Dn5.ogg new file mode 100644 index 00000000000..2e9ebe49891 Binary files /dev/null and b/sound/instruments/banjo/Dn5.ogg differ diff --git a/sound/instruments/banjo/Dn6.ogg b/sound/instruments/banjo/Dn6.ogg new file mode 100644 index 00000000000..89ae62361dc Binary files /dev/null and b/sound/instruments/banjo/Dn6.ogg differ diff --git a/sound/instruments/banjo/Eb3.ogg b/sound/instruments/banjo/Eb3.ogg new file mode 100644 index 00000000000..1d1e43049d2 Binary files /dev/null and b/sound/instruments/banjo/Eb3.ogg differ diff --git a/sound/instruments/banjo/Eb4.ogg b/sound/instruments/banjo/Eb4.ogg new file mode 100644 index 00000000000..2722655f5a3 Binary files /dev/null and b/sound/instruments/banjo/Eb4.ogg differ diff --git a/sound/instruments/banjo/Eb5.ogg b/sound/instruments/banjo/Eb5.ogg new file mode 100644 index 00000000000..7a109dfdf79 Binary files /dev/null and b/sound/instruments/banjo/Eb5.ogg differ diff --git a/sound/instruments/banjo/En3.ogg b/sound/instruments/banjo/En3.ogg new file mode 100644 index 00000000000..4610efdd4f0 Binary files /dev/null and b/sound/instruments/banjo/En3.ogg differ diff --git a/sound/instruments/banjo/En4.ogg b/sound/instruments/banjo/En4.ogg new file mode 100644 index 00000000000..64c14daf915 Binary files /dev/null and b/sound/instruments/banjo/En4.ogg differ diff --git a/sound/instruments/banjo/En5.ogg b/sound/instruments/banjo/En5.ogg new file mode 100644 index 00000000000..8e0b6c1637e Binary files /dev/null and b/sound/instruments/banjo/En5.ogg differ diff --git a/sound/instruments/banjo/Fn3.ogg b/sound/instruments/banjo/Fn3.ogg new file mode 100644 index 00000000000..5cdc4f13fb3 Binary files /dev/null and b/sound/instruments/banjo/Fn3.ogg differ diff --git a/sound/instruments/banjo/Fn4.ogg b/sound/instruments/banjo/Fn4.ogg new file mode 100644 index 00000000000..78d5454f186 Binary files /dev/null and b/sound/instruments/banjo/Fn4.ogg differ diff --git a/sound/instruments/banjo/Fn5.ogg b/sound/instruments/banjo/Fn5.ogg new file mode 100644 index 00000000000..b21559b4656 Binary files /dev/null and b/sound/instruments/banjo/Fn5.ogg differ diff --git a/sound/instruments/banjo/Gb3.ogg b/sound/instruments/banjo/Gb3.ogg new file mode 100644 index 00000000000..fd055b74717 Binary files /dev/null and b/sound/instruments/banjo/Gb3.ogg differ diff --git a/sound/instruments/banjo/Gb4.ogg b/sound/instruments/banjo/Gb4.ogg new file mode 100644 index 00000000000..f2c62510ed0 Binary files /dev/null and b/sound/instruments/banjo/Gb4.ogg differ diff --git a/sound/instruments/banjo/Gb5.ogg b/sound/instruments/banjo/Gb5.ogg new file mode 100644 index 00000000000..ab17347912b Binary files /dev/null and b/sound/instruments/banjo/Gb5.ogg differ diff --git a/sound/instruments/banjo/Gn3.ogg b/sound/instruments/banjo/Gn3.ogg new file mode 100644 index 00000000000..ad52ef85c08 Binary files /dev/null and b/sound/instruments/banjo/Gn3.ogg differ diff --git a/sound/instruments/banjo/Gn4.ogg b/sound/instruments/banjo/Gn4.ogg new file mode 100644 index 00000000000..2ddb13b86b3 Binary files /dev/null and b/sound/instruments/banjo/Gn4.ogg differ diff --git a/sound/instruments/banjo/Gn5.ogg b/sound/instruments/banjo/Gn5.ogg new file mode 100644 index 00000000000..d5a7886c4cf Binary files /dev/null and b/sound/instruments/banjo/Gn5.ogg differ diff --git a/sound/instruments/synthesis_samples/brass/crisis_brass/c2.ogg b/sound/instruments/synthesis_samples/brass/crisis_brass/c2.ogg new file mode 100644 index 00000000000..aaa1e27ab89 Binary files /dev/null and b/sound/instruments/synthesis_samples/brass/crisis_brass/c2.ogg differ diff --git a/sound/instruments/synthesis_samples/brass/crisis_brass/c3.ogg b/sound/instruments/synthesis_samples/brass/crisis_brass/c3.ogg new file mode 100644 index 00000000000..ce50e76aae6 Binary files /dev/null and b/sound/instruments/synthesis_samples/brass/crisis_brass/c3.ogg differ diff --git a/sound/instruments/synthesis_samples/brass/crisis_brass/c4.ogg b/sound/instruments/synthesis_samples/brass/crisis_brass/c4.ogg new file mode 100644 index 00000000000..22f34d67592 Binary files /dev/null and b/sound/instruments/synthesis_samples/brass/crisis_brass/c4.ogg differ diff --git a/sound/instruments/synthesis_samples/brass/crisis_brass/c5.ogg b/sound/instruments/synthesis_samples/brass/crisis_brass/c5.ogg new file mode 100644 index 00000000000..eb5bb7c295e Binary files /dev/null and b/sound/instruments/synthesis_samples/brass/crisis_brass/c5.ogg differ diff --git a/sound/instruments/synthesis_samples/brass/crisis_trombone/C2.ogg b/sound/instruments/synthesis_samples/brass/crisis_trombone/C2.ogg new file mode 100644 index 00000000000..bd299e321ab Binary files /dev/null and b/sound/instruments/synthesis_samples/brass/crisis_trombone/C2.ogg differ diff --git a/sound/instruments/synthesis_samples/brass/crisis_trombone/C3.ogg b/sound/instruments/synthesis_samples/brass/crisis_trombone/C3.ogg new file mode 100644 index 00000000000..0519d2d20dd Binary files /dev/null and b/sound/instruments/synthesis_samples/brass/crisis_trombone/C3.ogg differ diff --git a/sound/instruments/synthesis_samples/brass/crisis_trombone/C4.ogg b/sound/instruments/synthesis_samples/brass/crisis_trombone/C4.ogg new file mode 100644 index 00000000000..3b969a34b1c Binary files /dev/null and b/sound/instruments/synthesis_samples/brass/crisis_trombone/C4.ogg differ diff --git a/sound/instruments/synthesis_samples/brass/crisis_trombone/C5.ogg b/sound/instruments/synthesis_samples/brass/crisis_trombone/C5.ogg new file mode 100644 index 00000000000..75f709c16fe Binary files /dev/null and b/sound/instruments/synthesis_samples/brass/crisis_trombone/C5.ogg differ diff --git a/sound/instruments/synthesis_samples/brass/crisis_trumpet/C4.ogg b/sound/instruments/synthesis_samples/brass/crisis_trumpet/C4.ogg new file mode 100644 index 00000000000..ba347f80034 Binary files /dev/null and b/sound/instruments/synthesis_samples/brass/crisis_trumpet/C4.ogg differ diff --git a/sound/instruments/synthesis_samples/brass/crisis_trumpet/C5.ogg b/sound/instruments/synthesis_samples/brass/crisis_trumpet/C5.ogg new file mode 100644 index 00000000000..cee89761d0d Binary files /dev/null and b/sound/instruments/synthesis_samples/brass/crisis_trumpet/C5.ogg differ diff --git a/sound/instruments/synthesis_samples/chromatic/fluid_celeste/C2.ogg b/sound/instruments/synthesis_samples/chromatic/fluid_celeste/C2.ogg new file mode 100644 index 00000000000..105f7676557 Binary files /dev/null and b/sound/instruments/synthesis_samples/chromatic/fluid_celeste/C2.ogg differ diff --git a/sound/instruments/synthesis_samples/chromatic/fluid_celeste/C3.ogg b/sound/instruments/synthesis_samples/chromatic/fluid_celeste/C3.ogg new file mode 100644 index 00000000000..4aa33b6cded Binary files /dev/null and b/sound/instruments/synthesis_samples/chromatic/fluid_celeste/C3.ogg differ diff --git a/sound/instruments/synthesis_samples/chromatic/fluid_celeste/C4.ogg b/sound/instruments/synthesis_samples/chromatic/fluid_celeste/C4.ogg new file mode 100644 index 00000000000..d661e8d7580 Binary files /dev/null and b/sound/instruments/synthesis_samples/chromatic/fluid_celeste/C4.ogg differ diff --git a/sound/instruments/synthesis_samples/chromatic/fluid_celeste/C5.ogg b/sound/instruments/synthesis_samples/chromatic/fluid_celeste/C5.ogg new file mode 100644 index 00000000000..bf650f1a6fa Binary files /dev/null and b/sound/instruments/synthesis_samples/chromatic/fluid_celeste/C5.ogg differ diff --git a/sound/instruments/synthesis_samples/chromatic/fluid_celeste/C6.ogg b/sound/instruments/synthesis_samples/chromatic/fluid_celeste/C6.ogg new file mode 100644 index 00000000000..c00f7949b7e Binary files /dev/null and b/sound/instruments/synthesis_samples/chromatic/fluid_celeste/C6.ogg differ diff --git a/sound/instruments/synthesis_samples/chromatic/fluid_celeste/C7.ogg b/sound/instruments/synthesis_samples/chromatic/fluid_celeste/C7.ogg new file mode 100644 index 00000000000..72588e9ca4c Binary files /dev/null and b/sound/instruments/synthesis_samples/chromatic/fluid_celeste/C7.ogg differ diff --git a/sound/instruments/synthesis_samples/chromatic/fluid_celeste/C8.ogg b/sound/instruments/synthesis_samples/chromatic/fluid_celeste/C8.ogg new file mode 100644 index 00000000000..b2a0b445b92 Binary files /dev/null and b/sound/instruments/synthesis_samples/chromatic/fluid_celeste/C8.ogg differ diff --git a/sound/instruments/synthesis_samples/chromatic/sgmbox/c2.ogg b/sound/instruments/synthesis_samples/chromatic/sgmbox/c2.ogg new file mode 100644 index 00000000000..ecf6778343b Binary files /dev/null and b/sound/instruments/synthesis_samples/chromatic/sgmbox/c2.ogg differ diff --git a/sound/instruments/synthesis_samples/chromatic/sgmbox/c3.ogg b/sound/instruments/synthesis_samples/chromatic/sgmbox/c3.ogg new file mode 100644 index 00000000000..867e9ce00d0 Binary files /dev/null and b/sound/instruments/synthesis_samples/chromatic/sgmbox/c3.ogg differ diff --git a/sound/instruments/synthesis_samples/chromatic/sgmbox/c4.ogg b/sound/instruments/synthesis_samples/chromatic/sgmbox/c4.ogg new file mode 100644 index 00000000000..446d45993e8 Binary files /dev/null and b/sound/instruments/synthesis_samples/chromatic/sgmbox/c4.ogg differ diff --git a/sound/instruments/synthesis_samples/chromatic/sgmbox/c5.ogg b/sound/instruments/synthesis_samples/chromatic/sgmbox/c5.ogg new file mode 100644 index 00000000000..54d56400c03 Binary files /dev/null and b/sound/instruments/synthesis_samples/chromatic/sgmbox/c5.ogg differ diff --git a/sound/instruments/synthesis_samples/chromatic/vibraphone1/c2.ogg b/sound/instruments/synthesis_samples/chromatic/vibraphone1/c2.ogg new file mode 100644 index 00000000000..f3770c1f1a0 Binary files /dev/null and b/sound/instruments/synthesis_samples/chromatic/vibraphone1/c2.ogg differ diff --git a/sound/instruments/synthesis_samples/chromatic/vibraphone1/c3.ogg b/sound/instruments/synthesis_samples/chromatic/vibraphone1/c3.ogg new file mode 100644 index 00000000000..28954fbb47f Binary files /dev/null and b/sound/instruments/synthesis_samples/chromatic/vibraphone1/c3.ogg differ diff --git a/sound/instruments/synthesis_samples/chromatic/vibraphone1/c4.ogg b/sound/instruments/synthesis_samples/chromatic/vibraphone1/c4.ogg new file mode 100644 index 00000000000..1233f5314a3 Binary files /dev/null and b/sound/instruments/synthesis_samples/chromatic/vibraphone1/c4.ogg differ diff --git a/sound/instruments/synthesis_samples/chromatic/vibraphone1/c5.ogg b/sound/instruments/synthesis_samples/chromatic/vibraphone1/c5.ogg new file mode 100644 index 00000000000..00daf331357 Binary files /dev/null and b/sound/instruments/synthesis_samples/chromatic/vibraphone1/c5.ogg differ diff --git a/sound/instruments/synthesis_samples/guitar/crisis_clean/C2.ogg b/sound/instruments/synthesis_samples/guitar/crisis_clean/C2.ogg new file mode 100644 index 00000000000..13ad54bff00 Binary files /dev/null and b/sound/instruments/synthesis_samples/guitar/crisis_clean/C2.ogg differ diff --git a/sound/instruments/synthesis_samples/guitar/crisis_clean/C3.ogg b/sound/instruments/synthesis_samples/guitar/crisis_clean/C3.ogg new file mode 100644 index 00000000000..17bf392c4b2 Binary files /dev/null and b/sound/instruments/synthesis_samples/guitar/crisis_clean/C3.ogg differ diff --git a/sound/instruments/synthesis_samples/guitar/crisis_clean/C4.ogg b/sound/instruments/synthesis_samples/guitar/crisis_clean/C4.ogg new file mode 100644 index 00000000000..feda419a0ad Binary files /dev/null and b/sound/instruments/synthesis_samples/guitar/crisis_clean/C4.ogg differ diff --git a/sound/instruments/synthesis_samples/guitar/crisis_clean/C5.ogg b/sound/instruments/synthesis_samples/guitar/crisis_clean/C5.ogg new file mode 100644 index 00000000000..bd088dd850e Binary files /dev/null and b/sound/instruments/synthesis_samples/guitar/crisis_clean/C5.ogg differ diff --git a/sound/instruments/synthesis_samples/guitar/crisis_muted/C2.ogg b/sound/instruments/synthesis_samples/guitar/crisis_muted/C2.ogg new file mode 100644 index 00000000000..09cdbeec42c Binary files /dev/null and b/sound/instruments/synthesis_samples/guitar/crisis_muted/C2.ogg differ diff --git a/sound/instruments/synthesis_samples/guitar/crisis_muted/C3.ogg b/sound/instruments/synthesis_samples/guitar/crisis_muted/C3.ogg new file mode 100644 index 00000000000..f82c39cee5b Binary files /dev/null and b/sound/instruments/synthesis_samples/guitar/crisis_muted/C3.ogg differ diff --git a/sound/instruments/synthesis_samples/guitar/crisis_muted/C4.ogg b/sound/instruments/synthesis_samples/guitar/crisis_muted/C4.ogg new file mode 100644 index 00000000000..23bfd113d6c Binary files /dev/null and b/sound/instruments/synthesis_samples/guitar/crisis_muted/C4.ogg differ diff --git a/sound/instruments/synthesis_samples/guitar/crisis_muted/C5.ogg b/sound/instruments/synthesis_samples/guitar/crisis_muted/C5.ogg new file mode 100644 index 00000000000..e5ec38d5ab8 Binary files /dev/null and b/sound/instruments/synthesis_samples/guitar/crisis_muted/C5.ogg differ diff --git a/sound/instruments/synthesis_samples/guitar/crisis_nylon/c2.ogg b/sound/instruments/synthesis_samples/guitar/crisis_nylon/c2.ogg new file mode 100644 index 00000000000..42a6cdfad3c Binary files /dev/null and b/sound/instruments/synthesis_samples/guitar/crisis_nylon/c2.ogg differ diff --git a/sound/instruments/synthesis_samples/guitar/crisis_nylon/c3.ogg b/sound/instruments/synthesis_samples/guitar/crisis_nylon/c3.ogg new file mode 100644 index 00000000000..cd6414c0aa2 Binary files /dev/null and b/sound/instruments/synthesis_samples/guitar/crisis_nylon/c3.ogg differ diff --git a/sound/instruments/synthesis_samples/guitar/crisis_nylon/c4.ogg b/sound/instruments/synthesis_samples/guitar/crisis_nylon/c4.ogg new file mode 100644 index 00000000000..e5366018653 Binary files /dev/null and b/sound/instruments/synthesis_samples/guitar/crisis_nylon/c4.ogg differ diff --git a/sound/instruments/synthesis_samples/guitar/crisis_nylon/c5.ogg b/sound/instruments/synthesis_samples/guitar/crisis_nylon/c5.ogg new file mode 100644 index 00000000000..60382228374 Binary files /dev/null and b/sound/instruments/synthesis_samples/guitar/crisis_nylon/c5.ogg differ diff --git a/sound/instruments/synthesis_samples/guitar/crisis_steel/c2.ogg b/sound/instruments/synthesis_samples/guitar/crisis_steel/c2.ogg new file mode 100644 index 00000000000..648549d594a Binary files /dev/null and b/sound/instruments/synthesis_samples/guitar/crisis_steel/c2.ogg differ diff --git a/sound/instruments/synthesis_samples/guitar/crisis_steel/c3.ogg b/sound/instruments/synthesis_samples/guitar/crisis_steel/c3.ogg new file mode 100644 index 00000000000..01ba59a908c Binary files /dev/null and b/sound/instruments/synthesis_samples/guitar/crisis_steel/c3.ogg differ diff --git a/sound/instruments/synthesis_samples/guitar/crisis_steel/c4.ogg b/sound/instruments/synthesis_samples/guitar/crisis_steel/c4.ogg new file mode 100644 index 00000000000..7cfaa8ca72b Binary files /dev/null and b/sound/instruments/synthesis_samples/guitar/crisis_steel/c4.ogg differ diff --git a/sound/instruments/synthesis_samples/guitar/crisis_steel/c5.ogg b/sound/instruments/synthesis_samples/guitar/crisis_steel/c5.ogg new file mode 100644 index 00000000000..b4ca49dc047 Binary files /dev/null and b/sound/instruments/synthesis_samples/guitar/crisis_steel/c5.ogg differ diff --git a/sound/instruments/synthesis_samples/organ/crisis_accordian/c2.ogg b/sound/instruments/synthesis_samples/organ/crisis_accordian/c2.ogg new file mode 100644 index 00000000000..7c9870a7c3b Binary files /dev/null and b/sound/instruments/synthesis_samples/organ/crisis_accordian/c2.ogg differ diff --git a/sound/instruments/synthesis_samples/organ/crisis_accordian/c3.ogg b/sound/instruments/synthesis_samples/organ/crisis_accordian/c3.ogg new file mode 100644 index 00000000000..5723c2edd27 Binary files /dev/null and b/sound/instruments/synthesis_samples/organ/crisis_accordian/c3.ogg differ diff --git a/sound/instruments/synthesis_samples/organ/crisis_accordian/c4.ogg b/sound/instruments/synthesis_samples/organ/crisis_accordian/c4.ogg new file mode 100644 index 00000000000..329f14f6feb Binary files /dev/null and b/sound/instruments/synthesis_samples/organ/crisis_accordian/c4.ogg differ diff --git a/sound/instruments/synthesis_samples/organ/crisis_accordian/c5.ogg b/sound/instruments/synthesis_samples/organ/crisis_accordian/c5.ogg new file mode 100644 index 00000000000..5e8ac69de28 Binary files /dev/null and b/sound/instruments/synthesis_samples/organ/crisis_accordian/c5.ogg differ diff --git a/sound/instruments/synthesis_samples/organ/crisis_church/c2.ogg b/sound/instruments/synthesis_samples/organ/crisis_church/c2.ogg new file mode 100644 index 00000000000..ddc44c69c29 Binary files /dev/null and b/sound/instruments/synthesis_samples/organ/crisis_church/c2.ogg differ diff --git a/sound/instruments/synthesis_samples/organ/crisis_church/c3.ogg b/sound/instruments/synthesis_samples/organ/crisis_church/c3.ogg new file mode 100644 index 00000000000..28557475284 Binary files /dev/null and b/sound/instruments/synthesis_samples/organ/crisis_church/c3.ogg differ diff --git a/sound/instruments/synthesis_samples/organ/crisis_church/c4.ogg b/sound/instruments/synthesis_samples/organ/crisis_church/c4.ogg new file mode 100644 index 00000000000..906fff5bd8d Binary files /dev/null and b/sound/instruments/synthesis_samples/organ/crisis_church/c4.ogg differ diff --git a/sound/instruments/synthesis_samples/organ/crisis_church/c5.ogg b/sound/instruments/synthesis_samples/organ/crisis_church/c5.ogg new file mode 100644 index 00000000000..96d28a7206d Binary files /dev/null and b/sound/instruments/synthesis_samples/organ/crisis_church/c5.ogg differ diff --git a/sound/instruments/synthesis_samples/organ/crisis_hammond/c2.ogg b/sound/instruments/synthesis_samples/organ/crisis_hammond/c2.ogg new file mode 100644 index 00000000000..9b917b7eb53 Binary files /dev/null and b/sound/instruments/synthesis_samples/organ/crisis_hammond/c2.ogg differ diff --git a/sound/instruments/synthesis_samples/organ/crisis_hammond/c3.ogg b/sound/instruments/synthesis_samples/organ/crisis_hammond/c3.ogg new file mode 100644 index 00000000000..c68410d6f09 Binary files /dev/null and b/sound/instruments/synthesis_samples/organ/crisis_hammond/c3.ogg differ diff --git a/sound/instruments/synthesis_samples/organ/crisis_hammond/c4.ogg b/sound/instruments/synthesis_samples/organ/crisis_hammond/c4.ogg new file mode 100644 index 00000000000..df84ba99e8e Binary files /dev/null and b/sound/instruments/synthesis_samples/organ/crisis_hammond/c4.ogg differ diff --git a/sound/instruments/synthesis_samples/organ/crisis_hammond/c5.ogg b/sound/instruments/synthesis_samples/organ/crisis_hammond/c5.ogg new file mode 100644 index 00000000000..af8c178efe8 Binary files /dev/null and b/sound/instruments/synthesis_samples/organ/crisis_hammond/c5.ogg differ diff --git a/sound/instruments/synthesis_samples/organ/crisis_harmonica/c3.ogg b/sound/instruments/synthesis_samples/organ/crisis_harmonica/c3.ogg new file mode 100644 index 00000000000..268b41f1fce Binary files /dev/null and b/sound/instruments/synthesis_samples/organ/crisis_harmonica/c3.ogg differ diff --git a/sound/instruments/synthesis_samples/organ/crisis_harmonica/c4.ogg b/sound/instruments/synthesis_samples/organ/crisis_harmonica/c4.ogg new file mode 100644 index 00000000000..04ceb54bfc2 Binary files /dev/null and b/sound/instruments/synthesis_samples/organ/crisis_harmonica/c4.ogg differ diff --git a/sound/instruments/synthesis_samples/organ/crisis_harmonica/c5.ogg b/sound/instruments/synthesis_samples/organ/crisis_harmonica/c5.ogg new file mode 100644 index 00000000000..b321983e74f Binary files /dev/null and b/sound/instruments/synthesis_samples/organ/crisis_harmonica/c5.ogg differ diff --git a/sound/instruments/synthesis_samples/organ/crisis_tangaccordian/c2.ogg b/sound/instruments/synthesis_samples/organ/crisis_tangaccordian/c2.ogg new file mode 100644 index 00000000000..250a5c08e08 Binary files /dev/null and b/sound/instruments/synthesis_samples/organ/crisis_tangaccordian/c2.ogg differ diff --git a/sound/instruments/synthesis_samples/organ/crisis_tangaccordian/c3.ogg b/sound/instruments/synthesis_samples/organ/crisis_tangaccordian/c3.ogg new file mode 100644 index 00000000000..8b1c23007bb Binary files /dev/null and b/sound/instruments/synthesis_samples/organ/crisis_tangaccordian/c3.ogg differ diff --git a/sound/instruments/synthesis_samples/organ/crisis_tangaccordian/c4.ogg b/sound/instruments/synthesis_samples/organ/crisis_tangaccordian/c4.ogg new file mode 100644 index 00000000000..098587183bb Binary files /dev/null and b/sound/instruments/synthesis_samples/organ/crisis_tangaccordian/c4.ogg differ diff --git a/sound/instruments/synthesis_samples/organ/crisis_tangaccordian/c5.ogg b/sound/instruments/synthesis_samples/organ/crisis_tangaccordian/c5.ogg new file mode 100644 index 00000000000..81b60ef4c2f Binary files /dev/null and b/sound/instruments/synthesis_samples/organ/crisis_tangaccordian/c5.ogg differ diff --git a/sound/instruments/synthesis_samples/piano/crisis_bright_piano/c2.ogg b/sound/instruments/synthesis_samples/piano/crisis_bright_piano/c2.ogg new file mode 100644 index 00000000000..39e992fbd85 Binary files /dev/null and b/sound/instruments/synthesis_samples/piano/crisis_bright_piano/c2.ogg differ diff --git a/sound/instruments/synthesis_samples/piano/crisis_bright_piano/c3.ogg b/sound/instruments/synthesis_samples/piano/crisis_bright_piano/c3.ogg new file mode 100644 index 00000000000..04aa9852815 Binary files /dev/null and b/sound/instruments/synthesis_samples/piano/crisis_bright_piano/c3.ogg differ diff --git a/sound/instruments/synthesis_samples/piano/crisis_bright_piano/c4.ogg b/sound/instruments/synthesis_samples/piano/crisis_bright_piano/c4.ogg new file mode 100644 index 00000000000..aff97942e9e Binary files /dev/null and b/sound/instruments/synthesis_samples/piano/crisis_bright_piano/c4.ogg differ diff --git a/sound/instruments/synthesis_samples/piano/crisis_bright_piano/c5.ogg b/sound/instruments/synthesis_samples/piano/crisis_bright_piano/c5.ogg new file mode 100644 index 00000000000..19fd937707a Binary files /dev/null and b/sound/instruments/synthesis_samples/piano/crisis_bright_piano/c5.ogg differ diff --git a/sound/instruments/synthesis_samples/piano/crisis_bright_piano/c6.ogg b/sound/instruments/synthesis_samples/piano/crisis_bright_piano/c6.ogg new file mode 100644 index 00000000000..452e7485be1 Binary files /dev/null and b/sound/instruments/synthesis_samples/piano/crisis_bright_piano/c6.ogg differ diff --git a/sound/instruments/synthesis_samples/piano/crisis_bright_piano/c7.ogg b/sound/instruments/synthesis_samples/piano/crisis_bright_piano/c7.ogg new file mode 100644 index 00000000000..66c88185a73 Binary files /dev/null and b/sound/instruments/synthesis_samples/piano/crisis_bright_piano/c7.ogg differ diff --git a/sound/instruments/synthesis_samples/piano/crisis_bright_piano/c8.ogg b/sound/instruments/synthesis_samples/piano/crisis_bright_piano/c8.ogg new file mode 100644 index 00000000000..d93c5176ced Binary files /dev/null and b/sound/instruments/synthesis_samples/piano/crisis_bright_piano/c8.ogg differ diff --git a/sound/instruments/synthesis_samples/piano/crisis_grand_piano/c2.ogg b/sound/instruments/synthesis_samples/piano/crisis_grand_piano/c2.ogg new file mode 100644 index 00000000000..fabd90d2e6a Binary files /dev/null and b/sound/instruments/synthesis_samples/piano/crisis_grand_piano/c2.ogg differ diff --git a/sound/instruments/synthesis_samples/piano/crisis_grand_piano/c3.ogg b/sound/instruments/synthesis_samples/piano/crisis_grand_piano/c3.ogg new file mode 100644 index 00000000000..e4cda1487aa Binary files /dev/null and b/sound/instruments/synthesis_samples/piano/crisis_grand_piano/c3.ogg differ diff --git a/sound/instruments/synthesis_samples/piano/crisis_grand_piano/c4.ogg b/sound/instruments/synthesis_samples/piano/crisis_grand_piano/c4.ogg new file mode 100644 index 00000000000..c596994b3eb Binary files /dev/null and b/sound/instruments/synthesis_samples/piano/crisis_grand_piano/c4.ogg differ diff --git a/sound/instruments/synthesis_samples/piano/crisis_grand_piano/c5.ogg b/sound/instruments/synthesis_samples/piano/crisis_grand_piano/c5.ogg new file mode 100644 index 00000000000..d265514e27b Binary files /dev/null and b/sound/instruments/synthesis_samples/piano/crisis_grand_piano/c5.ogg differ diff --git a/sound/instruments/synthesis_samples/piano/crisis_grand_piano/c6.ogg b/sound/instruments/synthesis_samples/piano/crisis_grand_piano/c6.ogg new file mode 100644 index 00000000000..3e17b3f99a6 Binary files /dev/null and b/sound/instruments/synthesis_samples/piano/crisis_grand_piano/c6.ogg differ diff --git a/sound/instruments/synthesis_samples/piano/crisis_grand_piano/c7.ogg b/sound/instruments/synthesis_samples/piano/crisis_grand_piano/c7.ogg new file mode 100644 index 00000000000..b57a8a9109a Binary files /dev/null and b/sound/instruments/synthesis_samples/piano/crisis_grand_piano/c7.ogg differ diff --git a/sound/instruments/synthesis_samples/piano/crisis_grand_piano/c8.ogg b/sound/instruments/synthesis_samples/piano/crisis_grand_piano/c8.ogg new file mode 100644 index 00000000000..ce4d9535e84 Binary files /dev/null and b/sound/instruments/synthesis_samples/piano/crisis_grand_piano/c8.ogg differ diff --git a/sound/instruments/synthesis_samples/piano/crisis_harpsichord/c2.ogg b/sound/instruments/synthesis_samples/piano/crisis_harpsichord/c2.ogg new file mode 100644 index 00000000000..bb02363fffb Binary files /dev/null and b/sound/instruments/synthesis_samples/piano/crisis_harpsichord/c2.ogg differ diff --git a/sound/instruments/synthesis_samples/piano/crisis_harpsichord/c3.ogg b/sound/instruments/synthesis_samples/piano/crisis_harpsichord/c3.ogg new file mode 100644 index 00000000000..1a532ac8d42 Binary files /dev/null and b/sound/instruments/synthesis_samples/piano/crisis_harpsichord/c3.ogg differ diff --git a/sound/instruments/synthesis_samples/piano/crisis_harpsichord/c4.ogg b/sound/instruments/synthesis_samples/piano/crisis_harpsichord/c4.ogg new file mode 100644 index 00000000000..16ff313baa3 Binary files /dev/null and b/sound/instruments/synthesis_samples/piano/crisis_harpsichord/c4.ogg differ diff --git a/sound/instruments/synthesis_samples/piano/crisis_harpsichord/c5.ogg b/sound/instruments/synthesis_samples/piano/crisis_harpsichord/c5.ogg new file mode 100644 index 00000000000..04161d2571b Binary files /dev/null and b/sound/instruments/synthesis_samples/piano/crisis_harpsichord/c5.ogg differ diff --git a/sound/instruments/synthesis_samples/piano/fluid_harpsi/C2.ogg b/sound/instruments/synthesis_samples/piano/fluid_harpsi/C2.ogg new file mode 100644 index 00000000000..30a3c653a1c Binary files /dev/null and b/sound/instruments/synthesis_samples/piano/fluid_harpsi/C2.ogg differ diff --git a/sound/instruments/synthesis_samples/piano/fluid_harpsi/C3.ogg b/sound/instruments/synthesis_samples/piano/fluid_harpsi/C3.ogg new file mode 100644 index 00000000000..f6bc891506c Binary files /dev/null and b/sound/instruments/synthesis_samples/piano/fluid_harpsi/C3.ogg differ diff --git a/sound/instruments/synthesis_samples/piano/fluid_harpsi/C4.ogg b/sound/instruments/synthesis_samples/piano/fluid_harpsi/C4.ogg new file mode 100644 index 00000000000..ab47f6940c9 Binary files /dev/null and b/sound/instruments/synthesis_samples/piano/fluid_harpsi/C4.ogg differ diff --git a/sound/instruments/synthesis_samples/piano/fluid_harpsi/C5.ogg b/sound/instruments/synthesis_samples/piano/fluid_harpsi/C5.ogg new file mode 100644 index 00000000000..5dfb9aa5291 Binary files /dev/null and b/sound/instruments/synthesis_samples/piano/fluid_harpsi/C5.ogg differ diff --git a/sound/instruments/synthesis_samples/piano/fluid_harpsi/C6.ogg b/sound/instruments/synthesis_samples/piano/fluid_harpsi/C6.ogg new file mode 100644 index 00000000000..7bc8784207e Binary files /dev/null and b/sound/instruments/synthesis_samples/piano/fluid_harpsi/C6.ogg differ diff --git a/sound/instruments/synthesis_samples/piano/fluid_harpsi/C7.ogg b/sound/instruments/synthesis_samples/piano/fluid_harpsi/C7.ogg new file mode 100644 index 00000000000..185b4d3db64 Binary files /dev/null and b/sound/instruments/synthesis_samples/piano/fluid_harpsi/C7.ogg differ diff --git a/sound/instruments/synthesis_samples/piano/fluid_harpsi/C8.ogg b/sound/instruments/synthesis_samples/piano/fluid_harpsi/C8.ogg new file mode 100644 index 00000000000..f358ef0810d Binary files /dev/null and b/sound/instruments/synthesis_samples/piano/fluid_harpsi/C8.ogg differ diff --git a/sound/instruments/synthesis_samples/piano/fluid_piano/c2.ogg b/sound/instruments/synthesis_samples/piano/fluid_piano/c2.ogg new file mode 100644 index 00000000000..048f9640bfe Binary files /dev/null and b/sound/instruments/synthesis_samples/piano/fluid_piano/c2.ogg differ diff --git a/sound/instruments/synthesis_samples/piano/fluid_piano/c3.ogg b/sound/instruments/synthesis_samples/piano/fluid_piano/c3.ogg new file mode 100644 index 00000000000..f1083d7dcb2 Binary files /dev/null and b/sound/instruments/synthesis_samples/piano/fluid_piano/c3.ogg differ diff --git a/sound/instruments/synthesis_samples/piano/fluid_piano/c4.ogg b/sound/instruments/synthesis_samples/piano/fluid_piano/c4.ogg new file mode 100644 index 00000000000..244ebc3d5f2 Binary files /dev/null and b/sound/instruments/synthesis_samples/piano/fluid_piano/c4.ogg differ diff --git a/sound/instruments/synthesis_samples/piano/fluid_piano/c5.ogg b/sound/instruments/synthesis_samples/piano/fluid_piano/c5.ogg new file mode 100644 index 00000000000..d3c68d64e9c Binary files /dev/null and b/sound/instruments/synthesis_samples/piano/fluid_piano/c5.ogg differ diff --git a/sound/instruments/synthesis_samples/piano/fluid_piano/c6.ogg b/sound/instruments/synthesis_samples/piano/fluid_piano/c6.ogg new file mode 100644 index 00000000000..2666ee66134 Binary files /dev/null and b/sound/instruments/synthesis_samples/piano/fluid_piano/c6.ogg differ diff --git a/sound/instruments/synthesis_samples/piano/fluid_piano/c7.ogg b/sound/instruments/synthesis_samples/piano/fluid_piano/c7.ogg new file mode 100644 index 00000000000..050e463c0d1 Binary files /dev/null and b/sound/instruments/synthesis_samples/piano/fluid_piano/c7.ogg differ diff --git a/sound/instruments/synthesis_samples/piano/fluid_piano/c8.ogg b/sound/instruments/synthesis_samples/piano/fluid_piano/c8.ogg new file mode 100644 index 00000000000..4793c5b7fd7 Binary files /dev/null and b/sound/instruments/synthesis_samples/piano/fluid_piano/c8.ogg differ diff --git a/sound/instruments/synthesis_samples/tones/Sawtooth.ogg b/sound/instruments/synthesis_samples/tones/Sawtooth.ogg new file mode 100644 index 00000000000..10b1930a64c Binary files /dev/null and b/sound/instruments/synthesis_samples/tones/Sawtooth.ogg differ diff --git a/sound/instruments/synthesis_samples/tones/Sine.ogg b/sound/instruments/synthesis_samples/tones/Sine.ogg new file mode 100644 index 00000000000..96a09d501b5 Binary files /dev/null and b/sound/instruments/synthesis_samples/tones/Sine.ogg differ diff --git a/sound/instruments/synthesis_samples/tones/Square.ogg b/sound/instruments/synthesis_samples/tones/Square.ogg new file mode 100644 index 00000000000..71029c07f95 Binary files /dev/null and b/sound/instruments/synthesis_samples/tones/Square.ogg differ diff --git a/sound/instruments/violin/Ab1.mid b/sound/instruments/violin/Ab1.mid new file mode 100644 index 00000000000..b8253364b4e Binary files /dev/null and b/sound/instruments/violin/Ab1.mid differ diff --git a/sound/instruments/violin/Ab2.mid b/sound/instruments/violin/Ab2.mid new file mode 100644 index 00000000000..4cd7f9b55a7 Binary files /dev/null and b/sound/instruments/violin/Ab2.mid differ diff --git a/sound/instruments/violin/Ab3.mid b/sound/instruments/violin/Ab3.mid new file mode 100644 index 00000000000..e827cfc635e Binary files /dev/null and b/sound/instruments/violin/Ab3.mid differ diff --git a/sound/instruments/violin/Ab4.mid b/sound/instruments/violin/Ab4.mid new file mode 100644 index 00000000000..57e1f76c976 Binary files /dev/null and b/sound/instruments/violin/Ab4.mid differ diff --git a/sound/instruments/violin/Ab5.mid b/sound/instruments/violin/Ab5.mid new file mode 100644 index 00000000000..59e95a6d997 Binary files /dev/null and b/sound/instruments/violin/Ab5.mid differ diff --git a/sound/instruments/violin/Ab6.mid b/sound/instruments/violin/Ab6.mid new file mode 100644 index 00000000000..9bd3436287b Binary files /dev/null and b/sound/instruments/violin/Ab6.mid differ diff --git a/sound/instruments/violin/Ab7.mid b/sound/instruments/violin/Ab7.mid new file mode 100644 index 00000000000..3c90af807e2 Binary files /dev/null and b/sound/instruments/violin/Ab7.mid differ diff --git a/sound/instruments/violin/Ab8.mid b/sound/instruments/violin/Ab8.mid new file mode 100644 index 00000000000..873d771f2ae Binary files /dev/null and b/sound/instruments/violin/Ab8.mid differ diff --git a/sound/instruments/violin/An1.mid b/sound/instruments/violin/An1.mid new file mode 100644 index 00000000000..d7f8a001d93 Binary files /dev/null and b/sound/instruments/violin/An1.mid differ diff --git a/sound/instruments/violin/An2.mid b/sound/instruments/violin/An2.mid new file mode 100644 index 00000000000..2f01800a075 Binary files /dev/null and b/sound/instruments/violin/An2.mid differ diff --git a/sound/instruments/violin/An3.mid b/sound/instruments/violin/An3.mid new file mode 100644 index 00000000000..c8ed3cdfa6c Binary files /dev/null and b/sound/instruments/violin/An3.mid differ diff --git a/sound/instruments/violin/An4.mid b/sound/instruments/violin/An4.mid new file mode 100644 index 00000000000..e7984ca7e62 Binary files /dev/null and b/sound/instruments/violin/An4.mid differ diff --git a/sound/instruments/violin/An5.mid b/sound/instruments/violin/An5.mid new file mode 100644 index 00000000000..e1fd228f7a9 Binary files /dev/null and b/sound/instruments/violin/An5.mid differ diff --git a/sound/instruments/violin/An6.mid b/sound/instruments/violin/An6.mid new file mode 100644 index 00000000000..1c8df6c98e5 Binary files /dev/null and b/sound/instruments/violin/An6.mid differ diff --git a/sound/instruments/violin/An7.mid b/sound/instruments/violin/An7.mid new file mode 100644 index 00000000000..2784428daf9 Binary files /dev/null and b/sound/instruments/violin/An7.mid differ diff --git a/sound/instruments/violin/An8.mid b/sound/instruments/violin/An8.mid new file mode 100644 index 00000000000..2db2ab70a7d Binary files /dev/null and b/sound/instruments/violin/An8.mid differ diff --git a/sound/instruments/violin/Bb1.mid b/sound/instruments/violin/Bb1.mid new file mode 100644 index 00000000000..693b73f5420 Binary files /dev/null and b/sound/instruments/violin/Bb1.mid differ diff --git a/sound/instruments/violin/Bb2.mid b/sound/instruments/violin/Bb2.mid new file mode 100644 index 00000000000..40da5f3da15 Binary files /dev/null and b/sound/instruments/violin/Bb2.mid differ diff --git a/sound/instruments/violin/Bb3.mid b/sound/instruments/violin/Bb3.mid new file mode 100644 index 00000000000..5bab6ccd636 Binary files /dev/null and b/sound/instruments/violin/Bb3.mid differ diff --git a/sound/instruments/violin/Bb4.mid b/sound/instruments/violin/Bb4.mid new file mode 100644 index 00000000000..dce830448ef Binary files /dev/null and b/sound/instruments/violin/Bb4.mid differ diff --git a/sound/instruments/violin/Bb5.mid b/sound/instruments/violin/Bb5.mid new file mode 100644 index 00000000000..fda796e27b9 Binary files /dev/null and b/sound/instruments/violin/Bb5.mid differ diff --git a/sound/instruments/violin/Bb6.mid b/sound/instruments/violin/Bb6.mid new file mode 100644 index 00000000000..9e5da684f43 Binary files /dev/null and b/sound/instruments/violin/Bb6.mid differ diff --git a/sound/instruments/violin/Bb7.mid b/sound/instruments/violin/Bb7.mid new file mode 100644 index 00000000000..215c56cbe7e Binary files /dev/null and b/sound/instruments/violin/Bb7.mid differ diff --git a/sound/instruments/violin/Bb8.mid b/sound/instruments/violin/Bb8.mid new file mode 100644 index 00000000000..4b55c34691f Binary files /dev/null and b/sound/instruments/violin/Bb8.mid differ diff --git a/sound/instruments/violin/Bn1.mid b/sound/instruments/violin/Bn1.mid new file mode 100644 index 00000000000..27968b5f9e7 Binary files /dev/null and b/sound/instruments/violin/Bn1.mid differ diff --git a/sound/instruments/violin/Bn2.mid b/sound/instruments/violin/Bn2.mid new file mode 100644 index 00000000000..54c9b99d03f Binary files /dev/null and b/sound/instruments/violin/Bn2.mid differ diff --git a/sound/instruments/violin/Bn3.mid b/sound/instruments/violin/Bn3.mid new file mode 100644 index 00000000000..f73476fb7bb Binary files /dev/null and b/sound/instruments/violin/Bn3.mid differ diff --git a/sound/instruments/violin/Bn4.mid b/sound/instruments/violin/Bn4.mid new file mode 100644 index 00000000000..2aa30708a6c Binary files /dev/null and b/sound/instruments/violin/Bn4.mid differ diff --git a/sound/instruments/violin/Bn5.mid b/sound/instruments/violin/Bn5.mid new file mode 100644 index 00000000000..0ebe636b714 Binary files /dev/null and b/sound/instruments/violin/Bn5.mid differ diff --git a/sound/instruments/violin/Bn6.mid b/sound/instruments/violin/Bn6.mid new file mode 100644 index 00000000000..3b8e1c217f7 Binary files /dev/null and b/sound/instruments/violin/Bn6.mid differ diff --git a/sound/instruments/violin/Bn7.mid b/sound/instruments/violin/Bn7.mid new file mode 100644 index 00000000000..afcb1982a13 Binary files /dev/null and b/sound/instruments/violin/Bn7.mid differ diff --git a/sound/instruments/violin/Bn8.mid b/sound/instruments/violin/Bn8.mid new file mode 100644 index 00000000000..3afd469256c Binary files /dev/null and b/sound/instruments/violin/Bn8.mid differ diff --git a/sound/instruments/violin/Cn1.mid b/sound/instruments/violin/Cn1.mid new file mode 100644 index 00000000000..857120f31f4 Binary files /dev/null and b/sound/instruments/violin/Cn1.mid differ diff --git a/sound/instruments/violin/Cn2.mid b/sound/instruments/violin/Cn2.mid new file mode 100644 index 00000000000..3ccd6670e87 Binary files /dev/null and b/sound/instruments/violin/Cn2.mid differ diff --git a/sound/instruments/violin/Cn3.mid b/sound/instruments/violin/Cn3.mid new file mode 100644 index 00000000000..1851e4f8d27 Binary files /dev/null and b/sound/instruments/violin/Cn3.mid differ diff --git a/sound/instruments/violin/Cn4.mid b/sound/instruments/violin/Cn4.mid new file mode 100644 index 00000000000..65e8b0efe4e Binary files /dev/null and b/sound/instruments/violin/Cn4.mid differ diff --git a/sound/instruments/violin/Cn5.mid b/sound/instruments/violin/Cn5.mid new file mode 100644 index 00000000000..544f921e43b Binary files /dev/null and b/sound/instruments/violin/Cn5.mid differ diff --git a/sound/instruments/violin/Cn6.mid b/sound/instruments/violin/Cn6.mid new file mode 100644 index 00000000000..7c78dab2f07 Binary files /dev/null and b/sound/instruments/violin/Cn6.mid differ diff --git a/sound/instruments/violin/Cn7.mid b/sound/instruments/violin/Cn7.mid new file mode 100644 index 00000000000..3abe4cde086 Binary files /dev/null and b/sound/instruments/violin/Cn7.mid differ diff --git a/sound/instruments/violin/Cn8.mid b/sound/instruments/violin/Cn8.mid new file mode 100644 index 00000000000..06f14081b3b Binary files /dev/null and b/sound/instruments/violin/Cn8.mid differ diff --git a/sound/instruments/violin/Cn9.mid b/sound/instruments/violin/Cn9.mid new file mode 100644 index 00000000000..62f4eef045a Binary files /dev/null and b/sound/instruments/violin/Cn9.mid differ diff --git a/sound/instruments/violin/Db1.mid b/sound/instruments/violin/Db1.mid new file mode 100644 index 00000000000..88dba851452 Binary files /dev/null and b/sound/instruments/violin/Db1.mid differ diff --git a/sound/instruments/violin/Db2.mid b/sound/instruments/violin/Db2.mid new file mode 100644 index 00000000000..b510926b45f Binary files /dev/null and b/sound/instruments/violin/Db2.mid differ diff --git a/sound/instruments/violin/Db3.mid b/sound/instruments/violin/Db3.mid new file mode 100644 index 00000000000..9954bbe478a Binary files /dev/null and b/sound/instruments/violin/Db3.mid differ diff --git a/sound/instruments/violin/Db4.mid b/sound/instruments/violin/Db4.mid new file mode 100644 index 00000000000..2c5ff74db0a Binary files /dev/null and b/sound/instruments/violin/Db4.mid differ diff --git a/sound/instruments/violin/Db5.mid b/sound/instruments/violin/Db5.mid new file mode 100644 index 00000000000..e5850a3fd04 Binary files /dev/null and b/sound/instruments/violin/Db5.mid differ diff --git a/sound/instruments/violin/Db6.mid b/sound/instruments/violin/Db6.mid new file mode 100644 index 00000000000..217c0ad014c Binary files /dev/null and b/sound/instruments/violin/Db6.mid differ diff --git a/sound/instruments/violin/Db7.mid b/sound/instruments/violin/Db7.mid new file mode 100644 index 00000000000..ec32bdbf904 Binary files /dev/null and b/sound/instruments/violin/Db7.mid differ diff --git a/sound/instruments/violin/Db8.mid b/sound/instruments/violin/Db8.mid new file mode 100644 index 00000000000..555bce3db0d Binary files /dev/null and b/sound/instruments/violin/Db8.mid differ diff --git a/sound/instruments/violin/Dn1.mid b/sound/instruments/violin/Dn1.mid new file mode 100644 index 00000000000..92e4e0d9581 Binary files /dev/null and b/sound/instruments/violin/Dn1.mid differ diff --git a/sound/instruments/violin/Dn2.mid b/sound/instruments/violin/Dn2.mid new file mode 100644 index 00000000000..34eb9d1db1b Binary files /dev/null and b/sound/instruments/violin/Dn2.mid differ diff --git a/sound/instruments/violin/Dn3.mid b/sound/instruments/violin/Dn3.mid new file mode 100644 index 00000000000..fbd56085aaf Binary files /dev/null and b/sound/instruments/violin/Dn3.mid differ diff --git a/sound/instruments/violin/Dn4.mid b/sound/instruments/violin/Dn4.mid new file mode 100644 index 00000000000..e13c7448292 Binary files /dev/null and b/sound/instruments/violin/Dn4.mid differ diff --git a/sound/instruments/violin/Dn5.mid b/sound/instruments/violin/Dn5.mid new file mode 100644 index 00000000000..8fd41e5c6fe Binary files /dev/null and b/sound/instruments/violin/Dn5.mid differ diff --git a/sound/instruments/violin/Dn6.mid b/sound/instruments/violin/Dn6.mid new file mode 100644 index 00000000000..d47329e8f9e Binary files /dev/null and b/sound/instruments/violin/Dn6.mid differ diff --git a/sound/instruments/violin/Dn7.mid b/sound/instruments/violin/Dn7.mid new file mode 100644 index 00000000000..b2496603876 Binary files /dev/null and b/sound/instruments/violin/Dn7.mid differ diff --git a/sound/instruments/violin/Dn8.mid b/sound/instruments/violin/Dn8.mid new file mode 100644 index 00000000000..56667a1a86d Binary files /dev/null and b/sound/instruments/violin/Dn8.mid differ diff --git a/sound/instruments/violin/Eb1.mid b/sound/instruments/violin/Eb1.mid new file mode 100644 index 00000000000..829e6fcf185 Binary files /dev/null and b/sound/instruments/violin/Eb1.mid differ diff --git a/sound/instruments/violin/Eb2.mid b/sound/instruments/violin/Eb2.mid new file mode 100644 index 00000000000..66029b340cc Binary files /dev/null and b/sound/instruments/violin/Eb2.mid differ diff --git a/sound/instruments/violin/Eb3.mid b/sound/instruments/violin/Eb3.mid new file mode 100644 index 00000000000..c982375941e Binary files /dev/null and b/sound/instruments/violin/Eb3.mid differ diff --git a/sound/instruments/violin/Eb4.mid b/sound/instruments/violin/Eb4.mid new file mode 100644 index 00000000000..016ed4f1edf Binary files /dev/null and b/sound/instruments/violin/Eb4.mid differ diff --git a/sound/instruments/violin/Eb5.mid b/sound/instruments/violin/Eb5.mid new file mode 100644 index 00000000000..ddb511795df Binary files /dev/null and b/sound/instruments/violin/Eb5.mid differ diff --git a/sound/instruments/violin/Eb6.mid b/sound/instruments/violin/Eb6.mid new file mode 100644 index 00000000000..b7242b9ab99 Binary files /dev/null and b/sound/instruments/violin/Eb6.mid differ diff --git a/sound/instruments/violin/Eb7.mid b/sound/instruments/violin/Eb7.mid new file mode 100644 index 00000000000..773538340a5 Binary files /dev/null and b/sound/instruments/violin/Eb7.mid differ diff --git a/sound/instruments/violin/Eb8.mid b/sound/instruments/violin/Eb8.mid new file mode 100644 index 00000000000..4ad074e173b Binary files /dev/null and b/sound/instruments/violin/Eb8.mid differ diff --git a/sound/instruments/violin/En1.mid b/sound/instruments/violin/En1.mid new file mode 100644 index 00000000000..79ab68df9df Binary files /dev/null and b/sound/instruments/violin/En1.mid differ diff --git a/sound/instruments/violin/En2.mid b/sound/instruments/violin/En2.mid new file mode 100644 index 00000000000..cd61c8d0de5 Binary files /dev/null and b/sound/instruments/violin/En2.mid differ diff --git a/sound/instruments/violin/En3.mid b/sound/instruments/violin/En3.mid new file mode 100644 index 00000000000..da5b703d545 Binary files /dev/null and b/sound/instruments/violin/En3.mid differ diff --git a/sound/instruments/violin/En4.mid b/sound/instruments/violin/En4.mid new file mode 100644 index 00000000000..f7d3af024ff Binary files /dev/null and b/sound/instruments/violin/En4.mid differ diff --git a/sound/instruments/violin/En5.mid b/sound/instruments/violin/En5.mid new file mode 100644 index 00000000000..d3d353943f9 Binary files /dev/null and b/sound/instruments/violin/En5.mid differ diff --git a/sound/instruments/violin/En6.mid b/sound/instruments/violin/En6.mid new file mode 100644 index 00000000000..73eb5b0697d Binary files /dev/null and b/sound/instruments/violin/En6.mid differ diff --git a/sound/instruments/violin/En7.mid b/sound/instruments/violin/En7.mid new file mode 100644 index 00000000000..79a9462c844 Binary files /dev/null and b/sound/instruments/violin/En7.mid differ diff --git a/sound/instruments/violin/En8.mid b/sound/instruments/violin/En8.mid new file mode 100644 index 00000000000..88947fc7318 Binary files /dev/null and b/sound/instruments/violin/En8.mid differ diff --git a/sound/instruments/violin/Fn1.mid b/sound/instruments/violin/Fn1.mid new file mode 100644 index 00000000000..abe0d4e4051 Binary files /dev/null and b/sound/instruments/violin/Fn1.mid differ diff --git a/sound/instruments/violin/Fn2.mid b/sound/instruments/violin/Fn2.mid new file mode 100644 index 00000000000..d245bef3b54 Binary files /dev/null and b/sound/instruments/violin/Fn2.mid differ diff --git a/sound/instruments/violin/Fn3.mid b/sound/instruments/violin/Fn3.mid new file mode 100644 index 00000000000..e532e30dac9 Binary files /dev/null and b/sound/instruments/violin/Fn3.mid differ diff --git a/sound/instruments/violin/Fn4.mid b/sound/instruments/violin/Fn4.mid new file mode 100644 index 00000000000..47219c72fa2 Binary files /dev/null and b/sound/instruments/violin/Fn4.mid differ diff --git a/sound/instruments/violin/Fn5.mid b/sound/instruments/violin/Fn5.mid new file mode 100644 index 00000000000..630d16371d9 Binary files /dev/null and b/sound/instruments/violin/Fn5.mid differ diff --git a/sound/instruments/violin/Fn6.mid b/sound/instruments/violin/Fn6.mid new file mode 100644 index 00000000000..08cbc981bdb Binary files /dev/null and b/sound/instruments/violin/Fn6.mid differ diff --git a/sound/instruments/violin/Fn7.mid b/sound/instruments/violin/Fn7.mid new file mode 100644 index 00000000000..6c28c7d272e Binary files /dev/null and b/sound/instruments/violin/Fn7.mid differ diff --git a/sound/instruments/violin/Fn8.mid b/sound/instruments/violin/Fn8.mid new file mode 100644 index 00000000000..2d73762f269 Binary files /dev/null and b/sound/instruments/violin/Fn8.mid differ diff --git a/sound/instruments/violin/Gb1.mid b/sound/instruments/violin/Gb1.mid new file mode 100644 index 00000000000..d18668e8911 Binary files /dev/null and b/sound/instruments/violin/Gb1.mid differ diff --git a/sound/instruments/violin/Gb2.mid b/sound/instruments/violin/Gb2.mid new file mode 100644 index 00000000000..302f0c6fdc1 Binary files /dev/null and b/sound/instruments/violin/Gb2.mid differ diff --git a/sound/instruments/violin/Gb3.mid b/sound/instruments/violin/Gb3.mid new file mode 100644 index 00000000000..1f592fc9039 Binary files /dev/null and b/sound/instruments/violin/Gb3.mid differ diff --git a/sound/instruments/violin/Gb4.mid b/sound/instruments/violin/Gb4.mid new file mode 100644 index 00000000000..45854126f98 Binary files /dev/null and b/sound/instruments/violin/Gb4.mid differ diff --git a/sound/instruments/violin/Gb5.mid b/sound/instruments/violin/Gb5.mid new file mode 100644 index 00000000000..fb1e1da339a Binary files /dev/null and b/sound/instruments/violin/Gb5.mid differ diff --git a/sound/instruments/violin/Gb6.mid b/sound/instruments/violin/Gb6.mid new file mode 100644 index 00000000000..bfa896bb784 Binary files /dev/null and b/sound/instruments/violin/Gb6.mid differ diff --git a/sound/instruments/violin/Gb7.mid b/sound/instruments/violin/Gb7.mid new file mode 100644 index 00000000000..a27763c1d47 Binary files /dev/null and b/sound/instruments/violin/Gb7.mid differ diff --git a/sound/instruments/violin/Gb8.mid b/sound/instruments/violin/Gb8.mid new file mode 100644 index 00000000000..aaab80a7276 Binary files /dev/null and b/sound/instruments/violin/Gb8.mid differ diff --git a/sound/instruments/violin/Gn1.mid b/sound/instruments/violin/Gn1.mid new file mode 100644 index 00000000000..1df52ab0760 Binary files /dev/null and b/sound/instruments/violin/Gn1.mid differ diff --git a/sound/instruments/violin/Gn2.mid b/sound/instruments/violin/Gn2.mid new file mode 100644 index 00000000000..6e0ca383127 Binary files /dev/null and b/sound/instruments/violin/Gn2.mid differ diff --git a/sound/instruments/violin/Gn3.mid b/sound/instruments/violin/Gn3.mid new file mode 100644 index 00000000000..bb3e6dedcbf Binary files /dev/null and b/sound/instruments/violin/Gn3.mid differ diff --git a/sound/instruments/violin/Gn4.mid b/sound/instruments/violin/Gn4.mid new file mode 100644 index 00000000000..0c46432afee Binary files /dev/null and b/sound/instruments/violin/Gn4.mid differ diff --git a/sound/instruments/violin/Gn5.mid b/sound/instruments/violin/Gn5.mid new file mode 100644 index 00000000000..f39dcf5e2b9 Binary files /dev/null and b/sound/instruments/violin/Gn5.mid differ diff --git a/sound/instruments/violin/Gn6.mid b/sound/instruments/violin/Gn6.mid new file mode 100644 index 00000000000..0efa2259ca1 Binary files /dev/null and b/sound/instruments/violin/Gn6.mid differ diff --git a/sound/instruments/violin/Gn7.mid b/sound/instruments/violin/Gn7.mid new file mode 100644 index 00000000000..22fd1b6bcb0 Binary files /dev/null and b/sound/instruments/violin/Gn7.mid differ diff --git a/sound/instruments/violin/Gn8.mid b/sound/instruments/violin/Gn8.mid new file mode 100644 index 00000000000..16b7171d627 Binary files /dev/null and b/sound/instruments/violin/Gn8.mid differ diff --git a/sound/weapons/banjoslap.ogg b/sound/weapons/banjoslap.ogg new file mode 100644 index 00000000000..06a86a535dd Binary files /dev/null and b/sound/weapons/banjoslap.ogg differ diff --git a/sound/weapons/guitarslam.ogg b/sound/weapons/guitarslam.ogg new file mode 100644 index 00000000000..4fa53db9404 Binary files /dev/null and b/sound/weapons/guitarslam.ogg differ diff --git a/tgui/packages/tgui/components/Box.js b/tgui/packages/tgui/components/Box.js index 9d5e71c78a7..ada17f05d55 100644 --- a/tgui/packages/tgui/components/Box.js +++ b/tgui/packages/tgui/components/Box.js @@ -68,7 +68,10 @@ const mapColorPropTo = attrName => (style, value) => { const styleMapperByPropName = { // Direct mapping + display: mapRawPropTo('display'), position: mapRawPropTo('position'), + float: mapRawPropTo('float'), + clear: mapRawPropTo('clear'), overflow: mapRawPropTo('overflow'), overflowX: mapRawPropTo('overflow-x'), overflowY: mapRawPropTo('overflow-y'), @@ -88,6 +91,9 @@ const styleMapperByPropName = { opacity: mapRawPropTo('opacity'), textAlign: mapRawPropTo('text-align'), verticalAlign: mapRawPropTo('vertical-align'), + textTransform: mapRawPropTo('text-transform'), + wordWrap: mapRawPropTo('word-wrap'), + textOverflow: mapRawPropTo('text-overflow'), // Boolean props inline: mapBooleanPropTo('display', 'inline-block'), bold: mapBooleanPropTo('font-weight', 'bold'), @@ -125,6 +131,18 @@ const styleMapperByPropName = { color: mapColorPropTo('color'), textColor: mapColorPropTo('color'), backgroundColor: mapColorPropTo('background-color'), + // Flex props + order: mapRawPropTo('order'), + flexDirection: mapRawPropTo('flex-direction'), + flexGrow: mapRawPropTo('flex-grow'), + flexShrink: mapRawPropTo('flex-shrink'), + flexWrap: mapRawPropTo('flex-wrap'), + flexFlow: mapRawPropTo('flex-flow'), + flexBasis: mapRawPropTo('flex-basis'), + flex: mapRawPropTo('flex'), + alignItems: mapRawPropTo('align-items'), + justifyContent: mapRawPropTo('justify-content'), + alignSelf: mapRawPropTo('align-self'), // Utility props fillPositionedParent: (style, value) => { if (value) { @@ -140,6 +158,9 @@ const styleMapperByPropName = { export const computeBoxProps = props => { const computedProps = {}; const computedStyles = {}; + if (props.double) { + computedStyles["transform"] = "scale(2);"; + } // Compute props for (let propName of Object.keys(props)) { if (propName === 'style') { diff --git a/tgui/packages/tgui/components/Flex.js b/tgui/packages/tgui/components/Flex.js index e63051438f3..26de30ef93c 100644 --- a/tgui/packages/tgui/components/Flex.js +++ b/tgui/packages/tgui/components/Flex.js @@ -8,9 +8,11 @@ export const computeFlexProps = props => { direction, wrap, align, + alignContent, justify, inline, spacing = 0, + spacingPrecise = 0, ...rest } = props; return { @@ -23,6 +25,7 @@ export const computeFlexProps = props => { ), inline && 'Flex--inline', spacing > 0 && 'Flex--spacing--' + spacing, + spacingPrecise > 0 && 'Flex--spacingPrecise--' + spacingPrecise, className, ]), style: { @@ -30,6 +33,7 @@ export const computeFlexProps = props => { 'flex-direction': direction, 'flex-wrap': wrap, 'align-items': align, + 'align-content': alignContent, 'justify-content': justify, }, ...rest, diff --git a/tgui/packages/tgui/components/Input.js b/tgui/packages/tgui/components/Input.js index 3c73d88a8fd..b7f2dcb1f15 100644 --- a/tgui/packages/tgui/components/Input.js +++ b/tgui/packages/tgui/components/Input.js @@ -76,6 +76,11 @@ export class Input extends Component { const input = this.inputRef.current; if (input) { input.value = toInputValue(nextValue); + if (this.props.autofocus) { + input.focus(); + input.selectionStart = 0; + input.selectionEnd = input.value.length; + } } } @@ -104,6 +109,7 @@ export class Input extends Component { value, maxLength, placeholder, + autofocus, ...boxProps } = props; // Box props diff --git a/tgui/packages/tgui/components/Knob.js b/tgui/packages/tgui/components/Knob.js index 501308aea8d..0e817111653 100644 --- a/tgui/packages/tgui/components/Knob.js +++ b/tgui/packages/tgui/components/Knob.js @@ -35,6 +35,7 @@ export const Knob = props => { size, bipolar, children, + popUpPosition, ...rest } = props; return ( @@ -102,7 +103,10 @@ export const Knob = props => { {dragging && ( -
+
{displayElement}
)} diff --git a/tgui/packages/tgui/components/LabeledList.js b/tgui/packages/tgui/components/LabeledList.js index e2b515014a2..43dbd0d5a32 100644 --- a/tgui/packages/tgui/components/LabeledList.js +++ b/tgui/packages/tgui/components/LabeledList.js @@ -20,6 +20,7 @@ export const LabeledListItem = props => { labelColor = 'label', color, textAlign, + verticalAlign, buttons, content, children, @@ -33,6 +34,7 @@ export const LabeledListItem = props => { { as="td" color={color} textAlign={textAlign} + verticalAlign={verticalAlign} className={classes([ 'LabeledList__cell', 'LabeledList__content', diff --git a/tgui/packages/tgui/components/Modal.js b/tgui/packages/tgui/components/Modal.js index 916150de402..125593902b6 100644 --- a/tgui/packages/tgui/components/Modal.js +++ b/tgui/packages/tgui/components/Modal.js @@ -6,10 +6,20 @@ export const Modal = props => { const { className, children, + onEnter, ...rest } = props; + let handleKeyDown; + if (onEnter) { + handleKeyDown = e => { + if (e.keyCode === 13) { + onEnter(e); + } + }; + } return ( - +
{ level = 1, buttons, content, + stretchContents, + noTopPadding, children, + scrollable, ...rest } = props; const hasTitle = !isFalsy(title) || !isFalsy(buttons); @@ -18,6 +21,7 @@ export const Section = props => { className={classes([ 'Section', 'Section--level--' + level, + props.flexGrow && 'Section--flex', className, ])} {...rest}> @@ -32,10 +36,14 @@ export const Section = props => {
)} {hasContent && ( -
+ {content} {children} -
+
)} ); diff --git a/tgui/packages/tgui/index.js b/tgui/packages/tgui/index.js index 59199f9edbe..45012a7e956 100644 --- a/tgui/packages/tgui/index.js +++ b/tgui/packages/tgui/index.js @@ -3,30 +3,30 @@ import 'core-js/es'; import 'core-js/web/immediate'; import 'core-js/web/queue-microtask'; import 'core-js/web/timers'; -import 'regenerator-runtime/runtime'; -import './polyfills/html5shiv'; -import './polyfills/ie8'; -import './polyfills/dom4'; -import './polyfills/css-om'; -import './polyfills/inferno'; - -// Themes -import './styles/main.scss'; -import './styles/themes/cardtable.scss'; -import './styles/themes/malfunction.scss'; -import './styles/themes/ntos.scss'; -import './styles/themes/hackerman.scss'; -import './styles/themes/retro.scss'; -import './styles/themes/syndicate.scss'; - import { loadCSS } from 'fg-loadcss'; import { render } from 'inferno'; +import 'regenerator-runtime/runtime'; import { setupHotReloading } from 'tgui-dev-server/link/client'; import { backendUpdate } from './backend'; import { IS_IE8 } from './byond'; import { setupDrag } from './drag'; import { logger } from './logging'; +import './polyfills/css-om'; +import './polyfills/dom4'; +import './polyfills/html5shiv'; +import './polyfills/ie8'; +import './polyfills/inferno'; import { createStore, StoreProvider } from './store'; +// Themes +import './styles/main.scss'; +import './styles/themes/cardtable.scss'; +import './styles/themes/hackerman.scss'; +import './styles/themes/malfunction.scss'; +import './styles/themes/ntos.scss'; +import './styles/themes/retro.scss'; +import './styles/themes/syndicate.scss'; + + const enteredBundleAt = Date.now(); const store = createStore(); diff --git a/tgui/packages/tgui/interfaces/APC.js b/tgui/packages/tgui/interfaces/APC.js new file mode 100644 index 00000000000..652188a3073 --- /dev/null +++ b/tgui/packages/tgui/interfaces/APC.js @@ -0,0 +1,195 @@ +import { Fragment } from 'inferno'; +import { useBackend } from '../backend'; +import { Box, Button, LabeledList, NoticeBox, ProgressBar, Section } from '../components'; +import { Window } from '../layouts'; +import { InterfaceLockNoticeBox } from './common/InterfaceLockNoticeBox'; + +export const APC = (props, context) => { + return ( + + + + + + ); +}; + +const powerStatusMap = { + 2: { + color: 'good', + externalPowerText: 'External Power', + chargingText: 'Fully Charged', + }, + 1: { + color: 'average', + externalPowerText: 'Low External Power', + chargingText: 'Charging', + }, + 0: { + color: 'bad', + externalPowerText: 'No External Power', + chargingText: 'Not Charging', + }, +}; + +const malfMap = { + 1: { + icon: 'terminal', + content: 'Override Programming', + action: 'hack', + }, + 2: { + icon: 'caret-square-down', + content: 'Shunt Core Process', + action: 'occupy', + }, + 3: { + icon: 'caret-square-left', + content: 'Return to Main Core', + action: 'deoccupy', + }, + 4: { + icon: 'caret-square-down', + content: 'Shunt Core Process', + action: 'occupy', + }, +}; + +const ApcContent = (props, context) => { + const { act, data } = useBackend(context); + const locked = data.locked && !data.siliconUser; + const normallyLocked = data.normallyLocked; + const externalPowerStatus = powerStatusMap[data.externalPower] + || powerStatusMap[0]; + const chargingStatus = powerStatusMap[data.chargingStatus] + || powerStatusMap[0]; + const channelArray = data.powerChannels || []; + const malfStatus = malfMap[data.malfStatus] || malfMap[0]; + const adjustedCellChange = data.powerCellStatus / 100; + + return ( + + +
+ + act('breaker')} /> + )}> + [ {externalPowerStatus.externalPowerText} ] + + + + + act('charge')} /> + )}> + [ {chargingStatus.chargingText} ] + + +
+
+ + {channelArray.map(channel => { + const { topicParams } = channel; + return ( + + = 2 ? 'good' : 'bad'}> + {channel.status >= 2 ? 'On' : 'Off'} + +
+
+ {!!data.malfStatus && ( +
+
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/AiAirlock.js b/tgui/packages/tgui/interfaces/AiAirlock.js new file mode 100644 index 00000000000..ee47422d32d --- /dev/null +++ b/tgui/packages/tgui/interfaces/AiAirlock.js @@ -0,0 +1,202 @@ +import { Fragment } from 'inferno'; +import { useBackend } from '../backend'; +import { Button, LabeledList, Section } from '../components'; +import { Window } from '../layouts'; + +const dangerMap = { + 2: { + color: 'good', + localStatusText: 'Offline', + }, + 1: { + color: 'average', + localStatusText: 'Caution', + }, + 0: { + color: 'bad', + localStatusText: 'Optimal', + }, +}; + +export const AiAirlock = (props, context) => { + const { act, data } = useBackend(context); + const statusMain = dangerMap[data.power.main] || dangerMap[0]; + const statusBackup = dangerMap[data.power.backup] || dangerMap[0]; + const statusElectrify = dangerMap[data.shock] || dangerMap[0]; + return ( + + +
+ + act('disrupt-main')} /> + )}> + {data.power.main ? 'Online' : 'Offline'} + {' '} + {(!data.wires.main_power) + && '[Wires have been cut!]' + || (data.power.main_timeleft > 0 + && `[${data.power.main_timeleft}s]`)} + + act('disrupt-backup')} /> + )}> + {data.power.backup ? 'Online' : 'Offline'} + {' '} + {(!data.wires.backup_power) + && '[Wires have been cut!]' + || (data.power.backup_timeleft > 0 + && `[${data.power.backup_timeleft}s]`)} + + +
+
+ + act('idscan-toggle')} /> + )}> + {!data.wires.id_scanner && '[Wires have been cut!]'} + + act('emergency-toggle')} /> + )} /> + + act('bolt-toggle')} /> + )}> + {!data.wires.bolts && '[Wires have been cut!]'} + + act('light-toggle')} /> + )}> + {!data.wires.lights && '[Wires have been cut!]'} + + act('safe-toggle')} /> + )}> + {!data.wires.safe && '[Wires have been cut!]'} + + act('speed-toggle')} /> + )}> + {!data.wires.timing && '[Wires have been cut!]'} + + + act('open-close')} /> + )}> + {!!(data.locked || data.welded) && ( + + [Door is {data.locked ? 'bolted' : ''} + {(data.locked && data.welded) ? ' and ' : ''} + {data.welded ? 'welded' : ''}!] + + )} + + +
+
+
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/BodyScanner.js b/tgui/packages/tgui/interfaces/BodyScanner.js new file mode 100644 index 00000000000..48a4a08e20f --- /dev/null +++ b/tgui/packages/tgui/interfaces/BodyScanner.js @@ -0,0 +1,433 @@ +import { round } from 'common/math'; +import { Fragment } from 'inferno'; +import { useBackend } from "../backend"; +import { AnimatedNumber, Box, Button, Flex, Icon, LabeledList, ProgressBar, Section, Table, Tooltip } from "../components"; +import { Window } from "../layouts"; + +const stats = [ + ['good', 'Alive'], + ['average', 'Critical'], + ['bad', 'DEAD'], +]; + +const abnormalities = [ + ['hasBorer', 'bad', 'Large growth detected in frontal lobe,' + + ' possibly cancerous. Surgical removal is recommended.'], + ['hasVirus', 'bad', 'Viral pathogen detected in blood stream.'], + ['blind', 'average', 'Cataracts detected.'], + ['colourblind', 'average', 'Photoreceptor abnormalities detected.'], + ['nearsighted', 'average', 'Retinal misalignment detected.'], +]; + +const damages = [ + ['Respiratory', 'oxyLoss'], + ['Brain', 'brainLoss'], + ['Toxin', 'toxLoss'], + ['Radioactive', 'radLoss'], + ['Brute', 'bruteLoss'], + ['Genetic', 'cloneLoss'], + ['Burn', 'fireLoss'], + ['Paralysis', 'paralysis'], +]; + +const damageRange = { + average: [0.25, 0.5], + bad: [0.5, Infinity], +}; + +const mapTwoByTwo = (a, c) => { + let result = []; + for (let i = 0; i < a.length; i += 2) { + result.push(c(a[i], a[i + 1], i)); + } + return result; +}; + +const reduceOrganStatus = A => { + return A.length > 0 + ? A + .filter(s => !!s && s.length > 0) + .reduce((a, s) => a === null + ? [s] + : {a}
{s}
, null + ) + : null; +}; + +const germStatus = i => { + if (i > 100) { + if (i < 300) { return "mild infection"; } + if (i < 400) { return "mild infection+"; } + if (i < 500) { return "mild infection++"; } + if (i < 700) { return "acute infection"; } + if (i < 800) { return "acute infection+"; } + if (i < 900) { return "acute infection++"; } + if (i >= 900) { return "septic"; } + } + + return ""; +}; + +export const BodyScanner = (props, context) => { + const { data } = useBackend(context); + const { + occupied, + occupant = {}, + } = data; + const body = occupied ? ( + + ) : ( + + ); + return ( + + + {body} + + + ); +}; + +const BodyScannerMain = props => { + const { + occupant, + } = props; + return ( + + + + + + + + ); +}; + +const BodyScannerMainOccupant = (props, context) => { + const { act, data } = useBackend(context); + const { + occupant, + } = data; + return ( +
+ + + + )}> + + + {occupant.name} + + + + + + {stats[occupant.stat][1]} + + + °C,  + °F + + + {occupant.implant_len ? ( + + {occupant.implant.map(im => im.name).join(', ')} + + ) : ( + + None + + )} + + +
+ ); +}; + +const BodyScannerMainAbnormalities = props => { + const { + occupant, + } = props; + if (!(occupant.hasBorer || occupant.blind + || occupant.colourblind || occupant.nearsighted + || occupant.hasVirus)) { + return ( +
+ + No abnormalities found. + +
+ ); + } + + return ( +
+ {abnormalities.map((a, i) => { + if (occupant[a[0]]) { + return ( + + {a[2]} + + ); + } + })} +
+ ); +}; + +const BodyScannerMainDamage = props => { + const { + occupant, + } = props; + return ( +
+ + {mapTwoByTwo(damages, (d1, d2, i) => ( + + + + {d1[0]}: + + + {!!d2 && d2[0] + ":"} + + + + + + + + {!!d2 && ( + + )} + + + + ))} +
+
+ ); +}; + +const BodyScannerMainDamageBar = props => { + return ( + + {round(props.value, 0)} + + ); +}; + +const BodyScannerMainOrgansExternal = props => { + if (props.organs.length === 0) { + return ( +
+ + N/A + +
+ ); + } + + return ( +
+ + + + Name + + + Damage + + + Injuries + + + {props.organs.map((o, i) => ( + + + {o.name} + + + 0 && "0.5rem"} + value={o.totalLoss / 100} + ranges={damageRange}> + + {!!o.bruteLoss && ( + + + {round(o.bruteLoss, 0)}  + + )} + {!!o.fireLoss && ( + + + {round(o.fireLoss, 0)} + + )} + + + {round(o.totalLoss, 0)} + + + + + + {reduceOrganStatus([ + o.internalBleeding && "Internal bleeding", + o.lungRuptured && "Ruptured lung", + !!o.status.broken && o.status.broken, + germStatus(o.germ_level), + !!o.open && "Open incision", + ])} + + + {reduceOrganStatus([ + !!o.status.splinted && "Splinted", + !!o.status.robotic && "Robotic", + !!o.status.dead && ( + + DEAD + + ), + ])} + {reduceOrganStatus(o.shrapnel.map( + s => s.known + ? s.name + : "Unknown object" + ))} + + + + ))} +
+
+ ); +}; + +const BodyScannerMainOrgansInternal = props => { + if (props.organs.length === 0) { + return ( +
+ + N/A + +
+ ); + } + + return ( +
+ + + + Name + + + Damage + + + Injuries + + + {props.organs.map((o, i) => ( + + + {o.name} + + + 0 && "0.5rem"} + ranges={damageRange}> + {round(o.damage, 0)} + + + + + {reduceOrganStatus([ + germStatus(o.germ_level), + ])} + + + {reduceOrganStatus([ + (o.robotic === 1) && "Robotic", + (o.robotic === 2) && "Assisted", + !!o.dead && ( + + DEAD + + ), + ])} + + + + ))} +
+
+ ); +}; + +const BodyScannerEmpty = () => { + return ( +
+ + +
+ No occupant detected. +
+
+
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/Canister.js b/tgui/packages/tgui/interfaces/Canister.js new file mode 100644 index 00000000000..8c184f71e68 --- /dev/null +++ b/tgui/packages/tgui/interfaces/Canister.js @@ -0,0 +1,248 @@ +import { toFixed } from 'common/math'; +import { Fragment } from 'inferno'; +import { useBackend } from '../backend'; +import { AnimatedNumber, Box, Button, Dropdown, Icon, Knob, LabeledControls, LabeledList, Section, Tooltip } from '../components'; +import { formatSiUnit } from '../format'; +import { Window } from '../layouts'; + +export const Canister = (props, context) => { + const { act, data } = useBackend(context); + const { + portConnected, + tankPressure, + releasePressure, + defaultReleasePressure, + minReleasePressure, + maxReleasePressure, + valveOpen, + name, + canLabel, + colorContainer, + color_index, + hasHoldingTank, + holdingTank, + } = data; + + + let preset_prim = ""; + if (color_index["prim"]) { + preset_prim = colorContainer.prim.options[color_index["prim"]]["name"]; + } + let preset_sec = ""; + if (color_index["sec"]) { + preset_sec = colorContainer.sec.options[color_index["sec"]]["name"]; + } + let preset_ter = ""; + if (color_index["ter"]) { + preset_ter = colorContainer.ter.options[color_index["ter"]]["name"]; + } + let preset_quart = ""; + if (color_index["quart"]) { + preset_quart + = colorContainer.quart.options[color_index["quart"]]["name"]; + } + + let array_prim = []; + let array_sec = []; + let array_ter = []; + let array_quart = []; + let i = 0; + + for (i = 0; i < colorContainer.prim.options.length; i++) { + array_prim.push(colorContainer.prim.options[i]["name"]); + } + for (i = 0; i < colorContainer.sec.options.length; i++) { + array_sec.push(colorContainer.sec.options[i]["name"]); + } + for (i = 0; i < colorContainer.ter.options.length; i++) { + array_ter.push(colorContainer.ter.options[i]["name"]); + } + for (i = 0; i < colorContainer.quart.options.length; i++) { + array_quart.push(colorContainer.quart.options[i]["name"]); + } + let paintSection = ""; + if (canLabel) { + paintSection = ( +
+ + + act('recolor', + { nc: array_prim.indexOf(value), + ctype: "prim" })} /> + + + act('recolor', + { nc: array_sec.indexOf(value), + ctype: "sec" })} /> + + + act('recolor', + { nc: array_ter.indexOf(value), + ctype: "ter" })} /> + + + act('recolor', + { nc: array_quart.indexOf(value), + ctype: "quart" })} /> + + +
+ ); + } + + return ( + + +
act('relabel')} /> + )}> + + + { + if (value < 10000) { + return toFixed(value) + ' kPa'; + } + return formatSiUnit(value * 1000, 1, 'Pa'); + }} /> + + + + act('pressure', { + pressure: value, + })} /> +
+
act('eject')} /> + )}> + {!!hasHoldingTank && ( + + + {holdingTank.name} + + + kPa + + + )} + {!hasHoldingTank && ( + + No Holding Tank + + )} +
+ {paintSection} +
+
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/ChemDispenser.js b/tgui/packages/tgui/interfaces/ChemDispenser.js new file mode 100644 index 00000000000..7a7300fec23 --- /dev/null +++ b/tgui/packages/tgui/interfaces/ChemDispenser.js @@ -0,0 +1,181 @@ +import { Fragment } from 'inferno'; +import { useBackend } from "../backend"; +import { Box, Button, Flex, LabeledList, ProgressBar, Section } from "../components"; +import { BeakerContents } from "../interfaces/common/BeakerContents"; +import { Window } from "../layouts"; + +const dispenseAmounts = [1, 5, 10, 20, 30, 50]; +const removeAmounts = [1, 5, 10]; + +export const ChemDispenser = (props, context) => { + return ( + + + + + + + + ); +}; + +const ChemDispenserSettings = (properties, context) => { + const { act, data } = useBackend(context); + const { + amount, + energy, + maxEnergy, + } = data; + return ( +
+ + + + {energy} / {maxEnergy} Units + + + + + {dispenseAmounts.map((a, i) => ( + +
+ ); +}; + +const ChemDispenserChemicals = (properties, context) => { + const { act, data } = useBackend(context); + const { + chemicals = [], + } = data; + const flexFillers = []; + for (let i = 0; i < (chemicals.length + 1) % 3; i++) { + flexFillers.push(true); + } + return ( +
+ + {chemicals.map((c, i) => ( + +
+ ); +}; + +const ChemDispenserBeaker = (properties, context) => { + const { act, data } = useBackend(context); + const { + isBeakerLoaded, + beakerCurrentVolume, + beakerMaxVolume, + beakerContents = [], + } = data; + return ( +
+ {!!isBeakerLoaded && ( + + {beakerCurrentVolume} / {beakerMaxVolume} units + + )} +
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/ChemHeater.js b/tgui/packages/tgui/interfaces/ChemHeater.js new file mode 100644 index 00000000000..e7ea7a6ace9 --- /dev/null +++ b/tgui/packages/tgui/interfaces/ChemHeater.js @@ -0,0 +1,106 @@ +import { round, toFixed } from 'common/math'; +import { Fragment } from 'inferno'; +import { useBackend } from "../backend"; +import { AnimatedNumber, Box, Button, LabeledList, NumberInput, Section } from "../components"; +import { BeakerContents } from '../interfaces/common/BeakerContents'; +import { Window } from "../layouts"; + +export const ChemHeater = (_props, _context) => { + return ( + + + + + + + ); +}; + +const ChemHeaterSettings = (_properties, context) => { + const { act, data } = useBackend(context); + const { + targetTemp, + targetTempReached, + autoEject, + isActive, + currentTemp, + isBeakerLoaded, + } = data; + return ( +
+
+ ); +}; + +const ChemHeaterBeaker = (_properties, context) => { + const { act, data } = useBackend(context); + const { + isBeakerLoaded, + beakerCurrentVolume, + beakerMaxVolume, + beakerContents, + } = data; + return ( +
+ + {beakerCurrentVolume} / {beakerMaxVolume} units + +
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/ChemMaster.js b/tgui/packages/tgui/interfaces/ChemMaster.js new file mode 100644 index 00000000000..d0a70d2b4ce --- /dev/null +++ b/tgui/packages/tgui/interfaces/ChemMaster.js @@ -0,0 +1,408 @@ +import { Fragment } from 'inferno'; +import { useBackend } from "../backend"; +import { Box, Button, Flex, Icon, LabeledList, Section } from "../components"; +import { Window } from "../layouts"; +import { BeakerContents } from './common/BeakerContents'; +import { ComplexModal, modalOpen, modalRegisterBodyOverride } from './common/ComplexModal'; + +const transferAmounts = [1, 5, 10]; +const bottleStyles = [ + "bottle.png", + "small_bottle.png", + "wide_bottle.png", + "round_bottle.png", + "reagent_bottle.png", +]; + +const analyzeModalBodyOverride = (modal, context) => { + const { act, data } = useBackend(context); + const result = modal.args.analysis; + return ( +
+ + + + {result.name} + + + {(result.desc || "").length > 0 ? result.desc : "N/A"} + + {result.blood_type && ( + + + {result.blood_type} + + + {result.blood_dna} + + + )} + {!data.condi && ( +
+ ); +}; + +export const ChemMaster = (props, context) => { + const { data } = useBackend(context); + const { + condi, + beaker, + beaker_reagents = [], + buffer_reagents = [], + mode, + } = data; + return ( + + + + 0} + /> + + 0} + /> + + + + ); +}; + +const ChemMasterBeaker = (props, context) => { + const { act } = useBackend(context); + const { + beaker, + beakerReagents, + bufferNonEmpty, + } = props; + return ( +
act('eject')} + /> + :
+ ); +}; + +const ChemMasterBuffer = (props, context) => { + const { act } = useBackend(context); + const { + mode, + bufferReagents = [], + } = props; + return ( +
+ Transferring to  +
+ ); +}; + +const ChemMasterProduction = (props, context) => { + const { act } = useBackend(context); + if (!props.bufferNonEmpty) { + return ( +
+ + +
+ Buffer is empty. +
+
+
+ ); + } + + return ( +
+ {!props.isCondiment ? ( + + ) : ( + + )} +
+ ); +}; + +const ChemMasterProductionChemical = (props, context) => { + const { act, data } = useBackend(context); + return ( + + + + + + + + + ); +}; + +const ChemMasterProductionCondiment = (props, context) => { + const { act } = useBackend(context); + return ( + + + )}> + {hasOccupant ? ( + + + {occupant.name || "Unknown"} + + + 0 ? 'good' : 'average'}> + + + + + {statNames[occupant.stat][1]} + + + + {' K'} + + + {(damageTypes.map(damageType => ( + + + + + + )))} + + ) : ( + + +
+ No occupant detected. +
+
+ )} + +
act('ejectBeaker')} + disabled={!isBeakerLoaded}> + Eject Beaker + + )}> + + + + + + K + + + + + + + + + + + + +
+
+ ); +}; + +const CryoBeaker = (props, context) => { + const { act, data } = useBackend(context); + const { + isBeakerLoaded, + beakerLabel, + beakerVolume, + } = data; + if (isBeakerLoaded) { + return ( + + {beakerLabel + ? beakerLabel + : ( + + No label + + )} + + {beakerVolume ? ( + Math.round(v) + " units remaining"} + /> + ) : "Beaker is empty"} + + + ); + } else { + return ( + + No beaker loaded + + ); + } +}; diff --git a/tgui/packages/tgui/interfaces/DNAModifier.js b/tgui/packages/tgui/interfaces/DNAModifier.js new file mode 100644 index 00000000000..111aa59c8a6 --- /dev/null +++ b/tgui/packages/tgui/interfaces/DNAModifier.js @@ -0,0 +1,711 @@ +import { Fragment } from 'inferno'; +import { useBackend } from "../backend"; +import { Box, Button, Dimmer, Flex, Icon, Knob, LabeledList, ProgressBar, Section, Tabs } from "../components"; +import { Window } from "../layouts"; +import { ComplexModal } from './common/ComplexModal'; + +const stats = [ + ['good', 'Alive'], + ['average', 'Critical'], + ['bad', 'DEAD'], +]; + +const operations = [ + ['ui', 'Modify U.I.', 'dna'], + ['se', 'Modify S.E.', 'dna'], + ['buffer', 'Transfer Buffers', 'syringe'], + ['rejuvenators', 'Rejuvenators', 'flask'], +]; + +const rejuvenatorsDoses = [5, 10, 20, 30, 50]; + +export const DNAModifier = (props, context) => { + const { act, data } = useBackend(context); + const { + irradiating, + dnaBlockSize, + occupant, + } = data; + context.dnaBlockSize = dnaBlockSize; + context.isDNAInvalid = !occupant.isViableSubject + || !occupant.uniqueIdentity + || !occupant.structuralEnzymes; + let radiatingModal; + if (irradiating) { + radiatingModal = ; + } + return ( + + + {radiatingModal} + + + + + + ); +}; + +const DNAModifierOccupant = (props, context) => { + const { act, data } = useBackend(context); + const { + locked, + hasOccupant, + occupant, + } = data; + return ( +
+ + Door Lock: + +
+ ); +}; + +const DNAModifierMain = (props, context) => { + const { act, data } = useBackend(context); + const { + selectedMenuKey, + hasOccupant, + occupant, + } = data; + if (!hasOccupant) { + return ( +
+ + +
+ No occupant in DNA modifier. +
+
+
+ ); + } else if (context.isDNAInvalid) { + return ( +
+ + +
+ No operation possible on this subject. +
+
+
+ ); + } + let body; + if (selectedMenuKey === "ui") { + body = ( + + + + + ); + } else if (selectedMenuKey === "se") { + body = ( + + + + + ); + } else if (selectedMenuKey === "buffer") { + body = ; + } else if (selectedMenuKey === "rejuvenators") { + body = ; + } + return ( +
+ + {operations.map((op, i) => ( + act('selectMenuKey', { key: op[0] })}> + + {op[1]} + + ))} + + {body} +
+ ); +}; + +const DNAModifierMainUI = (props, context) => { + const { act, data } = useBackend(context); + const { + selectedUIBlock, + selectedUISubBlock, + selectedUITarget, + occupant, + } = data; + return ( +
+ + + + value.toString(16).toUpperCase()} + ml="0" + onChange={(e, val) => act('changeUITarget', { value: val })} + /> + + +
+ ); +}; + +const DNAModifierMainSE = (props, context) => { + const { act, data } = useBackend(context); + const { + selectedSEBlock, + selectedSESubBlock, + occupant, + } = data; + return ( +
+ +
+ ); +}; + +const DNAModifierMainRadiationEmitter = (props, context) => { + const { act, data } = useBackend(context); + const { + radiationIntensity, + radiationDuration, + } = data; + return ( +
+ + + act('radiationIntensity', { value: val })} + /> + + + act('radiationDuration', { value: val })} + /> + + +
+ ); +}; + +const DNAModifierMainBuffers = (props, context) => { + const { act, data } = useBackend(context); + const { + buffers, + } = data; + let bufferElements = buffers.map((buffer, i) => ( + + )); + return ( + +
+ {bufferElements} +
+ +
+ ); +}; + +const DNAModifierMainBuffersElement = (props, context) => { + const { act, data } = useBackend(context); + const { + id, + name, + buffer, + } = props; + const isInjectorReady = data.isInjectorReady; + const realName = name + (buffer.data ? ' - ' + buffer.label : ''); + return ( + +
+ act('bufferOption', { + option: 'clear', + id: id, + })} + /> +
+
+ ); +}; + +const DNAModifierMainBuffersDisk = (props, context) => { + const { act, data } = useBackend(context); + const { + hasDisk, + disk, + } = data; + return ( +
+ act('wipeDisk')} + /> +
+ ); +}; + +const DNAModifierMainRejuvenators = (props, context) => { + const { act, data } = useBackend(context); + const { + isBeakerLoaded, + beakerVolume, + beakerLabel, + } = data; + return ( +
act('ejectBeaker')} + /> + }> + {isBeakerLoaded ? ( + + + {rejuvenatorsDoses.map((a, i) => ( +
+ ); +}; + +const DNAModifierIrradiating = (props, context) => { + return ( + +
+ +

+ +  Irradiating occupant  + +

+
+ +

+ For {props.duration} second{props.duration === 1 ? "" : "s"} +

+
+
+ ); +}; + +const DNAModifierBlocks = (props, context) => { + const { act, data } = useBackend(context); + const { + dnaString, + selectedBlock, + selectedSubblock, + blockSize, + action, + } = props; + + const characters = dnaString.split(''); + let curBlock = 0; + let dnaBlocks = []; + for (let block = 0; block < characters.length; block += blockSize) { + const realBlock = block / blockSize + 1; + let subBlocks = []; + for (let subblock = 0; subblock < blockSize; subblock++) { + const realSubblock = subblock + 1; + subBlocks.push( + + + ))} + + ); + } else if (type === "boolean") { + modalFooter = ( + +