diff --git a/_maps/map_files/BoxStation/BoxStation.dmm b/_maps/map_files/BoxStation/BoxStation.dmm
index 7590bcc0f2d..d63869db231 100644
--- a/_maps/map_files/BoxStation/BoxStation.dmm
+++ b/_maps/map_files/BoxStation/BoxStation.dmm
@@ -1503,6 +1503,25 @@
/obj/structure/cable,
/turf/open/space/basic,
/area/solar/port/fore)
+"adv" = (
+/obj/structure/chair/office{
+ dir = 8
+ },
+/obj/structure/disposalpipe/segment{
+ dir = 4
+ },
+/obj/effect/turf_decal/tile/blue{
+ dir = 1
+ },
+/obj/effect/turf_decal/tile/blue{
+ dir = 8
+ },
+/obj/machinery/button/ticket_machine{
+ pixel_x = -32;
+ pixel_y = 38
+ },
+/turf/open/floor/plasteel,
+/area/crew_quarters/heads/hop)
"adw" = (
/obj/machinery/atmospherics/pipe/manifold/supply/hidden{
dir = 8
@@ -1691,6 +1710,19 @@
"adR" = (
/turf/closed/wall/r_wall,
/area/security/main)
+"adS" = (
+/obj/machinery/door/poddoor/shutters/preopen{
+ id = "hopqueue";
+ name = "HoP Queue Shutters"
+ },
+/obj/effect/turf_decal/loading_area{
+ dir = 4
+ },
+/obj/machinery/ticket_machine{
+ pixel_y = 32
+ },
+/turf/open/floor/plasteel,
+/area/hallway/primary/central)
"adT" = (
/obj/structure/lattice/catwalk,
/obj/structure/cable,
@@ -26054,21 +26086,6 @@
},
/turf/open/floor/carpet,
/area/crew_quarters/heads/hop)
-"bpc" = (
-/obj/structure/chair/office{
- dir = 8
- },
-/obj/structure/disposalpipe/segment{
- dir = 4
- },
-/obj/effect/turf_decal/tile/blue{
- dir = 1
- },
-/obj/effect/turf_decal/tile/blue{
- dir = 8
- },
-/turf/open/floor/plasteel,
-/area/crew_quarters/heads/hop)
"bpd" = (
/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden,
/turf/open/floor/carpet,
@@ -28628,16 +28645,6 @@
},
/turf/open/floor/plasteel,
/area/hallway/primary/central)
-"bwg" = (
-/obj/machinery/door/poddoor/shutters/preopen{
- id = "hopqueue";
- name = "HoP Queue Shutters"
- },
-/obj/effect/turf_decal/loading_area{
- dir = 4
- },
-/turf/open/floor/plasteel,
-/area/hallway/primary/central)
"bwh" = (
/obj/structure/sign/warning/securearea{
pixel_y = 32
@@ -77408,7 +77415,7 @@ bqv
bqv
bqv
bqv
-bwg
+adS
aJw
aJq
aJq
@@ -78174,7 +78181,7 @@ cNL
bkY
bmo
bnP
-bpc
+adv
bqA
brW
btB
diff --git a/_maps/map_files/Deltastation/DeltaStation2.dmm b/_maps/map_files/Deltastation/DeltaStation2.dmm
index 1456ad917a1..b765ca17a37 100644
--- a/_maps/map_files/Deltastation/DeltaStation2.dmm
+++ b/_maps/map_files/Deltastation/DeltaStation2.dmm
@@ -20,6 +20,20 @@
/obj/structure/cable,
/turf/open/floor/plasteel/airless/solarpanel,
/area/solar/starboard/fore)
+"aaf" = (
+/obj/machinery/door/poddoor/shutters/preopen{
+ id = "hopline";
+ name = "Queue Shutters"
+ },
+/obj/effect/landmark/event_spawn,
+/obj/effect/turf_decal/loading_area{
+ dir = 4
+ },
+/obj/machinery/ticket_machine{
+ pixel_y = 32
+ },
+/turf/open/floor/plasteel,
+/area/hallway/primary/central)
"aag" = (
/obj/machinery/power/solar{
id = "forestarboard";
@@ -28,6 +42,12 @@
/obj/structure/cable,
/turf/open/floor/plasteel/airless/solarpanel,
/area/solar/starboard/fore)
+"aah" = (
+/obj/machinery/button/ticket_machine{
+ pixel_y = 8
+ },
+/turf/closed/wall/r_wall,
+/area/crew_quarters/heads/hop)
"aao" = (
/obj/structure/lattice/catwalk,
/obj/structure/cable,
@@ -60445,17 +60465,6 @@
/obj/machinery/vending/coffee,
/turf/open/floor/wood,
/area/library)
-"chY" = (
-/obj/machinery/door/poddoor/shutters/preopen{
- id = "hopline";
- name = "Queue Shutters"
- },
-/obj/effect/landmark/event_spawn,
-/obj/effect/turf_decal/loading_area{
- dir = 4
- },
-/turf/open/floor/plasteel,
-/area/hallway/primary/central)
"chZ" = (
/obj/machinery/computer/security/mining{
dir = 4
@@ -151620,7 +151629,7 @@ buq
buq
cen
buq
-chY
+aaf
bur
bUB
bUB
@@ -152384,7 +152393,7 @@ bMp
bOy
bQz
bSz
-bUB
+aah
bWQ
bYZ
caN
diff --git a/_maps/map_files/MetaStation/MetaStation.dmm b/_maps/map_files/MetaStation/MetaStation.dmm
index 08f4c88566b..b794554227b 100644
--- a/_maps/map_files/MetaStation/MetaStation.dmm
+++ b/_maps/map_files/MetaStation/MetaStation.dmm
@@ -57,6 +57,36 @@
/obj/structure/cable,
/turf/open/floor/plasteel/airless/solarpanel,
/area/solar/port/fore)
+"aam" = (
+/obj/structure/table/wood,
+/obj/item/paper_bin{
+ pixel_x = -2;
+ pixel_y = 4
+ },
+/obj/item/stamp/hop{
+ pixel_x = -4;
+ pixel_y = 4
+ },
+/obj/machinery/button/ticket_machine{
+ pixel_x = 32
+ },
+/turf/open/floor/wood,
+/area/crew_quarters/heads/hop)
+"aan" = (
+/obj/machinery/atmospherics/pipe/simple/supply/hidden{
+ dir = 4
+ },
+/obj/structure/disposalpipe/segment{
+ dir = 4
+ },
+/obj/effect/turf_decal/tile/neutral{
+ dir = 1
+ },
+/obj/machinery/ticket_machine{
+ pixel_y = 32
+ },
+/turf/open/floor/plasteel,
+/area/hallway/secondary/command)
"aaq" = (
/obj/structure/lattice/catwalk,
/turf/open/space,
@@ -34049,18 +34079,6 @@
/obj/structure/cable,
/turf/open/floor/wood,
/area/crew_quarters/heads/hop)
-"bwp" = (
-/obj/structure/table/wood,
-/obj/item/paper_bin{
- pixel_x = -2;
- pixel_y = 4
- },
-/obj/item/stamp/hop{
- pixel_x = -4;
- pixel_y = 4
- },
-/turf/open/floor/wood,
-/area/crew_quarters/heads/hop)
"bwq" = (
/obj/machinery/atmospherics/pipe/simple/supply/hidden,
/obj/effect/turf_decal/tile/blue,
@@ -99014,7 +99032,7 @@ bwk
bkz
bzM
bBz
-bDa
+aan
bEE
bGA
bIa
@@ -100295,7 +100313,7 @@ bkz
bqG
dbe
but
-bwp
+aam
bkz
bzQ
bzR
diff --git a/_maps/map_files/PubbyStation/PubbyStation.dmm b/_maps/map_files/PubbyStation/PubbyStation.dmm
index d1ba37ef84c..fedc8378a76 100644
--- a/_maps/map_files/PubbyStation/PubbyStation.dmm
+++ b/_maps/map_files/PubbyStation/PubbyStation.dmm
@@ -33,6 +33,36 @@
/obj/structure/closet/secure_closet/atmospherics,
/turf/open/floor/plasteel/dark,
/area/maintenance/disposal/incinerator)
+"aaf" = (
+/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden{
+ dir = 4
+ },
+/obj/effect/turf_decal/tile/blue{
+ dir = 1
+ },
+/obj/machinery/ticket_machine{
+ pixel_y = 32
+ },
+/turf/open/floor/plasteel,
+/area/hallway/primary/central)
+"aag" = (
+/obj/structure/table/wood,
+/obj/item/stamp/hop{
+ pixel_x = -4;
+ pixel_y = 4
+ },
+/obj/item/radio/intercom{
+ pixel_x = 27
+ },
+/obj/item/paper_bin{
+ layer = 2.9
+ },
+/obj/machinery/button/ticket_machine{
+ pixel_x = 28;
+ pixel_y = -12
+ },
+/turf/open/floor/carpet,
+/area/crew_quarters/heads/hop)
"abf" = (
/obj/structure/bed,
/turf/open/floor/plating,
@@ -11831,20 +11861,6 @@
},
/turf/open/floor/carpet,
/area/crew_quarters/heads/hop)
-"aER" = (
-/obj/structure/table/wood,
-/obj/item/stamp/hop{
- pixel_x = -4;
- pixel_y = 4
- },
-/obj/item/radio/intercom{
- pixel_x = 27
- },
-/obj/item/paper_bin{
- layer = 2.9
- },
-/turf/open/floor/carpet,
-/area/crew_quarters/heads/hop)
"aES" = (
/obj/machinery/camera{
c_tag = "Central Primary Hallway Vault";
@@ -90757,7 +90773,7 @@ aEM
aFA
aGv
aKV
-aHV
+aaf
aDZ
aJO
aKZ
@@ -92038,7 +92054,7 @@ aAH
aBG
aCU
aDY
-aER
+aag
aAH
aGy
aAN
diff --git a/code/modules/paperwork/ticketmachine.dm b/code/modules/paperwork/ticketmachine.dm
new file mode 100644
index 00000000000..e8694a0ab02
--- /dev/null
+++ b/code/modules/paperwork/ticketmachine.dm
@@ -0,0 +1,202 @@
+//Bureaucracy machine!
+//Simply set this up in the hopline and you can serve people based on ticket numbers
+
+/obj/machinery/ticket_machine
+ name = "ticket machine"
+ icon = 'icons/obj/bureaucracy.dmi'
+ icon_state = "ticketmachine"
+ desc = "A marvel of bureaucratic engineering encased in an efficient plastic shell. It can be refilled with a hand labeler refill roll and linked to buttons with a multitool."
+ density = FALSE
+ maptext_height = 26
+ maptext_width = 32
+ maptext_x = 7
+ maptext_y = 10
+ layer = HIGH_OBJ_LAYER
+ var/ticket_number = 0 //Increment the ticket number whenever the HOP presses his button
+ var/current_number = 0 //What customer are we currently serving?
+ var/max_number = 999 //To stop the text going fucky. At this point, you need to refill it.
+ var/cooldown = 50 //Small cooldown, stops the clown from immediately breaking it.
+ var/ready = TRUE
+ var/id = "ticket_machine_default" //For buttons
+
+/obj/machinery/ticket_machine/multitool_act(mob/living/user, obj/item/I)
+ if(!multitool_check_buffer(user, I)) //make sure it has a data buffer
+ return
+ var/obj/item/multitool/M = I
+ M.buffer = src
+ to_chat(user, "You store linkage information in [I]'s buffer.")
+ return TRUE
+
+/obj/machinery/ticket_machine/emag_act(mob/user) //Emag the ticket machine to dispense burning tickets, as well as randomize its customer number to destroy the HOP's mind.
+ if(obj_flags & EMAGGED)
+ return
+ to_chat(user, "You overload [src]'s bureaucratic logic circuitry to its MAXIMUM setting.")
+ ticket_number = rand(0,999)
+ obj_flags |= EMAGGED
+
+/obj/machinery/ticket_machine/Initialize()
+ . = ..()
+ update_icon()
+
+/obj/machinery/ticket_machine/proc/increment()
+ if(current_number >= ticket_number)
+ return
+ playsound(src, 'sound/misc/announce_dig.ogg', 50, 0)
+ say("Next customer, please!")
+ current_number ++ //Increment the one we're serving.
+ update_icon() //Update our icon here rather than when they take a ticket to show the current ticket number being served
+
+/obj/machinery/button/ticket_machine
+ name = "increment ticket counter"
+ desc = "Use this button when you've served a customer to tell the next one to come forward."
+ device_type = /obj/item/assembly/control/ticket_machine
+ req_access = list()
+ id = "ticket_machine_default"
+
+/obj/machinery/button/ticket_machine/Initialize()
+ . = ..()
+ if(device)
+ var/obj/item/assembly/control/ticket_machine/ours = device
+ ours.id = id
+
+/obj/machinery/button/ticket_machine/multitool_act(mob/living/user, obj/item/I)
+ if(I.tool_behaviour == TOOL_MULTITOOL)
+ var/obj/item/multitool/M = I
+ if(M.buffer && !istype(M.buffer, /obj/machinery/ticket_machine))
+ return
+ var/obj/item/assembly/control/ticket_machine/controller = device
+ controller.linked = M.buffer
+ id = null
+ controller.id = null
+ to_chat(user, "You've linked [src] to [controller.linked].")
+
+/obj/item/assembly/control/ticket_machine
+ name = "ticket machine controller"
+ desc = "A remote controller for the HOP's ticket machine."
+ var/obj/machinery/ticket_machine/linked //To whom are we linked?
+
+/obj/item/assembly/control/ticket_machine/Initialize()
+ ..()
+ return INITIALIZE_HINT_LATELOAD
+
+/obj/item/assembly/control/ticket_machine/LateInitialize()
+ find_machine()
+
+/obj/item/assembly/control/ticket_machine/proc/find_machine() //Locate the one to which we're linked
+ for(var/obj/machinery/ticket_machine/ticketsplease in GLOB.machines)
+ if(ticketsplease.id == id)
+ linked = ticketsplease
+ if(linked)
+ return TRUE
+ else
+ return FALSE
+
+/obj/item/assembly/control/ticket_machine/activate()
+ if(cooldown)
+ return
+ if(!linked)
+ return
+ cooldown = TRUE
+ linked.increment()
+ addtimer(VARSET_CALLBACK(src, cooldown, FALSE), 10)
+
+/obj/machinery/ticket_machine/update_icon()
+ switch(ticket_number) //Gives you an idea of how many tickets are left
+ if(0 to 200)
+ icon_state = "ticketmachine_100"
+ if(201 to 800)
+ icon_state = "ticketmachine_50"
+ if(801 to 999)
+ icon_state = "ticketmachine_0"
+ handle_maptext()
+
+/obj/machinery/ticket_machine/proc/handle_maptext()
+ switch(ticket_number) //This is here to handle maptext offsets so that the numbers align.
+ if(0 to 9)
+ maptext_x = 13
+ if(10 to 99)
+ maptext_x = 10
+ if(100 to 999)
+ maptext_x = 8
+ maptext = "[current_number]" //Finally, apply the maptext
+
+/obj/machinery/ticket_machine/attackby(obj/item/I, mob/user, params)
+ ..()
+ if(ticket_number >= max_number)
+ to_chat(user, "[src] refuses [I]!, perhaps it's already full?.")
+ return
+ if(istype(I, /obj/item/hand_labeler_refill))
+ to_chat(user, "You start to refill [src]'s ticket holder (doing this will reset its ticket count!).")
+ if(do_after(user, 30, target = src))
+ to_chat(user, "You insert [I] into [src] as it whirrs nondescriptly.")
+ qdel(I)
+ ticket_number = 0
+ current_number = 0
+ max_number = initial(max_number)
+ update_icon()
+ return
+ if(istype(I, /obj/item/ticket_machine_ticket))
+ to_chat(user, "You start to cram [I] into [src]'s recycling bin.")
+ if(do_after(user, 20, target = src)) //Slight delay so they don't accidentally dispose of their ticket and move to the BACK OF THE LINE
+ qdel(I)
+ return
+
+/obj/machinery/ticket_machine/proc/reset_cooldown()
+ ready = TRUE
+
+/obj/machinery/ticket_machine/attack_hand(mob/living/carbon/user)
+ . = ..()
+ if(!ready)
+ to_chat(user,"Temporarily unable to dispense ticket, please be patient!")
+ return
+ if(ticket_number >= max_number)
+ to_chat(user,"Ticket supply depleted, please refill this unit with a hand labeller refill cartridge!")
+ return
+ ready = FALSE
+ playsound(src, 'sound/machines/terminal_insert_disc.ogg', 100, 0)
+ addtimer(CALLBACK(src, .proc/reset_cooldown), cooldown)//Small cooldown to prevent the clown from ripping out every ticket
+ ticket_number ++
+ to_chat(user, "You take a ticket from [src], looks like you're customer #[ticket_number]...")
+ var/obj/item/ticket_machine_ticket/theirticket = new /obj/item/ticket_machine_ticket(get_turf(src))
+ theirticket.name = "Ticket #[ticket_number]"
+ theirticket.maptext = "[ticket_number]"
+ theirticket.saved_maptext = "[ticket_number]"
+ user.put_in_hands(theirticket)
+ if(obj_flags & EMAGGED) //Emag the machine to destroy the HOP's life.
+ theirticket.fire_act()
+ user.dropItemToGround(theirticket)
+ user.adjust_fire_stacks(1)
+ user.IgniteMob()
+ return
+
+/obj/item/ticket_machine_ticket
+ name = "Ticket"
+ desc = "A ticket which shows your place in the line, you can put it back into the ticket machine when youre done with it."
+ icon = 'icons/obj/bureaucracy.dmi'
+ icon_state = "ticket"
+ maptext_x = 7
+ maptext_y = 10
+ w_class = WEIGHT_CLASS_TINY
+ var/saved_maptext = null
+
+/obj/item/ticket_machine_ticket/attack_hand(mob/user)
+ . = ..()
+ maptext = saved_maptext //For some reason, storage code removes all maptext off objs, this stops its number from being wiped off when taken out of storage.
+
+/obj/item/ticket_machine_ticket/attackby(obj/item/P, mob/living/carbon/human/user, params) //Stolen from papercode
+ ..()
+ if(P.is_hot())
+ if(HAS_TRAIT(user, TRAIT_CLUMSY) && prob(10))
+ user.visible_message("[user] accidentally ignites [user.p_them()]self!", \
+ "You miss the paper and accidentally light yourself on fire!")
+ user.dropItemToGround(P)
+ user.adjust_fire_stacks(1)
+ user.IgniteMob()
+ return
+ user.visible_message("[user] lights [src] ablaze with [P]!", "You light [src] on fire!")
+ fire_act()
+
+/obj/item/ticket_machine_ticket/fire_act(exposed_temperature, exposed_volume)
+ ..()
+ if(!(resistance_flags & FIRE_PROOF))
+ icon_state = "ticket_onfire"
\ No newline at end of file
diff --git a/icons/obj/bureaucracy.dmi b/icons/obj/bureaucracy.dmi
index 1169888b344..fb5489f2203 100644
Binary files a/icons/obj/bureaucracy.dmi and b/icons/obj/bureaucracy.dmi differ
diff --git a/tgstation.dme b/tgstation.dme
index 5a9eea338a8..b3abef7db2e 100755
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -2355,6 +2355,7 @@
#include "code\modules\paperwork\pen.dm"
#include "code\modules\paperwork\photocopier.dm"
#include "code\modules\paperwork\stamps.dm"
+#include "code\modules\paperwork\ticketmachine.dm"
#include "code\modules\photography\_pictures.dm"
#include "code\modules\photography\camera\camera.dm"
#include "code\modules\photography\camera\camera_image_capturing.dm"