diff --git a/code/__DEFINES/say.dm b/code/__DEFINES/say.dm index 283202b99a0..6d9cf80b2e0 100644 --- a/code/__DEFINES/say.dm +++ b/code/__DEFINES/say.dm @@ -61,6 +61,7 @@ #define SPAN_COMMAND "command_headset" #define SPAN_CLOWN "clown" #define SPAN_SINGING "singing" +#define SPAN_TAPE_RECORDER "tape_recorder" //bitflag #defines for return value of the radio() proc. #define ITALICS (1<<0) diff --git a/code/datums/looping_sounds/item_sounds.dm b/code/datums/looping_sounds/item_sounds.dm index 3826c69e905..063e4bac409 100644 --- a/code/datums/looping_sounds/item_sounds.dm +++ b/code/datums/looping_sounds/item_sounds.dm @@ -51,3 +51,8 @@ mid_sounds = list('sound/items/weeoo1.ogg') mid_length = 15 volume = 20 + +/datum/looping_sound/tape_recorder_hiss + mid_sounds = list('sound/items/taperecorder/taperecorder_hiss_mid.ogg') + start_sound = list('sound/items/taperecorder/taperecorder_hiss_start.ogg') + volume = 10 diff --git a/code/game/objects/items/devices/taperecorder.dm b/code/game/objects/items/devices/taperecorder.dm index 2d51be7e31e..98857edaca1 100644 --- a/code/game/objects/items/devices/taperecorder.dm +++ b/code/game/objects/items/devices/taperecorder.dm @@ -12,7 +12,10 @@ slot_flags = ITEM_SLOT_BELT custom_materials = list(/datum/material/iron=60, /datum/material/glass=30) force = 2 - throwforce = 0 + throwforce = 2 + speech_span = SPAN_TAPE_RECORDER + drop_sound = 'sound/items/handling/taperecorder_drop.ogg' + pickup_sound = 'sound/items/handling/taperecorder_pickup.ogg' var/recording = FALSE var/playing = FALSE var/playsleepseconds = 0 @@ -21,19 +24,37 @@ var/open_panel = FALSE var/canprint = TRUE var/list/icons_available = list() - var/icon_directory = 'icons/effects/icons.dmi' - + var/radial_icon_file = 'icons/hud/radial_taperecorder.dmi' + ///Whether we've warned during this recording session that the tape is almost up. + var/time_warned = FALSE + ///Seconds under which to warn that the tape is almost up. + var/time_left_warning = 60 SECONDS + ///Sound loop that plays when recording or playing back. + var/datum/looping_sound/tape_recorder_hiss/soundloop /obj/item/taperecorder/Initialize(mapload) . = ..() if(starting_tape_type) mytape = new starting_tape_type(src) + soundloop = new(list(src)) update_icon() +/obj/item/taperecorder/proc/readout() + if(mytape) + if(playing) + return "PLAYING" + else + var/time = mytape.used_capacity / 10 //deciseconds / 10 = seconds + var/mins = round(time / 60) + var/secs = time - mins * 60 + return "[mins]m [secs]s" + return "NO TAPE INSERTED" /obj/item/taperecorder/examine(mob/user) . = ..() - . += "The wire panel is [open_panel ? "opened" : "closed"]." + if(in_range(src, user) || isobserver(user)) + . += "The wire panel is [open_panel ? "opened" : "closed"]. The display reads:" + . += "[readout()]" /obj/item/taperecorder/AltClick(mob/user) . = ..() @@ -42,22 +63,23 @@ /obj/item/taperecorder/proc/update_available_icons() icons_available = list() - if(recording) - icons_available += list("Stop Recording" = image(icon = icon_directory, icon_state = "record_stop")) - else - if(!playing) - icons_available += list("Record" = image(icon = icon_directory, icon_state = "record")) + if(!playing && !recording) + icons_available += list("Record" = image(radial_icon_file,"record")) + icons_available += list("Play" = image(radial_icon_file,"play")) + if(canprint && mytape?.storedinfo.len) + icons_available += list("Print Transcript" = image(radial_icon_file,"print")) - if(playing) - icons_available += list("Pause" = image(icon = icon_directory, icon_state = "pause")) - else - if(!recording) - icons_available += list("Play" = image(icon = icon_directory, icon_state = "play")) + if(playing || recording) + icons_available += list("Stop" = image(radial_icon_file,"stop")) - if(canprint && !recording && !playing) - icons_available += list("Print Transcript" = image(icon = icon_directory, icon_state = "print")) if(mytape) - icons_available += list("Eject" = image(icon = icon_directory, icon_state = "eject")) + icons_available += list("Eject" = image(radial_icon_file,"eject")) + +/obj/item/taperecorder/proc/update_sound() + if(!playing && !recording) + soundloop.stop() + else + soundloop.start() /obj/item/taperecorder/attackby(obj/item/I, mob/user, params) if(!mytape && istype(I, /obj/item/tape)) @@ -79,7 +101,7 @@ update_icon() /obj/item/taperecorder/fire_act(exposed_temperature, exposed_volume) - mytape.ruin() //Fires destroy the tape + mytape.unspool() //Fires unspool the tape, which makes sense if you don't think about it ..() //ATTACK HAND IGNORING PARENT RETURN VALUE @@ -122,7 +144,8 @@ . = ..() if(mytape && recording) mytape.timestamp += mytape.used_capacity - mytape.storedinfo += "\[[time2text(mytape.used_capacity * 10,"mm:ss")]\] [message]" + mytape.storedinfo += "\[[time2text(mytape.used_capacity,"mm:ss")]\] [message]" + /obj/item/taperecorder/verb/record() set name = "Start Recording" @@ -130,30 +153,35 @@ if(!can_use(usr)) return - if(!mytape || mytape.ruined) + if(!mytape || mytape.unspooled) return if(recording) return if(playing) return + playsound(src, 'sound/items/taperecorder/taperecorder_play.ogg', 50, FALSE) + if(mytape.used_capacity < mytape.max_capacity) - to_chat(usr, "Recording started.") - playsound(src, 'sound/items/taperecorder/taperecorder_play.ogg', 50, FALSE) - recording = 1 + recording = TRUE + say("Recording started.") + update_sound() update_icon() - mytape.timestamp += mytape.used_capacity - mytape.storedinfo += "\[[time2text(mytape.used_capacity * 10,"mm:ss")]\] Recording started." var/used = mytape.used_capacity //to stop runtimes when you eject the tape var/max = mytape.max_capacity while(recording && used < max) - mytape.used_capacity++ - used++ - sleep(10) - recording = FALSE - update_icon() + mytape.used_capacity += 1 SECONDS + used += 1 SECONDS + if(max - used < time_left_warning && !time_warned) + time_warned = TRUE + say("[(max - used) / 10] seconds left!") //deciseconds / 10 = seconds + sleep(1 SECONDS) + if(used >= max) + say("Tape full.") + stop() else - to_chat(usr, "The tape is full.") + say("The tape is full!") + playsound(src, 'sound/items/taperecorder/taperecorder_stop.ogg', 50, FALSE) /obj/item/taperecorder/verb/stop() @@ -164,19 +192,16 @@ return if(recording) + playsound(src, 'sound/items/taperecorder/taperecorder_stop.ogg', 50, FALSE) + say("Recording stopped.") recording = FALSE - mytape.timestamp += mytape.used_capacity - mytape.storedinfo += "\[[time2text(mytape.used_capacity * 10,"mm:ss")]\] Recording stopped." - playsound(src, 'sound/items/taperecorder/taperecorder_stop.ogg', 50, FALSE) - to_chat(usr, "Recording stopped.") - return else if(playing) - playing = FALSE - var/turf/T = get_turf(src) playsound(src, 'sound/items/taperecorder/taperecorder_stop.ogg', 50, FALSE) - T.visible_message("Tape Recorder: Playback stopped.") + say("Playback stopped.") + playing = FALSE + time_warned = FALSE update_icon() - + update_sound() /obj/item/taperecorder/verb/play() set name = "Play Tape" @@ -184,49 +209,49 @@ if(!can_use(usr)) return - if(!mytape || mytape.ruined) + if(!mytape || mytape.unspooled) return if(recording) return if(playing) return - playing = 1 + playing = TRUE update_icon() - to_chat(usr, "Playing started.") + update_sound() + say("Playback started.") playsound(src, 'sound/items/taperecorder/taperecorder_play.ogg', 50, FALSE) var/used = mytape.used_capacity //to stop runtimes when you eject the tape var/max = mytape.max_capacity - for(var/i = 1, used <= max, sleep(10 * playsleepseconds)) + for(var/i = 1, used <= max, sleep(playsleepseconds)) if(!mytape) break if(playing == FALSE) break if(mytape.storedinfo.len < i) + say("End of recording.") break - say(mytape.storedinfo[i]) + say("[mytape.storedinfo[i]]") if(mytape.storedinfo.len < i + 1) playsleepseconds = 1 - sleep(10) - say("End of recording.") + sleep(1 SECONDS) else playsleepseconds = mytape.timestamp[i + 1] - mytape.timestamp[i] - if(playsleepseconds > 14) - sleep(10) - say("Skipping [playsleepseconds] seconds of silence") - playsleepseconds = 1 + if(playsleepseconds > 14 SECONDS) + sleep(1 SECONDS) + say("Skipping [playsleepseconds] seconds of silence.") + playsleepseconds = 1 SECONDS i++ - playing = FALSE - update_icon() + stop() /obj/item/taperecorder/attack_self(mob/user) if(!mytape) - to_chat(user, "The [src] does not have a tape inside.") + to_chat(user, "\The [src] is empty.") return - if(mytape.ruined) - to_chat(user, "The tape inside the [src] appears to be broken.") + if(mytape.unspooled) + to_chat(user, "\The tape inside \the [src] is broken!") return update_available_icons() @@ -235,9 +260,7 @@ if(!selection) return switch(selection) - if("Pause") - stop() - if("Stop Recording") // yes we actually need 2 seperate stops for the same proc- Hopek + if("Stop") stop() if("Record") record() @@ -252,24 +275,28 @@ set name = "Print Transcript" set category = "Object" + if(!mytape.storedinfo.len) + return if(!can_use(usr)) return if(!mytape) return if(!canprint) - to_chat(usr, "The recorder can't print that fast!") + to_chat(usr, "The recorder can't print that fast!") return if(recording || playing) return - to_chat(usr, "Transcript printed.") + say("Transcript printed.") playsound(src, 'sound/items/taperecorder/taperecorder_print.ogg', 50, FALSE) var/obj/item/paper/P = new /obj/item/paper(get_turf(src)) var/t1 = "Transcript:

