diff --git a/code/game/machinery/suit_storage_unit.dm b/code/game/machinery/suit_storage_unit.dm
index e032701468e..328f61d6212 100644
--- a/code/game/machinery/suit_storage_unit.dm
+++ b/code/game/machinery/suit_storage_unit.dm
@@ -182,10 +182,7 @@
dump_contents()
update_icon()
-/**
- * Drops all contents of the unit onto the ground.
-*/
-/obj/machinery/suit_storage_unit/proc/dump_contents()
+/obj/machinery/suit_storage_unit/dump_contents()
dropContents()
helmet = null
suit = null
diff --git a/code/game/objects/items/stacks/sheets/sheet_types.dm b/code/game/objects/items/stacks/sheets/sheet_types.dm
index 8aa4067e68c..da24dc47822 100644
--- a/code/game/objects/items/stacks/sheets/sheet_types.dm
+++ b/code/game/objects/items/stacks/sheets/sheet_types.dm
@@ -90,6 +90,7 @@ GLOBAL_LIST_INIT(metal_recipes, list ( \
null, \
new/datum/stack_recipe("iron door", /obj/structure/mineral_door/iron, 20, one_per_turf = TRUE, on_floor = TRUE), \
new/datum/stack_recipe("floodlight frame", /obj/structure/floodlight_frame, 5, one_per_turf = TRUE, on_floor = TRUE), \
+ new/datum/stack_recipe("voting box", /obj/structure/votebox, 15, time = 50)
))
/obj/item/stack/sheet/metal
diff --git a/code/game/objects/objs.dm b/code/game/objects/objs.dm
index 9b734bfb1ad..ebb172a29f1 100644
--- a/code/game/objects/objs.dm
+++ b/code/game/objects/objs.dm
@@ -327,3 +327,8 @@
/obj/proc/plunger_act(obj/item/plunger/P, mob/living/user, reinforced)
return
+
+// Should move all contained objects to it's location.
+/obj/proc/dump_contents()
+ CRASH("Unimplemented.")
+ return
\ No newline at end of file
diff --git a/code/game/objects/structures/crates_lockers/closets.dm b/code/game/objects/structures/crates_lockers/closets.dm
index bfc878f9035..2920d57904d 100644
--- a/code/game/objects/structures/crates_lockers/closets.dm
+++ b/code/game/objects/structures/crates_lockers/closets.dm
@@ -120,7 +120,7 @@
return FALSE
return TRUE
-/obj/structure/closet/proc/dump_contents()
+/obj/structure/closet/dump_contents()
var/atom/L = drop_location()
for(var/atom/movable/AM in src)
AM.forceMove(L)
diff --git a/code/game/objects/structures/votingbox.dm b/code/game/objects/structures/votingbox.dm
new file mode 100644
index 00000000000..4d9fc14d9b6
--- /dev/null
+++ b/code/game/objects/structures/votingbox.dm
@@ -0,0 +1,194 @@
+/obj/structure/votebox
+ name = "voting box"
+ desc = "A automatic voting box."
+
+ icon = 'icons/obj/votebox.dmi'
+ icon_state = "votebox_maint"
+
+ anchored = TRUE
+
+ var/obj/item/card/id/owner //Slapping the box with this ID starts/ends the vote.
+
+ var/voting_active = FALSE //Voting or Maintenance Mode
+ var/id_auth = FALSE //One vote per ID.
+ var/vote_description = ""
+
+ var/list/voted //List of ID's that already voted.
+
+/obj/structure/votebox/attackby(obj/item/I, mob/living/user, params)
+ if(istype(I,/obj/item/card/id))
+ if(!owner)
+ register_owner(I,user)
+ return
+ if(istype(I,/obj/item/paper))
+ if(voting_active)
+ apply_vote(I,user)
+ else
+ to_chat(user,"[src] is in maintenance mode. Voting is not possible at the moment.")
+ return
+ return ..()
+
+/obj/structure/votebox/interact(mob/user)
+ ..()
+ ui_interact(user)
+
+/obj/structure/votebox/ui_interact(mob/user, ui_key, datum/tgui/ui, force_open, datum/tgui/master_ui, datum/ui_state/state)
+ . = ..()
+
+ var/list/dat = list()
+ if(!owner)
+ dat += "
Unregistered. Swipe ID card to register as voting box operator
"
+ dat += "[vote_description]
"
+ if(is_operator(user))
+ dat += "Voting: [voting_active ? "Active" : "Maintenance Mode"]
"
+ dat += "Set Description: Set Description
"
+ dat += "One vote per ID: [id_auth ? "Yes" : "No"]
"
+ dat += "Reset voted ID's: Reset
"
+ dat += "Draw random vote: Raffle
"
+ dat += "Shred votes: Shred
"
+ dat += "Tally votes: Tally
"
+
+ var/datum/browser/popup = new(user, "votebox", "Voting Box", 300, 300)
+ popup.set_content(dat.Join())
+ popup.open()
+
+/obj/structure/votebox/Topic(href, href_list)
+ if(..())
+ return
+
+ var/mob/user = usr
+ if(!is_operator(user))
+ to_chat(user,"Voting box operator authorization required.")
+ return
+
+ if(href_list["act"])
+ switch(href_list["act"])
+ if("toggle_vote")
+ voting_active = !voting_active
+ update_icon()
+ if("toggle_auth")
+ id_auth = !id_auth
+ if("reset_voted")
+ if(voted)
+ voted.Cut()
+ to_chat(user,"You reset the voter buffer. Everyone can vote again.")
+ if("raffle")
+ raffle(user)
+ if("shred")
+ shred(user)
+ if("tally")
+ print_tally(user)
+ if("set_desc")
+ set_description(user)
+ interact(user)
+
+/obj/structure/votebox/proc/register_owner(obj/item/card/id/I,mob/living/user)
+ owner = I
+ to_chat(user,"You register [src] to your ID card.")
+ ui_interact(user)
+
+/obj/structure/votebox/proc/set_description(mob/user)
+ var/new_description = stripped_multiline_input(user,"Enter new description","Vote Description",vote_description)
+ if(new_description)
+ vote_description = new_description
+
+/obj/structure/votebox/proc/is_operator(mob/user)
+ return user?.get_idcard() == owner
+
+/obj/structure/votebox/proc/apply_vote(obj/item/paper/I,mob/living/user)
+ var/obj/item/card/id/voter_card = user.get_idcard()
+ if(id_auth)
+ if(!voter_card)
+ to_chat(user,"[src] requires a valid id card to vote!")
+ return
+ if(voted && (voter_card in voted))
+ to_chat(user,"[src] allows only one vote per person.")
+ return
+ if(user.transferItemToLoc(I,src))
+ if(!voted)
+ voted = list()
+ voted += voter_card
+ to_chat(user,"You cast your vote.")
+
+/obj/structure/votebox/proc/shred(mob/user)
+ for(var/obj/item/paper/P in contents)
+ qdel(P)
+ to_chat(user,"You shred the current votes.")
+
+/obj/structure/votebox/wrench_act(mob/living/user, obj/item/I)
+ . = ..()
+ default_unfasten_wrench(user, I, 40)
+ return TRUE
+
+/obj/structure/votebox/crowbar_act(mob/living/user, obj/item/I)
+ . = ..()
+ if(voting_active)
+ to_chat(user,"You can only retrieve votes if maintenance mode is active.")
+ return FALSE
+ dump_contents()
+ to_chat(user,"You open vote retrieval hatch and dump all the votes.")
+ return TRUE
+
+/obj/structure/votebox/dump_contents()
+ var/atom/droppoint = drop_location()
+ for(var/atom/movable/AM in contents)
+ AM.forceMove(droppoint)
+
+/obj/structure/votebox/deconstruct(disassembled)
+ dump_contents()
+ . = ..()
+
+/obj/structure/votebox/proc/raffle(mob/user)
+ var/list/options = list()
+ for(var/obj/item/paper/P in contents)
+ options += P
+ if(!length(options))
+ to_chat(user,"[src] pops out random vote.")
+
+/obj/structure/votebox/proc/print_tally(mob/user)
+ var/list/results = list()
+ for(var/obj/item/paper/P in contents)
+ var/text = P.info
+ if(!results[text])
+ results[text] = 1
+ else
+ results[text] += 1
+ sortTim(results, cmp=/proc/cmp_numeric_dsc, associative = TRUE)
+
+ var/obj/item/paper/P = new(drop_location())
+ var/list/tally = list()
+ tally += "Voting Results:
"
+ for(var/option in results)
+ tally += "- \"
[option]
\" - [results[option]] Vote[results[option] > 1 ? "s" : ""]. "
+ tally += "
"
+ P.extra_headers = {"
+
+ "}
+ P.info = tally.Join()
+ P.name = "Voting Results"
+ P.update_icon()
+ user.put_in_hands(P)
+ to_chat(user,"[src] prints out the voting tally")
+
+/obj/structure/votebox/update_icon()
+ . = ..()
+ icon_state = "votebox_[voting_active ? "active" : "maint"]"
\ No newline at end of file
diff --git a/code/modules/paperwork/paper.dm b/code/modules/paperwork/paper.dm
index 8394c8ea91b..279e8c46e56 100644
--- a/code/modules/paperwork/paper.dm
+++ b/code/modules/paperwork/paper.dm
@@ -22,6 +22,9 @@
max_integrity = 50
dog_fashion = /datum/dog_fashion/head
+
+ var/extra_headers //For additional styling or other js features.
+
var/info //What's actually written on the paper.
var/info_links //A different version of the paper which includes html links at fields and EOF
var/stamps //The (text for the) stamps on the paper.
@@ -69,10 +72,10 @@
if(in_range(user, src) || isobserver(user))
if(user.is_literate())
- user << browse("[name][info]
[stamps]", "window=[name]")
+ user << browse("[name][extra_headers][info]
[stamps]", "window=[name]")
onclose(user, "[name]")
else
- user << browse("[name][stars(info)]
[stamps]", "window=[name]")
+ user << browse("[name][extra_headers][stars(info)]
[stamps]", "window=[name]")
onclose(user, "[name]")
else
. += "You're too far away to read it!"
diff --git a/icons/obj/votebox.dmi b/icons/obj/votebox.dmi
new file mode 100644
index 00000000000..7bc1342c836
Binary files /dev/null and b/icons/obj/votebox.dmi differ
diff --git a/tgstation.dme b/tgstation.dme
index 08b45d8d65a..ebf27384806 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -1080,6 +1080,7 @@
#include "code\game\objects\structures\tank_dispenser.dm"
#include "code\game\objects\structures\target_stake.dm"
#include "code\game\objects\structures\traps.dm"
+#include "code\game\objects\structures\votingbox.dm"
#include "code\game\objects\structures\watercloset.dm"
#include "code\game\objects\structures\windoor_assembly.dm"
#include "code\game\objects\structures\window.dm"