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%;