diff --git a/SQL/migrate/V023__Tickets_nullable_taker.sql b/SQL/migrate/V023__Tickets_nullable_taker.sql new file mode 100644 index 00000000000..13cc789a072 --- /dev/null +++ b/SQL/migrate/V023__Tickets_nullable_taker.sql @@ -0,0 +1,6 @@ +-- +-- Fixes a bug by allowing the "Taken by" admin to be null. +-- + +ALTER TABLE `ss13_tickets` + MODIFY `taken_by` VARCHAR(32) NULL DEFAULT NULL; diff --git a/SQL/migrate/V024__poll_links.sql b/SQL/migrate/V024__poll_links.sql new file mode 100644 index 00000000000..0225e44133a --- /dev/null +++ b/SQL/migrate/V024__poll_links.sql @@ -0,0 +1,6 @@ +-- +-- Allows to add a link to the poll +-- + +ALTER TABLE `ss13_poll_question` + ADD COLUMN `link` VARCHAR(250) NULL DEFAULT NULL AFTER `createdby_ip`; diff --git a/code/game/machinery/crusher_piston.dm b/code/game/machinery/crusher_piston.dm index 95b9cc43068..0f82f6d453c 100644 --- a/code/game/machinery/crusher_piston.dm +++ b/code/game/machinery/crusher_piston.dm @@ -75,14 +75,14 @@ var/oldloc = loc var/obj/machinery/crusher_base/left = locate(/obj/machinery/crusher_base, get_step(src, WEST)) var/obj/machinery/crusher_base/right = locate(/obj/machinery/crusher_base, get_step(src, EAST)) - + loc=null if(left) left.update_icon() if(right) right.update_icon() loc=oldloc - + QDEL_NULL(pstn) return ..() @@ -100,7 +100,7 @@ return if(default_part_replacement(user, O)) return - + //Stuff you can do if the maint hatch is open if(panel_open) if(iswrench(O)) @@ -138,7 +138,7 @@ if(QDELETED(left)) left = null - + if(QDELETED(right)) right = null @@ -164,7 +164,7 @@ holographic_overlay(src, icon, "[asmtype]-overlay-green") if(panel_open) add_overlay("[asmtype]-hatch") - update_above() + update_above() /obj/machinery/crusher_base/power_change() ..() @@ -214,7 +214,7 @@ status = "stage1" action_start_time = world.time initial = 1 - + //Extend the second piston else if(status == "stage1") if(valve_check()) @@ -248,7 +248,7 @@ status = "stage3" action_start_time = world.time initial = 1 - + //Wait a moment, then reverse the direction else if(status == "stage3") if(initial) @@ -308,7 +308,7 @@ action_start_time = world.time initial = 1 update_icon() - + //Move all the items in the move list for(var/atom/movable/AM in items_to_move) if(!AM.simulated) @@ -322,7 +322,7 @@ if(!AM.piston_move()) items_to_crush += AM CHECK_TICK - + //Destroy all the items in the crush list for(var/atom/movable/AM in items_to_crush) items_to_crush -= AM @@ -366,7 +366,7 @@ name = "trash compactor piston" desc = "A colossal piston used for crushing garbage." icon = 'icons/obj/machines/crusherpiston.dmi' //Placeholder TODO: Get a proper icon - icon_state = "piston_0" + icon_state = "piston_0" density = 0 anchored = 1 pixel_y = -64 @@ -513,6 +513,8 @@ // The crush act proc // /atom/movable/proc/crush_act() + if(!simulated) + return ex_act(1) if(!QDELETED(src)) qdel(src)//Just as a failsafe diff --git a/code/game/objects/items/weapons/material/twohanded.dm b/code/game/objects/items/weapons/material/twohanded.dm index 99317004992..d4f83e4a7e7 100644 --- a/code/game/objects/items/weapons/material/twohanded.dm +++ b/code/game/objects/items/weapons/material/twohanded.dm @@ -317,6 +317,7 @@ base_icon = "chainsaw_off" flags = CONDUCT force = 10 + force_unwielded = 10 force_wielded = 20 throwforce = 5 w_class = ITEMSIZE_LARGE @@ -361,8 +362,9 @@ var/turf/T = get_turf(src) T.audible_message(span("notice", "\The [src] rumbles to life.")) playsound(src, "sound/weapons/chainsawstart.ogg", 25, 0, 30) - force = 20 - force_wielded = 40 + force = 15 + force_unwielded = 30 + force_wielded = 60 throwforce = 20 icon_state = "chainsaw_on" base_icon = "chainsaw_on" diff --git a/code/game/objects/structures/crates_lockers/closets.dm b/code/game/objects/structures/crates_lockers/closets.dm index f5ce9bacc24..5ec2c2a26d0 100644 --- a/code/game/objects/structures/crates_lockers/closets.dm +++ b/code/game/objects/structures/crates_lockers/closets.dm @@ -182,7 +182,6 @@ update_icon() return 1 -// this should probably use dump_contents() /obj/structure/closet/ex_act(severity) switch(severity) if(1) @@ -194,15 +193,16 @@ if (health <= 0) for (var/atom/movable/A as mob|obj in src) - A.forceMove(loc) A.ex_act(severity + 1) + dump_contents() + new /obj/item/stack/material/steel(get_turf(src)) qdel(src) /obj/structure/closet/proc/damage(var/damage) health -= damage if(health <= 0) - for(var/atom/movable/A in src) - A.forceMove(loc) + dump_contents() + new /obj/item/stack/material/steel(get_turf(src)) qdel(src) /obj/structure/closet/bullet_act(var/obj/item/projectile/Proj) @@ -452,3 +452,14 @@ /obj/structure/closet/onDropInto(var/atom/movable/AM) return + +/obj/structure/closet/crush_act() + for (var/atom/movable/A in src) + if(istype(A, /mob/living)) + var/mob/living/M = A + M.gib() + else if(A.simulated) + A.ex_act(1) + dump_contents() + new /obj/item/stack/material/steel(get_turf(src)) + qdel(src) diff --git a/code/game/objects/structures/watercloset.dm b/code/game/objects/structures/watercloset.dm index ea0d4fc66b9..b79518a17b3 100644 --- a/code/game/objects/structures/watercloset.dm +++ b/code/game/objects/structures/watercloset.dm @@ -134,6 +134,7 @@ density = 0 anchored = 1 use_power = 0 + var/spray_amount = 20 var/on = 0 var/obj/effect/mist/mymist = null var/ismist = 0 //needs a var so we can make it linger~ @@ -221,10 +222,10 @@ /obj/machinery/shower/proc/wash(atom/movable/O as obj|mob) if(!on) return - if(isliving(O)) - var/mob/living/L = O - L.ExtinguishMob() - L.fire_stacks = -20 //Douse ourselves with water to avoid fire more easily + var/obj/effect/effect/water/W = new(O) + W.create_reagents(spray_amount) + W.reagents.add_reagent("water", spray_amount) + W.set_up(O, spray_amount) if(iscarbon(O)) var/mob/living/carbon/M = O @@ -326,6 +327,7 @@ wash_floor() if(!mobpresent) return for(var/mob/living/L in loc) + wash(L) // Why was it not here before? process_heat(L) /obj/machinery/shower/proc/wash_floor() diff --git a/code/game/turfs/simulated/water.dm b/code/game/turfs/simulated/water.dm index ef5afab1fcf..c07a0c9cb47 100644 --- a/code/game/turfs/simulated/water.dm +++ b/code/game/turfs/simulated/water.dm @@ -23,6 +23,7 @@ movement_cost = 2 var/watertype = "water5" var/obj/effect/water_effect/water_overlay + var/numobjects = 0 /turf/simulated/floor/beach/water/pool name = "pool" @@ -56,6 +57,7 @@ var/obj/effect/water_effect/W = new /obj/effect/water_effect(src) W.icon_state = watertype water_overlay = W + create_reagents(2) /turf/simulated/floor/beach/water/Destroy() if(water_overlay) @@ -86,20 +88,44 @@ return return_air() // Otherwise their head is above the water, so get the air from the atmosphere instead. /turf/simulated/floor/beach/water/Entered(atom/movable/AM, atom/oldloc) - if(istype(AM, /mob/living)) + if(!SSATOMS_IS_PROBABLY_DONE) + return + reagents.add_reagent("water", 2) + clean(src) + START_PROCESSING(SSprocessing, src) + if(istype(AM, /obj)) + numobjects += 1 + else if(istype(AM, /mob/living)) + numobjects += 1 var/mob/living/L = AM if(!istype(oldloc, /turf/simulated/floor/beach/water)) to_chat(L, "You get drenched in water from entering \the [src]!") - AM.water_act(5) + wash(L) ..() /turf/simulated/floor/beach/water/Exited(atom/movable/AM, atom/newloc) - if(istype(AM, /mob/living)) + if(!SSATOMS_IS_PROBABLY_DONE) + return + reagents.add_reagent("water", 2) + clean(src) + if(istype(AM, /obj) && numobjects) + numobjects -= 1 + else if(istype(AM, /mob/living)) + if(numobjects) + numobjects -= 1 var/mob/living/L = AM if(!istype(newloc, /turf/simulated/floor/beach/water)) to_chat(L, "You climb out of \the [src].") ..() +/turf/simulated/floor/beach/water/process() + reagents.add_reagent("water", 2) + clean(src) + for(var/mob/living/L in src) + wash(L) + if(!numobjects) + STOP_PROCESSING(SSprocessing, src) + /mob/living/proc/can_breathe_water() return FALSE @@ -108,11 +134,105 @@ return species.can_breathe_water() return ..() -// Use this to have things react to having water applied to them. -/atom/movable/proc/water_act(amount) - return +// Taken from shower +/turf/simulated/floor/beach/water/proc/wash(atom/movable/O as obj|mob) -/mob/living/water_act(amount) - adjust_fire_stacks(-amount * 5) - for(var/atom/movable/AM in contents) - AM.water_act(amount) \ No newline at end of file + var/obj/effect/effect/water/W = new(O) + W.create_reagents(100) + W.reagents.add_reagent("water", 100) + W.set_up(O, 100) + + if(iscarbon(O)) + var/mob/living/carbon/M = O + if(M.r_hand) + M.r_hand.clean_blood() + if(M.l_hand) + M.l_hand.clean_blood() + if(M.back) + if(M.back.clean_blood()) + M.update_inv_back(0) + + //flush away reagents on the skin + if(M.touching) + var/remove_amount = M.touching.maximum_volume * M.reagent_permeability() //take off your suit first + M.touching.remove_any(remove_amount) + + if(ishuman(M)) + var/mob/living/carbon/human/H = M + var/washgloves = 1 + var/washshoes = 1 + var/washmask = 1 + var/washears = 1 + var/washglasses = 1 + + if(H.wear_suit) + washgloves = !(H.wear_suit.flags_inv & HIDEGLOVES) + washshoes = !(H.wear_suit.flags_inv & HIDESHOES) + + if(H.head) + washmask = !(H.head.flags_inv & HIDEMASK) + washglasses = !(H.head.flags_inv & HIDEEYES) + washears = !(H.head.flags_inv & HIDEEARS) + + if(H.wear_mask) + if (washears) + washears = !(H.wear_mask.flags_inv & HIDEEARS) + if (washglasses) + washglasses = !(H.wear_mask.flags_inv & HIDEEYES) + + if(H.head) + if(H.head.clean_blood()) + H.update_inv_head(0) + if(H.wear_suit) + if(H.wear_suit.clean_blood()) + H.update_inv_wear_suit(0) + else if(H.w_uniform) + if(H.w_uniform.clean_blood()) + H.update_inv_w_uniform(0) + if(H.gloves && washgloves) + if(H.gloves.clean_blood()) + H.update_inv_gloves(0) + if(H.shoes && washshoes) + if(H.shoes.clean_blood()) + H.update_inv_shoes(0) + if(H.wear_mask && washmask) + if(H.wear_mask.clean_blood()) + H.update_inv_wear_mask(0) + if(H.glasses && washglasses) + if(H.glasses.clean_blood()) + H.update_inv_glasses(0) + if(H.l_ear && washears) + if(H.l_ear.clean_blood()) + H.update_inv_ears(0) + if(H.r_ear && washears) + if(H.r_ear.clean_blood()) + H.update_inv_ears(0) + if(H.belt) + if(H.belt.clean_blood()) + H.update_inv_belt(0) + H.clean_blood(washshoes) + else + if(M.wear_mask) //if the mob is not human, it cleans the mask without asking for bitflags + if(M.wear_mask.clean_blood()) + M.update_inv_wear_mask(0) + M.clean_blood() + else + O.clean_blood() + + if(istype(O, /obj/item/weapon/light)) + var/obj/item/weapon/light/L = O + L.brightness_color = initial(L.brightness_color) + L.update() + else if(istype(O, /obj/machinery/light)) + var/obj/machinery/light/L = O + L.brightness_color = initial(L.brightness_color) + L.update() + + O.color = initial(O.color) + + if(isturf(loc)) + var/turf/tile = loc + loc.clean_blood() + for(var/obj/effect/E in tile) + if(istype(E,/obj/effect/rune) || istype(E,/obj/effect/decal/cleanable) || istype(E,/obj/effect/overlay)) + qdel(E) diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index 698393e9424..4c5e8f5ebc2 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -99,8 +99,7 @@ var/list/admin_verbs_admin = list( /client/proc/clear_toxins, /client/proc/wipe_ai, // allow admins to force-wipe AIs /client/proc/fix_player_list, - /client/proc/reset_openturf, - /client/proc/create_poll //Allows to create polls + /client/proc/reset_openturf ) var/list/admin_verbs_ban = list( /client/proc/unban_panel, @@ -371,7 +370,8 @@ var/list/admin_verbs_dev = list( //will need to be altered - Ryan784 /client/proc/cmd_dev_bst, /client/proc/lighting_show_verbs, /client/proc/cmd_display_del_log, - /client/proc/cmd_display_init_log + /client/proc/cmd_display_init_log, + /client/proc/create_poll //Allows to create polls ) var/list/admin_verbs_cciaa = list( /client/proc/cmd_admin_pm_panel, /*admin-pm list*/ diff --git a/code/modules/admin/create_poll.dm b/code/modules/admin/create_poll.dm index 0d0d123e12c..cb8d78969a3 100644 --- a/code/modules/admin/create_poll.dm +++ b/code/modules/admin/create_poll.dm @@ -2,7 +2,7 @@ set category = "Special Verbs" set name = "Create Poll" - if(!check_rights(R_ADMIN|R_DEV)) + if(!check_rights(R_DEV)) return if(!establish_db_connection(dbcon)) to_chat(src,"Failed to establish database connection.") @@ -60,13 +60,20 @@ viewtoken = "'[sanitizeSQL(viewtoken)]'" else viewtoken = "NULL" + + var/link = null + link = input("Do you want to provide a link to get more information?","Link") as text + if(link) + link = "'[sanitizeSQL(link)]'" + else + link = "NULL" var/sql_ckey = sanitizeSQL(ckey) var/question = input("Write your question","Question") as message if(!question) return question = sanitizeSQL(question) - var/DBQuery/query_polladd_question = dbcon.NewQuery("INSERT INTO ss13_poll_question (polltype, starttime, endtime, question, adminonly, multiplechoiceoptions, createdby_ckey, createdby_ip, publicresult, viewtoken) VALUES ('[polltype]', '[starttime]', '[endtime]', '[question]', '[adminonly]', '[choice_amount]', '[sql_ckey]', '[address]', '[publicresult]', [viewtoken])") + var/DBQuery/query_polladd_question = dbcon.NewQuery("INSERT INTO ss13_poll_question (polltype, starttime, endtime, question, adminonly, multiplechoiceoptions, createdby_ckey, createdby_ip, publicresult, viewtoken, link) VALUES ('[polltype]', '[starttime]', '[endtime]', '[question]', '[adminonly]', '[choice_amount]', '[sql_ckey]', '[address]', '[publicresult]', [viewtoken], [link])") if(!query_polladd_question.Execute()) var/err = query_polladd_question.ErrorMsg() to_chat(src,"SQL ERROR adding new poll question to table. Error : \[[err]\]\n") diff --git a/code/modules/admin/ticket.dm b/code/modules/admin/ticket.dm index 17dee02e3de..4a57da7bb2c 100644 --- a/code/modules/admin/ticket.dm +++ b/code/modules/admin/ticket.dm @@ -135,14 +135,30 @@ var/global/list/ticket_panels = list() if (!establish_db_connection(dbcon)) return - var/DBQuery/Q = dbcon.NewQuery("INSERT INTO ss13_tickets (game_id, message_count, admin_count, admin_list, opened_by, taken_by, closed_by, response_delay, opened_at, closed_at) VALUES (:g_id:, :m_count:, :a_count:, :a_list:, :opened_by:, :taken_by, :closed_by:, :delay:, :opened_at:, :closed_at:)") - Q.Execute(list("g_id" = game_id, "m_count" = length(msgs), "a_count" = length(assigned_admins), "a_list" = json_encode(assigned_admins), "opened_by" = owner, "taken_by" = assigned_admins[0], "closed_by" = closed_by, "delay" = response_time, "opened_at" = SQLtime(opened_rt), "closed_at" = SQLtime(closed_rt))) + var/DBQuery/Q = dbcon.NewQuery({"INSERT INTO ss13_tickets + (game_id, message_count, admin_count, admin_list, opened_by, taken_by, + closed_by, response_delay, opened_at, closed_at) + VALUES + (:g_id:, :m_count:, :a_count:, :a_list:, :opened_by:, :taken_by:, + :closed_by:, :delay:, :opened_at:, :closed_at:)"}) + Q.Execute(list( + "g_id" = game_id, + "m_count" = length(msgs), + "a_count" = length(assigned_admins), + "a_list" = json_encode(assigned_admins), + "opened_by" = owner, + "taken_by" = length(assigned_admins) ? assigned_admins[1] : null, + "closed_by" = closed_by, + "delay" = response_time || -1, + "opened_at" = SQLtime(opened_rt), + "closed_at" = SQLtime(closed_rt) + )) /datum/ticket/proc/append_message(m_from, m_to, msg) msgs += new /datum/ticket_msg(m_from, m_to, msg) if (!response_time && m_from != owner) - response_time = round((world.time - opened_time) SECONDS) + response_time = round((world.time - opened_time) / 10) update_ticket_panels() diff --git a/code/modules/customitems/item_defines.dm b/code/modules/customitems/item_defines.dm index 848a36523f9..f774e356b09 100644 --- a/code/modules/customitems/item_defines.dm +++ b/code/modules/customitems/item_defines.dm @@ -1936,4 +1936,107 @@ obj/item/clothing/suit/storage/hooded/fluff/make_poncho //Raincoat Poncho - M.A. icon = 'icons/obj/custom_items/fakhr_items.dmi' icon_state = "fakhr_hat" item_state = "fakhr_hat" - contained_sprite = TRUE \ No newline at end of file + contained_sprite = TRUE + + +/obj/item/fluff/daliyah_visa //NanoTrasen Exchange Visa - Daliyah Veridan - xanderdox + name = "NanoTrasen exchange visa" + desc = "A work visa authorizing the holder, Daliyah Veridan, to work within the Republic of Biesel. An Eridani and NanoTrasen logo are embossed on the back." + icon = 'icons/obj/custom_items/daliyah_visa.dmi' + icon_state = "daliyah_visa" + w_class = 2 + + +/obj/item/weapon/retractor/fluff/tristen_retractor //Laser Retractor - Tristen Wolff - elianabeth + name = "laser retractor" + desc = "The fabled laser retractor. It's a horrible amalgamation of a laser pointer, a retractor, and lots of tape." + icon = 'icons/obj/custom_items/tristen_retractor.dmi' + icon_state = "tristen_retractor" + + +/obj/item/fluff/amy_player //Music player - Amy Heris - golle + name = "music player" + desc = "An olive green HF24 in pristine condition, there is a small engraving on the back, reading \"To Amy, I will always be here for you, Varan.\"" + icon = 'icons/obj/custom_items/amy_player.dmi' + icon_state = "amy_player_off" + item_state = "electornic" + w_class = 2 + slot_flags = SLOT_BELT + var/broken = FALSE + var/open = FALSE + var/on = FALSE + var/obj/item/clothing/ears/earmuffs/earbuds + +/obj/item/fluff/amy_player/Initialize() + . = ..() + earbuds = new(src) + +/obj/item/fluff/amy_player/Destroy() + QDEL_NULL(earbuds) + return ..() + +/obj/item/fluff/amy_player/update_icon() + if(broken) + icon_state = "amy_player_broken" + return + if(on) + icon_state = "amy_player_on" + else + icon_state = "amy_player_off" + +/obj/item/fluff/amy_player/attack_self(mob/user as mob) + if(broken) + to_chat(user, "The screen flickers and blinks with errors.") + return + + if(!on) + to_chat(user, "You turn on \the [src].") + on = TRUE + + else + to_chat(user, "You turn off \the [src].") + on = FALSE + + update_icon() + + +/obj/item/fluff/amy_player/attack_hand(var/mob/user) + if(earbuds && src.loc == user) + earbuds.forceMove(user.loc) + user.put_in_hands(earbuds) + earbuds = null + update_icon() + return + + return ..() + +/obj/item/fluff/amy_player/attackby(var/obj/item/I, var/mob/user) + if(istype(I, /obj/item/clothing/ears/earmuffs) && !earbuds) + user.unEquip(I) + I.forceMove(src) + earbuds = I + to_chat(user, "You place \the [I] in \the [src].") + return + + if(broken) + if(isscrewdriver(I)) + if(!open) + open = TRUE + to_chat(user, "You unfasten the back panel.") + + else + open = FALSE + to_chat(user, "You secure the back panel.") + playsound(user.loc, 'sound/items/Screwdriver.ogg', 50, 1) + + if(ismultitool(I) && open) + to_chat(user, "You quickly pulse a few fires, and reset the screen and device.") + broken = FALSE + update_icon() + +/obj/item/fluff/amy_player/emp_act(severity) + broken = TRUE + on = FALSE + update_icon() + playsound(src, "spark", 50, 1, -1) + ..() \ No newline at end of file diff --git a/code/modules/mob/abstract/new_player/new_player.dm b/code/modules/mob/abstract/new_player/new_player.dm index 980bf896327..c046df081cf 100644 --- a/code/modules/mob/abstract/new_player/new_player.dm +++ b/code/modules/mob/abstract/new_player/new_player.dm @@ -216,6 +216,10 @@ INITIALIZE_IMMEDIATE(/mob/abstract/new_player) handle_player_polling() return + if(href_list["showpolllink"]) + show_poll_link(href_list["showpolllink"]) + return + if(href_list["pollid"]) var/pollid = href_list["pollid"] diff --git a/code/modules/mob/abstract/new_player/poll.dm b/code/modules/mob/abstract/new_player/poll.dm index cbaf9048686..ca491e4fe69 100644 --- a/code/modules/mob/abstract/new_player/poll.dm +++ b/code/modules/mob/abstract/new_player/poll.dm @@ -34,20 +34,35 @@ src << browse(output,"window=playerpolllist;size=500x300") +/mob/abstract/new_player/proc/show_poll_link(var/pollid = -1) + if(pollid == -1) return + establish_db_connection(dbcon) + if(dbcon.IsConnected()) + var/DBQuery/select_query = dbcon.NewQuery("SELECT link FROM ss13_poll_question WHERE id = :pollid:") + select_query.Execute(list("pollid"=pollid)) + + var/link = null + while(select_query.NextRow()) + link = select_query.item[1] + if(link && link != "") + usr << link(link) + else + log_debug("Polling: [usr.ckey] tried to open poll [pollid] with a invalid link: [link]") /mob/abstract/new_player/proc/poll_player(var/pollid = -1) if(pollid == -1) return establish_db_connection(dbcon) if(dbcon.IsConnected()) - var/DBQuery/select_query = dbcon.NewQuery("SELECT starttime, endtime, question, polltype, multiplechoiceoptions FROM ss13_poll_question WHERE id = [pollid]") - select_query.Execute() + var/DBQuery/select_query = dbcon.NewQuery("SELECT starttime, endtime, question, polltype, multiplechoiceoptions, link FROM ss13_poll_question WHERE id = :pollid:") + select_query.Execute(list("pollid"=pollid)) var/pollstarttime = "" var/pollendtime = "" var/pollquestion = "" var/polltype = "" + var/haslink = 0 var/found = 0 var/multiplechoiceoptions = 0 @@ -56,6 +71,8 @@ pollendtime = select_query.item[2] pollquestion = select_query.item[3] polltype = select_query.item[4] + if(select_query.item[6] && select_query.item[6] != "") + haslink = 1 found = 1 break @@ -89,7 +106,10 @@ var/output = "
"
+ output += "Poll runs from [pollstarttime] until [pollendtime]"
+ if(haslink)
+ output += "
Additional information is available here"
+ output += "
" if(!voted) //Only make this a form if we have not voted yet output += "