diff --git a/aurorastation.dme b/aurorastation.dme
index 7228a4c8069..116adf98b06 100644
--- a/aurorastation.dme
+++ b/aurorastation.dme
@@ -839,6 +839,7 @@
#include "code\game\objects\items\devices\powersink.dm"
#include "code\game\objects\items\devices\radio_jammer.dm"
#include "code\game\objects\items\devices\scanners.dm"
+#include "code\game\objects\items\devices\slide_projector.dm"
#include "code\game\objects\items\devices\spy_bug.dm"
#include "code\game\objects\items\devices\suit_cooling.dm"
#include "code\game\objects\items\devices\t_scanner.dm"
diff --git a/code/defines/obj.dm b/code/defines/obj.dm
index 4b417bd00b2..4163c8b2ef9 100644
--- a/code/defines/obj.dm
+++ b/code/defines/obj.dm
@@ -41,11 +41,6 @@
return copytext(rank, 2+length(prefix))
return rank
-/obj/effect/projection
- name = "Projection"
- desc = "This looks like a projection of something."
- anchored = 1.0
-
/obj/structure/showcase
name = "Showcase"
icon = 'icons/obj/stationobjs.dmi'
diff --git a/code/game/objects/items/devices/slide_projector.dm b/code/game/objects/items/devices/slide_projector.dm
new file mode 100644
index 00000000000..e046d43af88
--- /dev/null
+++ b/code/game/objects/items/devices/slide_projector.dm
@@ -0,0 +1,205 @@
+/obj/item/storage/slide_projector
+ name = "slide projector"
+ desc = "A handy device capable of showing an enlarged projection of whatever you can fit inside."
+ desc_info = "You can use this in hand to open the interface, click-dragging it to you also works. Click anywhere with it in your hand to project at that location. Click dragging it to that location also works."
+ icon = 'icons/obj/projector.dmi'
+ icon_state = "projector0"
+ max_w_class = ITEMSIZE_SMALL
+ max_storage_space = 10
+ use_sound = 'sound/items/storage/toolbox.ogg'
+ var/static/list/projection_types = list(
+ /obj/item/photo = /obj/effect/projection/photo,
+ /obj/item/paper = /obj/effect/projection/paper,
+ /obj/item = /obj/effect/projection
+ )
+ var/obj/item/current_slide
+ var/obj/effect/projection/projection
+
+/obj/item/storage/slide_projector/Destroy()
+ QDEL_NULL(current_slide)
+ QDEL_NULL(projection)
+ return ..()
+
+/obj/item/storage/slide_projector/update_icon()
+ icon_state = "projector[!!projection]"
+
+/obj/item/storage/slide_projector/remove_from_storage(obj/item/W)
+ . = ..()
+ if(. && W == current_slide)
+ set_slide(length(contents) ? contents[1] : null)
+
+/obj/item/storage/slide_projector/handle_item_insertion(var/obj/item/W)
+ . = ..()
+ if(. && !current_slide)
+ set_slide(W)
+
+/obj/item/storage/slide_projector/afterattack(atom/target, mob/user, proximity_flag, click_parameters)
+ if(!current_slide)
+ to_chat(user, SPAN_WARNING("\The [src] does not have a slide loaded."))
+ return
+ project_at(get_turf(target))
+
+/obj/item/storage/slide_projector/MouseDrop(atom/over)
+ if(use_check_and_message(usr))
+ return
+
+ if(over == usr)
+ interact(usr)
+ return
+
+ var/turf/T = get_turf(over)
+ if(istype(T))
+ afterattack(over, usr)
+
+/obj/item/storage/slide_projector/proc/set_slide(obj/item/new_slide)
+ current_slide = new_slide
+ playsound(loc, 'sound/machines/slide_change.ogg', 50)
+ if(projection)
+ project_at(get_turf(projection))
+
+/obj/item/storage/slide_projector/proc/check_projections()
+ if(!projection)
+ return
+ if(!(projection in view(7,get_turf(src))))
+ stop_projecting()
+
+/obj/item/storage/slide_projector/proc/stop_projecting()
+ if(projection)
+ QDEL_NULL(projection)
+ moved_event.unregister(src, src, .proc/check_projections)
+ set_light(0)
+ update_icon()
+
+/obj/item/storage/slide_projector/proc/project_at(turf/target)
+ stop_projecting()
+ if(!current_slide)
+ return
+ var/projection_type
+ for(var/T in projection_types)
+ if(istype(current_slide, T))
+ projection_type = projection_types[T]
+ break
+ projection = new projection_type(target)
+ projection.set_source(current_slide)
+ moved_event.register(src, src, .proc/check_projections)
+ set_light(1.4, 0.1, COLOR_WHITE) //Bit of light
+ update_icon()
+
+/obj/item/storage/slide_projector/attack_self(mob/user)
+ interact(user)
+
+/obj/item/storage/slide_projector/interact(mob/user)
+ var/data = list()
+ if(projection)
+ data += "Disable Projector"
+ else
+ data += "Projector Inactive"
+
+ var/table = list("
| # | SLIDE | SHOW |
")
+ var/i = 1
+ for(var/obj/item/I in contents)
+ table += "| #[i] | "
+ if(I == current_slide)
+ table += "[I.name] | SHOWING | "
+ else
+ table += "[I.name] | SHOW | "
+ table += "
"
+ i++
+ table += "
"
+ data += jointext(table,null)
+
+ var/datum/browser/popup = new(user, "slides\ref[src]", "Slide Projector")
+ popup.set_content(jointext(data, "
"))
+ popup.open()
+
+/obj/item/storage/slide_projector/Topic(href, href_list)
+ . = ..()
+ if(.)
+ return
+
+ if(href_list["stop_projector"])
+ if(!projection)
+ return TRUE
+ stop_projecting()
+ . = FALSE
+
+ if(href_list["set_active"])
+ var/index = text2num(href_list["set_active"])
+ if(index < 1 || index > contents.len)
+ return TRUE
+ set_slide(contents[index])
+ . = FALSE
+
+ interact(usr)
+
+/obj/effect/projection
+ name = "projected slide"
+ icon = 'icons/effects/effects.dmi'
+ icon_state = "white"
+ anchored = TRUE
+ simulated = FALSE
+ layer = EFFECTS_ABOVE_LIGHTING_LAYER
+ alpha = 100
+ var/datum/weakref/source
+
+/obj/effect/projection/Initialize()
+ . = ..()
+ set_light(1.4, 0.1, COLOR_WHITE) //Makes turning off the lights not invalidate projection
+
+/obj/effect/projection/update_icon()
+ filters = filter(type="drop_shadow", color = COLOR_WHITE, size = 4, offset = 1,x = 0, y = 0)
+ project_icon()
+
+/obj/effect/projection/proc/project_icon()
+ var/obj/item/I = source.resolve()
+ if(!istype(I))
+ qdel(src)
+ return
+ cut_overlays()
+ var/mutable_appearance/MA = new(I)
+ MA.layer = EFFECTS_ABOVE_LIGHTING_LAYER
+ MA.appearance_flags = RESET_ALPHA
+ MA.alpha = 170
+ MA.pixel_x = 0
+ MA.pixel_y = 0
+ overlays |= MA
+
+/obj/effect/projection/proc/set_source(obj/item/I)
+ source = WEAKREF(I)
+ desc = "It's currently showing \the [I]."
+ update_icon()
+
+/obj/effect/projection/examine(mob/user, distance)
+ . = ..()
+ var/obj/item/slide = source.resolve()
+ if(!istype(slide))
+ qdel(src)
+ return
+ return slide.examine(user, 1)
+
+/obj/effect/projection/photo
+ alpha = 170
+
+/obj/effect/projection/photo/project_icon()
+ var/obj/item/photo/slide = source.resolve()
+ if(!istype(slide))
+ qdel(src)
+ return
+ icon = slide.img
+ transform = matrix()
+ transform *= 1 / slide.photo_size
+ pixel_x = -32 * round(slide.photo_size/2)
+ pixel_y = -32 * round(slide.photo_size/2)
+
+/obj/effect/projection/paper
+ alpha = 140
+ blend_mode = BLEND_ADD
+
+/obj/effect/projection/paper/project_icon()
+ var/obj/item/paper/P = source.resolve()
+ if(!istype(P))
+ qdel(src)
+ return
+ cut_overlays()
+ if(P.info)
+ icon_state = "text[rand(1,3)]"
\ No newline at end of file
diff --git a/html/changelogs/geeves-slide_projector.yml b/html/changelogs/geeves-slide_projector.yml
new file mode 100644
index 00000000000..9c07b28005d
--- /dev/null
+++ b/html/changelogs/geeves-slide_projector.yml
@@ -0,0 +1,6 @@
+author: Geeves
+
+delete-after: True
+
+changes:
+ - rscadd: "Added slide projectors, capable of projecting an image of any small item onto any turf."
\ No newline at end of file
diff --git a/icons/effects/effects.dmi b/icons/effects/effects.dmi
index 4b0657a6419..939711f7bef 100644
Binary files a/icons/effects/effects.dmi and b/icons/effects/effects.dmi differ
diff --git a/icons/obj/projector.dmi b/icons/obj/projector.dmi
new file mode 100644
index 00000000000..6e4012c09e7
Binary files /dev/null and b/icons/obj/projector.dmi differ
diff --git a/maps/aurora/aurora-4_mainlevel.dmm b/maps/aurora/aurora-4_mainlevel.dmm
index 6ea4d8e56af..d2a206d3729 100644
--- a/maps/aurora/aurora-4_mainlevel.dmm
+++ b/maps/aurora/aurora-4_mainlevel.dmm
@@ -5113,6 +5113,7 @@
pixel_y = 9
},
/obj/item/pen,
+/obj/item/storage/slide_projector,
/turf/simulated/floor/tiled/white,
/area/crew_quarters/sleep/research)
"aki" = (
@@ -23566,6 +23567,9 @@
"aOS" = (
/obj/structure/table/wood,
/obj/item/modular_computer/laptop/preset/security/hos,
+/obj/item/storage/slide_projector{
+ pixel_y = 12
+ },
/turf/simulated/floor/carpet,
/area/crew_quarters/heads/hos)
"aOT" = (
@@ -24277,6 +24281,7 @@
/obj/machinery/light{
dir = 4
},
+/obj/item/storage/slide_projector,
/turf/simulated/floor/carpet,
/area/crew_quarters/captain)
"aPZ" = (
@@ -25430,6 +25435,9 @@
},
/obj/item/folder/yellow,
/obj/item/stamp/ce,
+/obj/item/storage/slide_projector{
+ pixel_y = 12
+ },
/turf/simulated/floor/carpet,
/area/crew_quarters/heads/chief)
"aRV" = (
@@ -26997,6 +27005,9 @@
/obj/effect/floor_decal/spline/fancy/wood{
dir = 4
},
+/obj/item/storage/slide_projector{
+ pixel_y = 16
+ },
/turf/simulated/floor/carpet,
/area/bridge/meeting_room)
"aUD" = (
@@ -28477,6 +28488,9 @@
},
/obj/item/toy/figure/corgi,
/obj/item/device/megaphone/command,
+/obj/item/storage/slide_projector{
+ pixel_y = 12
+ },
/turf/simulated/floor/wood,
/area/crew_quarters/heads/hop)
"aXq" = (
@@ -35419,6 +35433,7 @@
/obj/effect/floor_decal/corner_wide/paleblue/full{
dir = 8
},
+/obj/item/storage/slide_projector,
/turf/simulated/floor/tiled/white,
/area/crew_quarters/heads/cmo)
"bjo" = (
@@ -44382,6 +44397,9 @@
"bBz" = (
/obj/structure/table/standard,
/obj/item/modular_computer/laptop/preset/research/rd,
+/obj/item/storage/slide_projector{
+ pixel_y = 12
+ },
/turf/simulated/floor/carpet/blue,
/area/rnd/rdoffice)
"bBB" = (
@@ -68370,6 +68388,7 @@
dir = 6
},
/obj/item/book/manual/wiki/security_space_law,
+/obj/item/storage/slide_projector,
/turf/simulated/floor/tiled/dark,
/area/security/forensics_office{
name = "Security - Investigations Office"
@@ -69962,6 +69981,11 @@
/obj/effect/decal/cleanable/liquid_fuel,
/turf/simulated/floor/carpet/rubber,
/area/maintenance/workshop)
+"whV" = (
+/obj/structure/table/wood/gamblingtable,
+/obj/item/storage/slide_projector,
+/turf/simulated/floor/lino/grey,
+/area/engineering/foyer)
"wil" = (
/obj/effect/floor_decal/corner/grey/diagonal,
/obj/structure/closet/secure_closet/freezer/kitchen,
@@ -91097,7 +91121,7 @@ aAF
aCc
aDU
aFL
-oKJ
+whV
oKJ
aLc
aMS
diff --git a/maps/aurora/aurora-5_interstitial.dmm b/maps/aurora/aurora-5_interstitial.dmm
index 1c91c44b0b2..6dc82f2cad6 100644
--- a/maps/aurora/aurora-5_interstitial.dmm
+++ b/maps/aurora/aurora-5_interstitial.dmm
@@ -2432,7 +2432,7 @@
/turf/simulated/floor/plating,
/area/security/training_theoretical)
"fA" = (
-/obj/structure/banner,
+/obj/structure/banner/scc,
/turf/simulated/floor/wood,
/area/security/training_theoretical)
"fB" = (
@@ -2442,16 +2442,7 @@
},
/turf/simulated/floor/wood,
/area/security/training_theoretical)
-"fC" = (
-/obj/structure/sign/double/map/left{
- pixel_y = 32
- },
-/turf/simulated/floor/wood,
-/area/security/training_theoretical)
"fD" = (
-/obj/structure/sign/double/map/right{
- pixel_y = 32
- },
/obj/effect/landmark/start{
name = "Warden"
},
@@ -2473,8 +2464,7 @@
/area/security/training_theoretical)
"fF" = (
/obj/structure/bed/chair/comfy/sofa/left{
- dir = 4;
- icon_state = "sofaend_left_preview"
+ dir = 4
},
/turf/simulated/floor/wood,
/area/security/break_room)
@@ -2515,6 +2505,9 @@
"fX" = (
/obj/structure/table/wood,
/obj/item/folder/sec,
+/obj/item/storage/slide_projector{
+ pixel_x = -16
+ },
/turf/simulated/floor/wood,
/area/security/training_theoretical)
"fY" = (
@@ -2542,22 +2535,19 @@
/area/security/training_theoretical)
"gb" = (
/obj/structure/bed/chair/comfy/sofa/corner{
- dir = 4;
- icon_state = "sofacorner_preview"
+ dir = 4
},
/turf/simulated/floor/wood,
/area/security/break_room)
"gc" = (
/obj/structure/bed/chair/comfy/sofa{
- dir = 1;
- icon_state = "sofamiddle_preview"
+ dir = 1
},
/turf/simulated/floor/wood,
/area/security/break_room)
"gd" = (
/obj/structure/bed/chair/comfy/sofa/left{
- dir = 1;
- icon_state = "sofaend_left_preview"
+ dir = 1
},
/obj/machinery/light,
/turf/simulated/floor/wood,
@@ -44847,7 +44837,7 @@ eJ
eW
fh
rp
-fC
+fZ
fY
gq
gH
diff --git a/sound/machines/slide_change.ogg b/sound/machines/slide_change.ogg
new file mode 100644
index 00000000000..129af6db727
Binary files /dev/null and b/sound/machines/slide_change.ogg differ