" for(var/i = 1, mytape.storedinfo.len >= i, i++) t1 += "[mytape.storedinfo[i]]
" P.info = t1 - P.name = "paper- 'Transcript'" + var/tapename = mytape.name + var/prototapename = initial(mytape.name) + P.name = "paper- '[tapename == prototapename ? "Tape" : "[tapename]"] Transcript'" P.update_icon_state() usr.put_in_hands(P) canprint = FALSE @@ -283,7 +310,7 @@ /obj/item/tape name = "tape" - desc = "A magnetic tape that can hold up to ten minutes of content." + desc = "A magnetic tape that can hold up to ten minutes of content on either side." icon_state = "tape_white" icon = 'icons/obj/device.dmi' inhand_icon_state = "analyzer" @@ -293,46 +320,114 @@ custom_materials = list(/datum/material/iron=20, /datum/material/glass=5) force = 1 throwforce = 0 - var/max_capacity = 600 - var/used_capacity = 0 + obj_flags = UNIQUE_RENAME //my mixtape + drop_sound = 'sound/items/handling/tape_drop.ogg' + pickup_sound = 'sound/items/handling/tape_pickup.ogg' + ///Because we can't expect God to do all the work. + var/initial_icon_state + var/max_capacity = 10 MINUTES + var/used_capacity = 0 SECONDS + ///Numbered list of chat messages the recorder has heard with spans and prepended timestamps. Used for playback and transcription. var/list/storedinfo = list() + ///Numbered list of seconds the messages in the previous list appear at on the tape. Used by playback to get the timing right. var/list/timestamp = list() - var/ruined = FALSE + var/used_capacity_otherside = 0 SECONDS //Separate my side + var/list/storedinfo_otherside = list() + var/list/timestamp_otherside = list() + var/unspooled = FALSE + var/list/icons_available = list() + var/radial_icon_file = 'icons/hud/radial_tape.dmi' /obj/item/tape/fire_act(exposed_temperature, exposed_volume) - ruin() + unspool() ..() +/obj/item/tape/Initialize() + . = ..() + initial_icon_state = icon_state //random tapes will set this after choosing their icon + + var/mycolor = random_short_color() + name += " ([mycolor])" //multiple tapes can get confusing fast + if(icon_state == "tape_greyscale") + add_atom_colour("#[mycolor]", FIXED_COLOUR_PRIORITY) + + if(prob(50)) + tapeflip() + +/obj/item/tape/proc/update_available_icons() + icons_available = list() + + if(!unspooled) + icons_available += list("Unwind tape" = image(radial_icon_file,"tape_unwind")) + icons_available += list("Flip tape" = image(radial_icon_file,"tape_flip")) + /obj/item/tape/attack_self(mob/user) - if(!ruined) - to_chat(user, "You pull out all the tape!") - ruin() + update_available_icons() + if(icons_available) + var/selection = show_radial_menu(user, src, icons_available, radius = 38, require_near = TRUE, tooltips = TRUE) + if(!selection) + return + switch(selection) + if("Flip tape") + if(loc != user) + return + tapeflip() + to_chat(user, "You turn \the [src] over.") + playsound(src, 'sound/items/taperecorder/tape_flip.ogg', 70, FALSE) + if("Unwind tape") + if(loc != user) + return + unspool() + to_chat(user, "You pull out all the tape!") +/obj/item/tape/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) + if(prob(50)) + tapeflip() + . = ..() -/obj/item/tape/proc/ruin() - //Lets not add infinite amounts of overlays when our fireact is called - //repeatedly - if(!ruined) +/obj/item/tape/proc/unspool() + //Let's not add infinite amounts of overlays when our fire_act is called repeatedly + if(!unspooled) add_overlay("ribbonoverlay") - ruined = TRUE + unspooled = TRUE - -/obj/item/tape/proc/fix() +/obj/item/tape/proc/respool() cut_overlay("ribbonoverlay") - ruined = FALSE + unspooled = FALSE +/obj/item/tape/proc/tapeflip() + //first we save a copy of our current side + var/list/storedinfo_currentside = storedinfo.Copy() + var/list/timestamp_currentside = timestamp.Copy() + var/used_capacity_currentside = used_capacity + //then we overwite our current side with our other side + storedinfo = storedinfo_otherside.Copy() + timestamp = timestamp_otherside.Copy() + used_capacity = used_capacity_otherside + //then we overwrite our other side with the saved side + storedinfo_otherside = storedinfo_currentside.Copy() + timestamp_otherside = timestamp_currentside.Copy() + used_capacity_otherside = used_capacity_currentside + + if(icon_state == initial_icon_state) + icon_state = "[initial_icon_state]_reverse" + else if(icon_state == "[initial_icon_state]_reverse") //so flipping doesn't overwrite an unexpected icon_state (e.g. an admin's) + icon_state = initial_icon_state /obj/item/tape/attackby(obj/item/I, mob/user, params) - if(ruined && (I.tool_behaviour == TOOL_SCREWDRIVER || istype(I, /obj/item/pen))) + if(unspooled && (I.tool_behaviour == TOOL_SCREWDRIVER)) to_chat(user, "You start winding the tape back in...") if(I.use_tool(src, user, 120)) - to_chat(user, "You wound the tape back in.") - fix() + to_chat(user, "You wind the tape back in.") + respool() //Random colour tapes /obj/item/tape/random icon_state = "random_tape" /obj/item/tape/random/Initialize() + icon_state = "tape_[pick("white", "blue", "red", "yellow", "purple", "greyscale")]" . = ..() - icon_state = "tape_[pick("white", "blue", "red", "yellow", "purple")]" + +/obj/item/tape/dyed + icon_state = "greyscale" diff --git a/icons/effects/icons.dmi b/icons/effects/icons.dmi deleted file mode 100644 index 0d62217efbf..00000000000 Binary files a/icons/effects/icons.dmi and /dev/null differ diff --git a/icons/hud/radial_tape.dmi b/icons/hud/radial_tape.dmi new file mode 100644 index 00000000000..ae5be96dd8a Binary files /dev/null and b/icons/hud/radial_tape.dmi differ diff --git a/icons/hud/radial_taperecorder.dmi b/icons/hud/radial_taperecorder.dmi new file mode 100644 index 00000000000..29d97b0aba0 Binary files /dev/null and b/icons/hud/radial_taperecorder.dmi differ diff --git a/icons/obj/device.dmi b/icons/obj/device.dmi index c43de3c950c..f9b9286c062 100644 Binary files a/icons/obj/device.dmi and b/icons/obj/device.dmi differ diff --git a/sound/items/handling/tape_drop.ogg b/sound/items/handling/tape_drop.ogg new file mode 100644 index 00000000000..5379a114a77 Binary files /dev/null and b/sound/items/handling/tape_drop.ogg differ diff --git a/sound/items/handling/tape_pickup.ogg b/sound/items/handling/tape_pickup.ogg new file mode 100644 index 00000000000..77f74f19eea Binary files /dev/null and b/sound/items/handling/tape_pickup.ogg differ diff --git a/sound/items/handling/taperecorder_drop.ogg b/sound/items/handling/taperecorder_drop.ogg new file mode 100644 index 00000000000..6e3c151140e Binary files /dev/null and b/sound/items/handling/taperecorder_drop.ogg differ diff --git a/sound/items/handling/taperecorder_pickup.ogg b/sound/items/handling/taperecorder_pickup.ogg new file mode 100644 index 00000000000..941640aefd1 Binary files /dev/null and b/sound/items/handling/taperecorder_pickup.ogg differ diff --git a/sound/items/taperecorder/tape_flip.ogg b/sound/items/taperecorder/tape_flip.ogg new file mode 100644 index 00000000000..fae3e07373c Binary files /dev/null and b/sound/items/taperecorder/tape_flip.ogg differ diff --git a/sound/items/taperecorder/taperecorder_hiss_mid.ogg b/sound/items/taperecorder/taperecorder_hiss_mid.ogg new file mode 100644 index 00000000000..50ef4f2171b Binary files /dev/null and b/sound/items/taperecorder/taperecorder_hiss_mid.ogg differ diff --git a/sound/items/taperecorder/taperecorder_hiss_start.ogg b/sound/items/taperecorder/taperecorder_hiss_start.ogg new file mode 100644 index 00000000000..fa57041a722 Binary files /dev/null and b/sound/items/taperecorder/taperecorder_hiss_start.ogg differ diff --git a/tgui/packages/tgui-panel/styles/goon/chat-dark.scss b/tgui/packages/tgui-panel/styles/goon/chat-dark.scss index 26f8164947f..c57ba432581 100644 --- a/tgui/packages/tgui-panel/styles/goon/chat-dark.scss +++ b/tgui/packages/tgui-panel/styles/goon/chat-dark.scss @@ -666,6 +666,11 @@ em { font-family: "Courier New", cursive, sans-serif; } +.tape_recorder { + color: #FF0000; + font-family: "Courier New", cursive, sans-serif; +} + .command_headset { font-weight: bold; font-size: 160%; diff --git a/tgui/packages/tgui-panel/styles/goon/chat-light.scss b/tgui/packages/tgui-panel/styles/goon/chat-light.scss index c253b31c29e..53b93065aaf 100644 --- a/tgui/packages/tgui-panel/styles/goon/chat-light.scss +++ b/tgui/packages/tgui-panel/styles/goon/chat-light.scss @@ -706,6 +706,11 @@ h1.alert, h2.alert { font-family: "Courier New", cursive, sans-serif; } +.tape_recorder { + color: #800000; + font-family: "Courier New", cursive, sans-serif; +} + .command_headset { font-weight: bold; font-size: 160%;