diff --git a/code/modules/paperwork/faxmachine.dm b/code/modules/paperwork/faxmachine.dm index 290d2d18aa..6fbff1a4a2 100644 --- a/code/modules/paperwork/faxmachine.dm +++ b/code/modules/paperwork/faxmachine.dm @@ -1,16 +1,17 @@ var/list/obj/machinery/photocopier/faxmachine/allfaxes = list() var/list/admin_departments = list("[using_map.boss_name]", "Solar Central Government", "Central Command Job Boards", "Supply") // YW EDIT var/list/alldepartments = list() +var/global/last_fax_role_request var/list/adminfaxes = list() //cache for faxes that have been sent to admins /obj/machinery/photocopier/faxmachine name = "fax machine" - desc = "Sent papers and pictures far away! Or to your co-worker's office a few doors down." + desc = "Send papers and pictures far away! Or to your co-worker's office a few doors down." icon = 'icons/obj/library.dmi' icon_state = "fax" insert_anim = "faxsend" - req_one_access = list(access_lawyer, access_heads, access_armory, access_qm) + req_one_access = list() use_power = USE_POWER_IDLE idle_power_usage = 30 @@ -41,6 +42,109 @@ var/list/adminfaxes = list() //cache for faxes that have been sent to admins else tgui_interact(user) +/obj/machinery/photocopier/faxmachine/verb/remove_card() + set name = "Remove ID card" + set category = "Object" + set src in oview(1) + + var/mob/living/L = usr + + if(!L || !isturf(L.loc) || !isliving(L)) + return + if(!ishuman(L) && !issilicon(L)) + return + if(L.stat || L.restrained()) + return + if(!scan) + to_chat(L, span_notice("There is no I.D card to remove!")) + return + + scan.forceMove(loc) + if(ishuman(usr) && !usr.get_active_hand()) + usr.put_in_hands(scan) + scan = null + authenticated = null + +/obj/machinery/photocopier/faxmachine/verb/request_roles() + set name = "Staff Request Form" + set category = "Object" + set src in oview(1) + + var/mob/living/L = usr + + if(!L || !isturf(L.loc) || !isliving(L)) + return + if(!ishuman(L) && !issilicon(L)) + return + if(L.stat || L.restrained()) + return + if(last_fax_role_request && (world.time - last_fax_role_request < 5 MINUTES)) + to_chat(L, "The global automated relays are still recalibrating. Try again later or relay your request in written form for processing.") + return + + var/confirmation = tgui_alert(L, "Are you sure you want to send automated crew request?", "Confirmation", list("Yes", "No", "Cancel")) + if(confirmation != "Yes") + return + + var/list/jobs = list() + for(var/datum/department/dept as anything in SSjob.get_all_department_datums()) + if(!dept.assignable || dept.centcom_only) + continue + for(var/job in SSjob.get_job_titles_in_department(dept.name)) + var/datum/job/J = SSjob.get_job(job) + if(J.requestable) + jobs |= job + + var/role = tgui_input_list(L, "Pick the job to request.", "Job Request", jobs) + if(!role) + return + + var/datum/job/job_to_request = SSjob.get_job(role) + var/reason = "Unspecified" + var/list/possible_reasons = list("Unspecified", "General duties", "Emergency situation") + possible_reasons += job_to_request.get_request_reasons() + reason = tgui_input_list(L, "Pick request reason.", "Request reason", possible_reasons) + + var/final_conf = tgui_alert(L, "You are about to request [role]. Are you sure?", "Confirmation", list("Yes", "No", "Cancel")) + if(final_conf != "Yes") + return + + var/datum/department/ping_dept = SSjob.get_ping_role(role) + if(!ping_dept) + to_chat(L, "Selected job cannot be requested for \[ERRORDEPTNOTFOUND] reason. Please report this to system administrator.") + return + var/message_color = "#FFFFFF" + var/ping_name = null + switch(ping_dept.name) + if(DEPARTMENT_COMMAND) + ping_name = "Command" + if(DEPARTMENT_SECURITY) + ping_name = "Security" + if(DEPARTMENT_ENGINEERING) + ping_name = "Engineering" + if(DEPARTMENT_MEDICAL) + ping_name = "Medical" + if(DEPARTMENT_RESEARCH) + ping_name = "Research" + if(DEPARTMENT_CARGO) + ping_name = "Supply" + if(DEPARTMENT_CIVILIAN) + ping_name = "Service" + if(DEPARTMENT_PLANET) + ping_name = "Expedition" + if(DEPARTMENT_SYNTHETIC) + ping_name = "Silicon" + //if(DEPARTMENT_TALON) + // ping_name = "Offmap" + if(!ping_name) + to_chat(L, "Selected job cannot be requested for \[ERRORUNKNOWNDEPT] reason. Please report this to system administrator.") + return + message_color = ping_dept.color + + message_chat_rolerequest(message_color, ping_name, reason, role) + last_fax_role_request = world.time + to_chat(L, "Your request was transmitted.") + /obj/machinery/photocopier/faxmachine/tgui_interact(mob/user, datum/tgui/ui) ui = SStgui.try_update_ui(user, src, ui) if(!ui) @@ -54,6 +158,7 @@ var/list/adminfaxes = list() //cache for faxes that have been sent to admins data["rank"] = rank data["isAI"] = isAI(user) data["isRobot"] = isrobot(user) + data["adminDepartments"] = admin_departments data["bossName"] = using_map.boss_name data["copyItem"] = copyitem @@ -111,6 +216,8 @@ var/list/adminfaxes = list() //cache for faxes that have been sent to admins usr.put_in_hands(copyitem) to_chat(usr, "You take \the [copyitem] out of \the [src].") copyitem = null + if("send_automated_staff_request") + request_roles() if(!authenticated) return @@ -258,10 +365,8 @@ var/list/adminfaxes = list() //cache for faxes that have been sent to admins C << 'sound/machines/printer.ogg' sender.client << 'sound/machines/printer.ogg' //CHOMPEdit - The pain must be felt - // VoreStation Edit Start var/faxid = export_fax(sent) - message_chat_admins(sender, faxname, sent, faxid, font_colour) - // VoreStation Edit End + message_chat_admins(sender, faxname, sent, faxid, font_colour) //Sends to admin chat // Webhooks don't parse the HTML on the paper, so we gotta strip them out so it's still readable. var/summary = make_summary(sent) @@ -282,3 +387,80 @@ var/list/adminfaxes = list() //cache for faxes that have been sent to admins "body" = summary ) ) + +/* + ##### #### + ##### Webhook Functionality #### + ##### #### +*/ + +/datum/configuration + var/chat_webhook_url = "" // URL of the webhook for sending announcements/faxes to discord chat. + var/chat_webhook_key = "" // Shared secret for authenticating to the chat webhook + var/fax_export_dir = "data/faxes" // Directory in which to write exported fax HTML files. + + +/** + * Write the fax to disk as (potentially multiple) HTML files. + * If the fax is a paper_bundle, do so recursively for each page. + * returns a random unique faxid. + */ +/obj/machinery/photocopier/faxmachine/proc/export_fax(fax) + var faxid = "[num2text(world.realtime,12)]_[rand(10000)]" + if (istype(fax, /obj/item/weapon/paper)) + var/obj/item/weapon/paper/P = fax + var/text = "[P.name][P.info][P.stamps]"; + file("[config.fax_export_dir]/fax_[faxid].html") << text; + else if (istype(fax, /obj/item/weapon/photo)) + var/obj/item/weapon/photo/H = fax + fcopy(H.img, "[config.fax_export_dir]/photo_[faxid].png") + var/text = "[H.name]" \ + + "" \ + + "" \ + + "[H.scribble ? "
Written on the back:
[H.scribble]" : ""]"\ + + "" + file("[config.fax_export_dir]/fax_[faxid].html") << text + else if (istype(fax, /obj/item/weapon/paper_bundle)) + var/obj/item/weapon/paper_bundle/B = fax + var/data = "" + for (var/page = 1, page <= B.pages.len, page++) + var/obj/pageobj = B.pages[page] + var/page_faxid = export_fax(pageobj) + data += "Page [page] - [pageobj.name]
" + var/text = "[B.name][data]" + file("[config.fax_export_dir]/fax_[faxid].html") << text + return faxid + + + +/** + * Call the chat webhook to transmit a notification of an admin fax to the admin chat. + */ +/obj/machinery/photocopier/faxmachine/proc/message_chat_admins(var/mob/sender, var/faxname, var/obj/item/sent, var/faxid, font_colour="#006100") + if (config.chat_webhook_url) + spawn(0) + var/query_string = "type=fax" + query_string += "&key=[url_encode(config.chat_webhook_key)]" + query_string += "&faxid=[url_encode(faxid)]" + query_string += "&color=[url_encode(font_colour)]" + query_string += "&faxname=[url_encode(faxname)]" + query_string += "&sendername=[url_encode(sender.name)]" + query_string += "&sentname=[url_encode(sent.name)]" + world.Export("[config.chat_webhook_url]?[query_string]") + + + + +/** + * Call the chat webhook to transmit a notification of a job request + */ +/obj/machinery/photocopier/faxmachine/proc/message_chat_rolerequest(var/font_colour="#006100", var/role_to_ping, var/reason, var/jobname) + if(config.chat_webhook_url) + spawn(0) + var/query_string = "type=rolerequest" + query_string += "&key=[url_encode(config.chat_webhook_key)]" + query_string += "&ping=[url_encode(role_to_ping)]" + query_string += "&color=[url_encode(font_colour)]" + query_string += "&reason=[url_encode(reason)]" + query_string += "&job=[url_encode(jobname)]" + world.Export("[config.chat_webhook_url]?[query_string]") diff --git a/tgui/packages/tgui/interfaces/Fax.js b/tgui/packages/tgui/interfaces/Fax.js index acd19b6189..86d8ea2982 100644 --- a/tgui/packages/tgui/interfaces/Fax.js +++ b/tgui/packages/tgui/interfaces/Fax.js @@ -7,21 +7,26 @@ import { LoginScreen } from './common/LoginScreen'; export const Fax = (props, context) => { const { data } = useBackend(context); - const { authenticated } = data; + const { authenticated, copyItem } = data; + + let variableHeight = 340; + if (copyItem) { + variableHeight = 358; + } if (!authenticated) { return ( - + ); } return ( - + @@ -34,7 +39,8 @@ export const Fax = (props, context) => { export const FaxContent = (props, context) => { const { act, data } = useBackend(context); - const { bossName, copyItem, cooldown, destination } = data; + const { bossName, copyItem, cooldown, destination, adminDepartments } = data; + const staffRequestDepartment = new Set(adminDepartments); return (
@@ -48,6 +54,7 @@ export const FaxContent = (props, context) => { {bossName} Quantum Entanglement Network + {(copyItem && ( @@ -70,6 +77,7 @@ export const FaxContent = (props, context) => { /> )) || Please insert item to transmit.} +
); }; @@ -94,3 +102,40 @@ const RemoveItem = (props, context) => { ); }; + +const AutomatedStaffRequest = (props, context) => { + const { act, data } = useBackend(context); + + const { adminDepartments, destination, copyItem } = data; + const staffRequestDepartment = new Set(adminDepartments); + + let flexiblePadding = '1rem'; + if (copyItem) { + flexiblePadding = '1.5rem'; + } + + if (!copyItem || (copyItem && staffRequestDepartment.has(destination))) { + return ( + + Or submit an automated staff request.

+ + The automated staff request form automatically populates the company + job board ((sends to discord, but does not ping.)) without requiring + intervention from central command clerks and officers.
+ It also works without requiring a written request to be composed. +
+
+ + ",$e+='
',$e+='
',$e+="Addons:
"+function(e){var t=[];return null==e||e.forEach((function(e){t.push(''+e+"")})),0===t.length&&t.push("No Addons Set"),t}(m)+"

",$e+="== Descriptions ==
",$e+="Vore Verb:
"+i+"

",$e+="Release Verb:
"+a+"

",$e+='Description:
"'+o+'"

',$e+='Absorbed Description:
"'+r+'"

',$e+="
",$e+="== Messages ==
",$e+='
',$e+='
",$e+='
',$e+='
',$e+='
',null==A||A.forEach((function(e){$e+=e+"
"})),$e+="
",$e+='
',null==M||M.forEach((function(e){$e+=e+"
"})),$e+="
",$e+='
',null==E||E.forEach((function(e){$e+=e+"
"})),$e+="
",$e+='
',null==O||O.forEach((function(e){$e+=e+"
"})),$e+="
",$e+='
',null==P||P.forEach((function(e){$e+=e+"
"})),$e+="
",$e+='
',null==F||F.forEach((function(e){$e+=e+"
"})),$e+="
",$e+='
',null==D||D.forEach((function(e){$e+=e+"
"})),$e+="
",$e+='
',null==R||R.forEach((function(e){$e+=e+"
"})),$e+="
",$e+='
',null==j||j.forEach((function(e){$e+=e+"
"})),$e+="
",$e+='
',null==W||W.forEach((function(e){$e+=e+"
"})),$e+="
",$e+='
',null==z||z.forEach((function(e){$e+=e+"
"})),$e+="
",$e+='
',null==U||U.forEach((function(e){$e+=e+"
"})),$e+="
",$e+="
",$e+="
",$e+="
",$e+="
= Idle Messages =

",$e+="

Idle Messages (Hold):

",null==G||G.forEach((function(e){$e+=e+"
"})),$e+="


",$e+="
Idle Messages (Hold Absorbed):

",null==q||q.forEach((function(e){$e+=e+"
"})),$e+="


",$e+="
Idle Messages (Digest):

",null==H||H.forEach((function(e){$e+=e+"
"})),$e+="


",$e+="
Idle Messages (Absorb):

",null==K||K.forEach((function(e){$e+=e+"
"})),$e+="


",$e+="
Idle Messages (Unabsorb):

",null==ee||ee.forEach((function(e){$e+=e+"
"})),$e+="


",$e+="
Idle Messages (Drain):

",null==$||$.forEach((function(e){$e+=e+"
"})),$e+="


",$e+="
Idle Messages (Heal):

",null==Y||Y.forEach((function(e){$e+=e+"
"})),$e+="


",$e+="
Idle Messages (Size Steal):

",null==X||X.forEach((function(e){$e+=e+"
"})),$e+="


",$e+="
Idle Messages (Shrink):

",null==J||J.forEach((function(e){$e+=e+"
"})),$e+="


",$e+="
Idle Messages (Grow):

",null==Z||Z.forEach((function(e){$e+=e+"
"})),$e+="


",$e+="
Idle Messages (Encase In Egg):

",null==Q||Q.forEach((function(e){$e+=e+"
"})),$e+="


",$e+="


",$e+="
",$e+='
',$e+='
',$e+='

',$e+='

",$e+='
',$e+='
',$e+='
    ',$e+='
  • Can Taste: '+(b?'Yes':'No')+"
  • ",$e+='
  • Feedable: '+(N?'Yes':'No')+"
  • ",$e+='
  • Contaminates: '+(g?'Yes':'No')+"
  • ",$e+='
  • Contamination Flavor: '+V+"
  • ",$e+='
  • Contamination Color: '+v+"
  • ",$e+='
  • Nutritional Gain: '+_+"%
  • ",$e+='
  • Required Examine Size: '+100*y+"%
  • ",$e+='
  • Display Absorbed Examines: '+(k?'True':'False')+"
  • ",$e+='
  • Save Digest Mode: '+(x?'True':'False')+"
  • ",$e+='
  • Idle Emotes: '+(w?'Active':'Inactive')+"
  • ",$e+='
  • Idle Emote Delay: '+L+" seconds
  • ",$e+='
  • Shrink/Grow Size: '+100*B+"%
  • ",$e+='
  • Vore Spawn Blacklist: '+(S?'Yes':'No')+"
  • ",$e+='
  • Egg Type: '+I+"
  • ",$e+='
  • Selective Mode Preference: '+T+"
  • ",$e+="
",$e+="
",$e+='
',$e+='

',$e+='

",$e+='
',$e+='
',$e+='
    ',$e+='
  • Fleshy Belly: '+(te?'Yes':'No')+"
  • ",$e+='
  • Internal Loop: '+(ne?'Yes':'No')+"
  • ",$e+='
  • Use Fancy Sounds: '+(oe?'Yes':'No')+"
  • ",$e+='
  • Vore Sound: '+re+"
  • ",$e+='
  • Release Sound: '+ie+"
  • ",$e+="
",$e+="
",$e+='
',$e+='

',$e+='

",$e+='
",$e+='
',$e+="Vore Sprites",$e+='
    ',$e+='
  • Affect Vore Sprites: '+(ae?'Yes':'No')+"
  • ",$e+='
  • Count Absorbed prey for vore sprites: '+(ce?'Yes':'No')+"
  • ",$e+='
  • Animation when prey resist: '+(le?'Yes':'No')+"
  • ",$e+='
  • Vore Sprite Size Factor: '+de+"
  • ",$e+='
  • Belly Sprite to affect: '+se+"
  • ",$e+="
",$e+="Belly Fullscreens Preview and Coloring",$e+='
    ',$e+='
  • Color: '+ue+"",$e+="
",$e+="Vore FX",$e+='
    ',$e+='
  • Disable Prey HUD: '+(me?'Yes':'No')+"
  • ",$e+="
",$e+="
",$e+='
',$e+='

',$e+='

",$e+='
',$e+='
',$e+="Belly Interactions ("+(pe?'Enabled':'Disabled')+")",$e+='
    ',$e+='
  • Escape Chance: '+fe+"%
  • ",$e+='
  • Escape Time: '+he/10+"s
  • ",$e+='
  • Transfer Chance: '+Ce+"%
  • ",$e+='
  • Transfer Location: '+be+"
  • ",$e+='
  • Secondary Transfer Chance: '+Ne+"%
  • ",$e+='
  • Secondary Transfer Location: '+ge+"
  • ",$e+='
  • Absorb Chance: '+Ve+"%
  • ",$e+='
  • Digest Chance: '+ve+"%
  • ",$e+="
",$e+="
",$e+="Auto-Transfer Options ("+(Le?'Enabled':'Disabled')+")",$e+='
    ',$e+='
  • Auto-Transfer Time: '+_e/10+"s
  • ",$e+='
  • Auto-Transfer Chance: '+ye+"%
  • ",$e+='
  • Auto-Transfer Location: '+ke+"
  • ",$e+='
  • Auto-Transfer Chance: '+xe+"%
  • ",$e+='
  • Auto-Transfer Location: '+we+"
  • ",$e+='
  • Auto-Transfer Min Amount: '+Be+"
  • ",$e+='
  • Auto-Transfer Max Amount: '+Se+"
  • ",$e+="
",$e+="
",$e+='
',$e+='

',$e+='

",$e+='
',$e+='
',$e+='
    ',$e+='
  • Generate Liquids: '+(Te?'On':'Off')+"
  • ",$e+='
  • Liquid Type: '+Ae+"
  • ",$e+='
  • Liquid Name: '+Me+"
  • ",$e+='
  • Transfer Verb: '+Ee+"
  • ",$e+='
  • Generation Time: '+Oe+"
  • ",$e+='
  • Liquid Capacity: '+Pe+"
  • ",$e+='
  • Slosh Sounds: '+(Fe?'On':'Off')+"
  • ",$e+='
  • Liquid Addons: '+function(e){var t=[];return null==e||e.forEach((function(e){t.push(''+e+"")})),0===t.length&&t.push("No Addons Set"),t}(De)+"
  • ",$e+="
",$e+="
",$e+='
',$e+='

',$e+='

",$e+='
',$e+='
',$e+='
',$e+='
",$e+='
',$e+='
',$e+='
',null==He||He.forEach((function(e){$e+=e+"
"})),$e+="
",$e+='
',null==Ge||Ge.forEach((function(e){$e+=e+"
"})),$e+="
",$e+='
',null==qe||qe.forEach((function(e){$e+=e+"
"})),$e+="
",$e+='
',null==Ke||Ke.forEach((function(e){$e+=e+"
"})),$e+="
",$e+='
',null==Ye||Ye.forEach((function(e){$e+=e+"
"})),$e+="
",$e+="
",$e+="
",$e+="
",$e+="
",$e+="
"},m=function(e,t){var n,o=(0,r.useBackend)(e),i=(o.act,o.data),a=i.db_version,c=i.db_repo,l=i.mob_name,d=i.bellies,s=function(){var e=new Date,t=String(e.getHours());t.length<2&&(t="0"+t);var n=String(e.getMinutes());n.length<2&&(n="0"+n);var o=String(e.getDate());o.length<2&&(o="0"+o);var r=String(e.getMonth()+1);return r.length<2&&(r="0"+r)," "+String(e.getFullYear())+"-"+r+"-"+o+" ("+t+" "+n+")"}(),m=l+s+t;if(".html"===t){n=new Blob([''+d.length+" Exported Bellies (DB_VER: "+c+"-"+a+')

Bellies of '+l+'

Generated on: '+s+'

'],{type:"text/html;charset=utf8"}),d.forEach((function(e,t){n=new Blob([n,u(e,t)],{type:"text/html;charset=utf8"})})),n=new Blob([n,"
",'