diff --git a/code/modules/paperwork/paper.dm b/code/modules/paperwork/paper.dm index 371f96e0da6..ca7d4ead559 100644 --- a/code/modules/paperwork/paper.dm +++ b/code/modules/paperwork/paper.dm @@ -77,6 +77,10 @@ camera_holder = null clear_paper() +/// Determines whether this paper has been written or stamped to. +/obj/item/paper/proc/is_empty() + return !(LAZYLEN(raw_text_inputs) || LAZYLEN(raw_stamp_data)) + /// Returns a deep copy list of raw_text_inputs, or null if the list is empty or doesn't exist. /obj/item/paper/proc/copy_raw_text() if(!LAZYLEN(raw_text_inputs)) @@ -124,7 +128,13 @@ * * greyscale_override - If set to a colour string and coloured is false, it will override the default of COLOR_WEBSAFE_DARK_GRAY when copying. */ /obj/item/paper/proc/copy(paper_type = /obj/item/paper, atom/location = loc, colored = TRUE, greyscale_override = null) - var/obj/item/paper/new_paper = new paper_type(location) + var/obj/item/paper/new_paper + if(ispath(paper_type, /obj/item/paper)) + new_paper = new paper_type(location) + else if(istype(paper_type, /obj/item/paper)) + new_paper = paper_type + else + CRASH("invalid paper_type [paper_type], paper type path or instance expected") new_paper.raw_text_inputs = copy_raw_text() new_paper.raw_field_input_data = copy_field_text() diff --git a/code/modules/paperwork/photocopier.dm b/code/modules/paperwork/photocopier.dm index 33eb035bfb8..3acf7733a64 100644 --- a/code/modules/paperwork/photocopier.dm +++ b/code/modules/paperwork/photocopier.dm @@ -1,3 +1,5 @@ +/// Name of the blanks file +#define BLANKS_FILE_NAME "config/blanks.json" /// For use with the `color_mode` var. Photos will be printed in greyscale while the var has this value. #define PHOTO_GREYSCALE "Greyscale" @@ -9,15 +11,52 @@ /// How much toner is used for making a copy of a photo. #define PHOTO_TONER_USE 0.625 /// How much toner is used for making a copy of a document. -#define DOCUMENT_TONER_USE 0.75 +#define DOCUMENT_TONER_USE (PAPER_TONER_USE * DOCUMENT_PAPER_USE) /// How much toner is used for making a copy of an ass. -#define ASS_TONER_USE 0.625 -/// How much toner is used for making a copy of paperwork -#define PAPERWORK_TONER_USE 0.75 +#define ASS_TONER_USE PHOTO_TONER_USE +/// How much toner is used for making a copy of paperwork. +#define PAPERWORK_TONER_USE (PAPER_TONER_USE * PAPERWORK_PAPER_USE) +/// At which toner charge amount we start losing color. Toner cartridges are scams. +#define TONER_CHARGE_LOW_AMOUNT 2 + +// please use integers here +/// How much paper is used for making a copy of paper. What, are you seriously surprised by this? +#define PAPER_PAPER_USE 1 +/// How much paper is used for making a copy of a photo. +#define PHOTO_PAPER_USE 1 +/// How much paper is used for making a copy of a document. +#define DOCUMENT_PAPER_USE 20 +/// How much paper is used for making a copy of a photo. +#define ASS_PAPER_USE PHOTO_PAPER_USE +/// How much paper is used for making a copy of paperwork. +#define PAPERWORK_PAPER_USE 10 + +/// Maximum capacity of a photocopier +#define MAX_PAPER_CAPACITY 60 /// The maximum amount of copies you can make with one press of the copy button. #define MAX_COPIES_AT_ONCE 10 +/// Photocopier copy fee. +#define PHOTOCOPIER_FEE 5 + +/// Paper blanks (form templates, basically). Loaded from `config/blanks.json`. +/// If invalid or not found, set to null. +GLOBAL_LIST_INIT(paper_blanks, init_paper_blanks()) + +/proc/init_paper_blanks() + if(!fexists(BLANKS_FILE_NAME)) + return null + var/list/blanks_json = json_decode(file2text(BLANKS_FILE_NAME)) + if(!length(blanks_json)) + return null + + var/list/parsed_blanks = list() + for(var/paper_blank in blanks_json) + parsed_blanks += list("[paper_blank["code"]]" = paper_blank) + + return parsed_blanks + /obj/machinery/photocopier name = "photocopier" desc = "Used to copy important documents and anatomy studies." @@ -39,8 +78,14 @@ var/busy = FALSE /// Variable needed to determine the selected category of forms on Photocopier.js var/category - ///Variable that holds a reference to any object supported for photocopying inside the photocopier + /// Variable that holds a reference to any object supported for photocopying inside the photocopier var/obj/object_copy + /// Variable for the UI telling us how many copies are in the queue. + var/copies_left = 0 + /// The amount of paper this photocoper starts with. + var/starting_paper = 30 + /// A stack for all the empty paper we have newly inserted (LIFO) + var/list/paper_stack = list() /obj/machinery/photocopier/Initialize(mapload) . = ..() @@ -49,7 +94,7 @@ /// Simply adds the necessary components for this to function. /obj/machinery/photocopier/proc/setup_components() - AddComponent(/datum/component/payment, 5, SSeconomy.get_dep_account(ACCOUNT_CIV), PAYMENT_CLINICAL) + AddComponent(/datum/component/payment, PHOTOCOPIER_FEE, SSeconomy.get_dep_account(ACCOUNT_CIV), PAYMENT_CLINICAL) /obj/machinery/photocopier/handle_atom_del(atom/deleting_atom) if(deleting_atom == object_copy) @@ -58,14 +103,27 @@ ass = null if(deleting_atom == toner_cartridge) toner_cartridge = null + if(deleting_atom in paper_stack) + paper_stack -= deleting_atom return ..() /obj/machinery/photocopier/Destroy() - QDEL_NULL(object_copy) + // object_copy can be a traitor objective, don't qdel + if(object_copy) + object_copy.forceMove(drop_location()) + object_copy = null + QDEL_NULL(toner_cartridge) + QDEL_LIST(paper_stack) + ass = null //the mob isn't actually contained and just referenced, no need to delete it. return ..() +/obj/machinery/photocopier/examine(mob/user) + . = ..() + if(object_copy) + . += span_notice("There is something inside the scanner tray.") + . += span_notice("You can put any type of blank paper inside to print a form onto it or to copy something onto it.") /obj/machinery/photocopier/ui_interact(mob/user, datum/tgui/ui) ui = SStgui.try_update_ui(user, src, ui) @@ -73,21 +131,33 @@ ui = new(user, src, "Photocopier") ui.open() +/obj/machinery/photocopier/ui_static_data(mob/user) + var/list/static_data = list() + + var/list/blank_infos = list() + var/list/category_names = list() + if(GLOB.paper_blanks) + for(var/blank_id in GLOB.paper_blanks) + var/list/paper_blank = GLOB.paper_blanks[blank_id] + blank_infos += list(list( + name = paper_blank["name"], + category = paper_blank["category"], + code = blank_id, + )) + category_names |= paper_blank["category"] + + static_data["blanks"] = blank_infos + static_data["categories"] = category_names + + return static_data + /obj/machinery/photocopier/ui_data(mob/user) var/list/data = list() data["has_item"] = !copier_empty() data["num_copies"] = num_copies - try - var/list/blanks = json_decode(file2text("config/blanks.json")) - if (blanks != null) - data["blanks"] = blanks - data["category"] = category - data["forms_exist"] = TRUE - else - data["forms_exist"] = FALSE - catch() - data["forms_exist"] = FALSE + data["category"] = category + data["copies_left"] = copies_left if(istype(object_copy, /obj/item/photo)) data["is_photo"] = TRUE @@ -95,7 +165,7 @@ if(isAI(user)) data["isAI"] = TRUE - data["can_AI_print"] = toner_cartridge ? toner_cartridge.charges >= PHOTO_TONER_USE : FALSE + data["can_AI_print"] = toner_cartridge && (toner_cartridge.charges >= PHOTO_TONER_USE) && (get_paper_count() >= PHOTO_PAPER_USE) else data["isAI"] = FALSE @@ -103,10 +173,10 @@ data["has_toner"] = TRUE data["current_toner"] = toner_cartridge.charges data["max_toner"] = toner_cartridge.max_charges - data["has_enough_toner"] = has_enough_toner() else data["has_toner"] = FALSE - data["has_enough_toner"] = FALSE + + data["paper_count"] = get_paper_count() return data @@ -115,6 +185,9 @@ if(.) return + if(machine_stat & (BROKEN|NOPOWER)) + return + switch(action) // Copying paper, photos, documents and asses. if("make_copy") @@ -122,28 +195,31 @@ return FALSE // ASS COPY. By Miauw if(ass) - do_copy_loop(CALLBACK(src, PROC_REF(make_ass_copy), usr), usr) + if(ishuman(ass) && (ass.get_item_by_slot(ITEM_SLOT_ICLOTHING) || ass.get_item_by_slot(ITEM_SLOT_OCLOTHING))) + if(ass == usr) + to_chat(usr, span_notice("You feel kind of silly, copying your ass with your clothes on.")) + else + to_chat(usr, span_notice("You feel kind of silly, copying [ass]\'s ass with [ass.p_their()] clothes on.")) + return FALSE + do_copies(CALLBACK(src, PROC_REF(make_ass_copy), usr), usr, ASS_PAPER_USE, ASS_TONER_USE, num_copies) return TRUE else + // Basic paper if(istype(object_copy, /obj/item/paper)) - var/obj/item/paper/paper_copy = object_copy - if(!paper_copy.get_total_length()) - to_chat(usr, span_warning("An error message flashes across [src]'s screen: \"The supplied paper is blank. Aborting.\"")) - return FALSE - // Basic paper - do_copy_loop(CALLBACK(src, PROC_REF(make_paper_copy), paper_copy), usr) + do_copies(CALLBACK(src, PROC_REF(make_paper_copy), object_copy), usr, PAPER_PAPER_USE, PAPER_TONER_USE, num_copies) return TRUE // Copying photo. if(istype(object_copy, /obj/item/photo)) - do_copy_loop(CALLBACK(src, PROC_REF(make_photo_copy), object_copy), usr) + var/obj/item/photo/photo_copy = object_copy + do_copies(CALLBACK(src, PROC_REF(make_photo_copy), photo_copy.picture, color_mode), usr, PHOTO_PAPER_USE, PHOTO_TONER_USE, num_copies) return TRUE // Copying Documents. if(istype(object_copy, /obj/item/documents)) - do_copy_loop(CALLBACK(src, PROC_REF(make_document_copy), object_copy), usr) + do_copies(CALLBACK(src, PROC_REF(make_document_copy), object_copy), usr, DOCUMENT_PAPER_USE, DOCUMENT_TONER_USE, num_copies) return TRUE // Copying paperwork if(istype(object_copy, /obj/item/paperwork)) - do_copy_loop(CALLBACK(src, PROC_REF(make_paperwork_copy), object_copy), usr) + do_copies(CALLBACK(src, PROC_REF(make_paperwork_copy), object_copy), usr, PAPERWORK_PAPER_USE, PAPERWORK_TONER_USE, num_copies) return TRUE // Remove the paper/photo/document from the photocopier. @@ -161,12 +237,10 @@ return FALSE var/mob/living/silicon/ai/tempAI = usr if(!length(tempAI.aicamera.stored)) - to_chat(usr, span_boldannounce("No images saved.")) - return + balloon_alert(usr, "no images saved!") + return FALSE var/datum/picture/selection = tempAI.aicamera.selectpicture(usr) - var/obj/item/photo/photo = new(loc, selection) // AI prints color photos only. - give_pixel_offset(photo) - toner_cartridge.charges -= PHOTO_TONER_USE + do_copies(CALLBACK(src, PROC_REF(make_photo_copy), selection, PHOTO_COLOR), usr, PHOTO_PAPER_USE, PHOTO_TONER_USE, 1) return TRUE // Switch between greyscale and color photos @@ -178,7 +252,7 @@ // Remove the toner cartridge from the copier. if("remove_toner") if(check_busy(usr)) - return + return FALSE var/success = usr.put_in_hands(toner_cartridge) if(!success) toner_cartridge.forceMove(drop_location()) @@ -198,63 +272,96 @@ if("print_blank") if(check_busy(usr)) return FALSE - if (toner_cartridge.charges - PAPER_TONER_USE < 0) - to_chat(usr, span_warning("There is not enough toner in [src] to print the form, please replace the cartridge.")) + if(!(params["code"] in GLOB.paper_blanks)) return FALSE - do_copy_loop(CALLBACK(src, PROC_REF(make_blank_print), params), usr) + var/list/blank = GLOB.paper_blanks[params["code"]] + do_copies(CALLBACK(src, PROC_REF(make_blank_print), blank), usr, PAPER_PAPER_USE, PAPER_TONER_USE, 1) return TRUE -/** - * Determines if the photocopier has enough toner to create `num_copies` amount of copies of the currently inserted item. - */ -/obj/machinery/photocopier/proc/has_enough_toner() - if(ass) - return toner_cartridge.charges >= (ASS_TONER_USE * num_copies) - if(isnull(object_copy)) - return FALSE - if(istype(object_copy, /obj/item/paper)) - return toner_cartridge.charges >= (PAPER_TONER_USE * num_copies) - if(istype(object_copy, /obj/item/documents)) - return toner_cartridge.charges >= (DOCUMENT_TONER_USE * num_copies) - if(istype(object_copy, /obj/item/photo)) - return toner_cartridge.charges >= (PHOTO_TONER_USE * num_copies) - if(istype(object_copy, /obj/item/paperwork)) - return toner_cartridge.charges >= (PAPERWORK_TONER_USE * num_copies) +/// Returns the color used for the printing operation. If the color is below TONER_LOW_PERCENTAGE, it returns a gray color. +/obj/machinery/photocopier/proc/get_toner_color() + return toner_cartridge.charges > TONER_CHARGE_LOW_AMOUNT ? COLOR_FULL_TONER_BLACK : COLOR_GRAY + + +/// Will invoke `do_copy_loop` asynchronously. Passes the supplied arguments on to it. +/obj/machinery/photocopier/proc/do_copies(datum/callback/copy_cb, mob/user, paper_use, toner_use, copies_amount) + busy = TRUE + update_use_power(ACTIVE_POWER_USE) + // fucking god proc + INVOKE_ASYNC(src, PROC_REF(do_copy_loop), copy_cb, user, paper_use, toner_use, copies_amount) /** - * Will invoke the passed in `copy_cb` callback in 1 second intervals, and charge the user 5 credits for each copy made. + * Will invoke the passed in `copy_cb` callback in 4 second intervals, and charge the user 5 credits for each copy made. * * Arguments: * * copy_cb - a callback for which proc to call. Should only be one of the `make_x_copy()` procs, such as `make_paper_copy()`. * * user - the mob who clicked copy. + * * paper_use - the amount of paper used in this operation + * * toner_use - the amount of toner used in this operation + * * copies_amount - the amount of copies we should make */ -/obj/machinery/photocopier/proc/do_copy_loop(datum/callback/copy_cb, mob/user) - busy = TRUE - update_use_power(ACTIVE_POWER_USE) - var/i - for(i in 1 to num_copies) - if(!toner_cartridge) //someone removed the toner cartridge during printing lol. - break - if(attempt_charge(src, user) & COMPONENT_OBJ_CANCEL_CHARGE) - balloon_alert(user, "insufficient funds!") - break - addtimer(copy_cb, i SECONDS) - addtimer(CALLBACK(src, PROC_REF(reset_busy)), i SECONDS) +/obj/machinery/photocopier/proc/do_copy_loop(datum/callback/copy_cb, mob/user, paper_use, toner_use, copies_amount) + var/error_message = null + if(!toner_cartridge) + copies_amount = 0 + error_message = span_warning("An error message flashes across \the [src]'s screen: \"No toner cartridge found. Aborting.\"") + else if(toner_cartridge.charges < toner_use * copies_amount) + copies_amount = FLOOR(toner_cartridge.charges / toner_use, 1) + error_message = span_warning("An error message flashes across \the [src]'s screen: \"Not enough toner to perform [copies_amount >= 1 ? "full " : ""]operation.\"") + if(get_paper_count() < paper_use * copies_amount) + copies_amount = FLOOR(get_paper_count() / paper_use, 1) + error_message = span_warning("An error message flashes across \the [src]'s screen: \"Not enough paper to perform [copies_amount >= 1 ? "full " : ""]operation.\"") -/** - * Sets busy to `FALSE`. Created as a proc so it can be used in callbacks. - */ + copies_left = copies_amount + + if(copies_amount <= 0) + to_chat(user, error_message) + reset_busy() + return + + if(attempt_charge(src, user, (copies_amount - 1) * PHOTOCOPIER_FEE) & COMPONENT_OBJ_CANCEL_CHARGE) + reset_busy() + return + + if(error_message) + to_chat(user, error_message) + + // if you managed to cancel the copy operation, tough luck. you aren't getting your money back. + for(var/i in 1 to copies_amount) + if(machine_stat & (BROKEN|NOPOWER)) + break + + if(!toner_cartridge) + break + + // arguments to copy_cb have been set at callback instantiation + var/atom/movable/copied_obj = copy_cb.Invoke() + if(isnull(copied_obj)) // something went wrong, so other copies will go wrong too + break + + playsound(src, 'sound/machines/printer.ogg', 50, vary = FALSE) + sleep(4 SECONDS) + + // reveal our copied item + copied_obj.forceMove(drop_location()) + give_pixel_offset(copied_obj) + copies_left-- + + copies_left = 0 + reset_busy() + +/// Sets busy to `FALSE`. /obj/machinery/photocopier/proc/reset_busy() update_use_power(IDLE_POWER_USE) busy = FALSE +/// Determines if the printer is currently busy, informs the user if it is. /obj/machinery/photocopier/proc/check_busy(mob/user) if(busy) - to_chat(user, span_warning("[src] is currently busy copying something. Please wait until it is finished.")) + balloon_alert(user, "printer is busy!") return TRUE return FALSE - /** * Gives items a random x and y pixel offset, between -10 and 10 for each. * @@ -267,36 +374,65 @@ copied_item.pixel_x = copied_item.base_pixel_x + rand(-10, 10) copied_item.pixel_y = copied_item.base_pixel_y + rand(-10, 10) +/// Gets the total amount of paper this printer has stored. +/obj/machinery/photocopier/proc/get_paper_count() + return length(paper_stack) + starting_paper + +/** + * Returns an empty paper, used for blanks and paper copies. + * Prioritizes `paper_stack`, creates new paper in case `paper_stack` is empty. + */ +/obj/machinery/photocopier/proc/get_empty_paper() + var/obj/item/paper/new_paper = pop(paper_stack) + if(new_paper == null && starting_paper > 0) + new_paper = new /obj/item/paper + starting_paper-- + return new_paper + +/** + * Removes an amount of paper from the printer's storage. + * This lets us pretend we actually consumed paper when we were actually printing something that wasn't paper. + */ +/obj/machinery/photocopier/proc/delete_paper(number) + if(number > get_paper_count()) + CRASH("Trying to delete more paper than is stored in the photocopier") + for(var/i in 1 to number) + var/to_delete = pop(paper_stack) + if(to_delete) + qdel(to_delete) + else + starting_paper-- + /** * Handles the copying of paper. Transfers all the text, stamps and so on from the old paper, to the copy. * * Checks first if `paper_copy` exists. Since this proc is called from a timer, it's possible that it was removed. */ /obj/machinery/photocopier/proc/make_paper_copy(obj/item/paper/paper_copy) - if(!paper_copy || !toner_cartridge) - return - - var/copy_colour = toner_cartridge.charges > 10 ? COLOR_FULL_TONER_BLACK : COLOR_GRAY; - - var/obj/item/paper/copied_paper = paper_copy.copy(/obj/item/paper, loc, FALSE, copy_colour) - - give_pixel_offset(copied_paper) - - copied_paper.name = paper_copy.name + if(isnull(paper_copy)) + return null + var/obj/item/paper/empty_paper = get_empty_paper() toner_cartridge.charges -= PAPER_TONER_USE + var/copy_colour = get_toner_color() + + var/obj/item/paper/copied_paper = paper_copy.copy(empty_paper, src, FALSE, copy_colour) + copied_paper.name = paper_copy.name + return copied_paper + /** * Handles the copying of photos, which can be printed in either color or greyscale. * - * Checks first if `photo_copy` exists. Since this proc is called from a timer, it's possible that it was removed. + * Checks first if `picture` exists. Since this proc is called from a timer, it's possible that it was removed. */ -/obj/machinery/photocopier/proc/make_photo_copy(obj/item/photo/photo_copy) - if(!photo_copy || !toner_cartridge) - return - var/obj/item/photo/copied_pic = new(loc, photo_copy.picture.Copy(color_mode == PHOTO_GREYSCALE ? TRUE : FALSE)) - give_pixel_offset(copied_pic) +/obj/machinery/photocopier/proc/make_photo_copy(datum/picture/photo, photo_color) + if(isnull(photo)) + return null + var/obj/item/photo/copied_pic = new(src, photo.Copy(photo_color == PHOTO_GREYSCALE ? TRUE : FALSE)) + delete_paper(PHOTO_PAPER_USE) toner_cartridge.charges -= PHOTO_TONER_USE + return copied_pic /** * Handles the copying of documents. @@ -304,11 +440,12 @@ * Checks first if `document_copy` exists. Since this proc is called from a timer, it's possible that it was removed. */ /obj/machinery/photocopier/proc/make_document_copy(obj/item/documents/document_copy) - if(!document_copy || !toner_cartridge) - return - var/obj/item/documents/photocopy/copied_doc = new(loc, document_copy) - give_pixel_offset(copied_doc) + if(isnull(document_copy)) + return null + var/obj/item/documents/photocopy/copied_doc = new(src, document_copy) + delete_paper(DOCUMENT_PAPER_USE) toner_cartridge.charges -= DOCUMENT_TONER_USE + return copied_doc /** * Handles the copying of documents. @@ -317,31 +454,33 @@ * Copies the stamp from a given piece of paperwork if it is already stamped, allowing for you to sell photocopied paperwork at the risk of losing budget money. */ /obj/machinery/photocopier/proc/make_paperwork_copy(obj/item/paperwork/paperwork_copy) - if(!paperwork_copy || !toner_cartridge) - return - var/obj/item/paperwork/photocopy/copied_paperwork = new(loc, paperwork_copy) + if(isnull(paperwork_copy)) + return null + var/obj/item/paperwork/photocopy/copied_paperwork = new(src, paperwork_copy) copied_paperwork.copy_stamp_info(paperwork_copy) if(paperwork_copy.stamped) copied_paperwork.stamp_icon = "paper_stamp-pc" //Override with the photocopy overlay sprite copied_paperwork.add_stamp() - give_pixel_offset(copied_paperwork) + delete_paper(PAPERWORK_PAPER_USE) toner_cartridge.charges -= PAPERWORK_TONER_USE + return copied_paperwork -/** - * The procedure is called when printing a blank to write off toner consumption. - */ -/obj/machinery/photocopier/proc/make_blank_print(params) - if(!toner_cartridge) - return - var/obj/item/paper/printblank = new(loc) - var/printname = sanitize(params["name"]) +/// Handles the copying of blanks. No mutating state, so this should not fail. +/obj/machinery/photocopier/proc/make_blank_print(list/blank) + var/copy_colour = get_toner_color() + var/obj/item/paper/printblank = get_empty_paper() + + var/printname = blank["name"] var/list/printinfo - for(var/infoline in params["info"]) + for(var/infoline in blank["info"]) printinfo += infoline - printblank.name = printname - printblank.add_raw_text(printinfo) + + printblank.name = "paper - '[printname]'" + printblank.add_raw_text(printinfo, color = copy_colour) printblank.update_appearance() + toner_cartridge.charges -= PAPER_TONER_USE + return printblank /** * Handles the copying of an ass photo. @@ -350,12 +489,8 @@ * Additionally checks that the mob has their clothes off. */ /obj/machinery/photocopier/proc/make_ass_copy(mob/user) - if(!check_ass() || !toner_cartridge) - return - if(ishuman(ass) && (ass.get_item_by_slot(ITEM_SLOT_ICLOTHING) || ass.get_item_by_slot(ITEM_SLOT_OCLOTHING))) - to_chat(user, span_notice("You feel kind of silly, copying [ass == user ? "your" : ass][ass == user ? "" : "\'s"] ass with [ass == user ? "your" : "[ass.p_their()]"] clothes on.") ) - return - + if(!check_ass()) + return null var/icon/temp_img if(ishuman(ass)) var/mob/living/carbon/human/H = ass @@ -371,25 +506,14 @@ else if(isdrone(ass)) //Drones are hot temp_img = icon('icons/ass/assdrone.png') - var/obj/item/photo/copied_ass = new /obj/item/photo(loc) + var/obj/item/photo/copied_ass = new /obj/item/photo(src) var/datum/picture/toEmbed = new(name = "[ass]'s Ass", desc = "You see [ass]'s ass on the photo.", image = temp_img) - give_pixel_offset(copied_ass) toEmbed.psize_x = 128 toEmbed.psize_y = 128 copied_ass.set_picture(toEmbed, TRUE, TRUE) + delete_paper(ASS_PAPER_USE) toner_cartridge.charges -= ASS_TONER_USE - -/** - * Inserts the item into the copier. Called in `attackby()` after a human mob clicked on the copier with a paper, photo, or document. - * - * Arugments: - * * object - the object that got inserted. - * * user - the mob that inserted the object. - */ -/obj/machinery/photocopier/proc/do_insertion(obj/item/object, mob/user) - object.forceMove(src) - to_chat(user, span_notice("You insert [object] into [src].")) - flick("photocopier1", src) + return copied_ass /** * Called when someone hits the "remove item" button on the copier UI. @@ -402,11 +526,13 @@ * * user - the user removing the item. */ /obj/machinery/photocopier/proc/remove_photocopy(obj/item/object, mob/user) - if(!issilicon(user)) //surprised this check didn't exist before, putting stuff in AI's hand is bad - object.forceMove(user.loc) - user.put_in_hands(object) - else + if(issilicon(user)) object.forceMove(drop_location()) + return + + object.forceMove(user.loc) + user.put_in_hands(object) + to_chat(user, span_notice("You take [object] out of [src]. [busy ? "The [src] comes to a halt." : ""]")) /obj/machinery/photocopier/wrench_act(mob/living/user, obj/item/tool) @@ -416,18 +542,23 @@ /obj/machinery/photocopier/attackby(obj/item/object, mob/user, params) if(istype(object, /obj/item/paper) || istype(object, /obj/item/photo) || istype(object, /obj/item/documents)) + if(istype(object, /obj/item/paper)) + var/obj/item/paper/paper = object + if(paper.is_empty()) + insert_empty_paper(paper, user) + return insert_copy_object(object, user) else if(istype(object, /obj/item/toner)) if(toner_cartridge) - to_chat(user, span_warning("[src] already has a toner cartridge inserted. Remove that one first.")) + balloon_alert(user, "another cartridge inside!") return object.forceMove(src) toner_cartridge = object - to_chat(user, span_notice("You insert [object] into [src].")) + balloon_alert(user, "cartridge inserted") else if(istype(object, /obj/item/areaeditor/blueprints)) - to_chat(user, span_warning("The Blueprint is too large to put into the copier. You need to find something else to record the document.")) + to_chat(user, span_warning("\The [object] is too large to put into the copier. You need to find something else to record the document.")) else if(istype(object, /obj/item/paperwork)) if(istype(object, /obj/item/paperwork/photocopy)) //No infinite paper chain. You need the original paperwork to make more copies. @@ -435,14 +566,29 @@ else insert_copy_object(object, user) +/// Proc that handles insertion of empty paper, useful for copying later. +/obj/machinery/photocopier/proc/insert_empty_paper(obj/item/paper/paper, mob/user) + if(istype(paper, /obj/item/paper/paperslip)) + return + if(get_paper_count() >= MAX_PAPER_CAPACITY) + balloon_alert(user, "cannot hold more paper!") + return + if(!user.temporarilyRemoveItemFromInventory(paper)) + return + paper_stack += paper + paper.forceMove(src) + balloon_alert(user, "paper inserted") + /obj/machinery/photocopier/proc/insert_copy_object(obj/item/object, mob/user) - if(copier_empty()) - if(!user.temporarilyRemoveItemFromInventory(object)) - return - object_copy = object - do_insertion(object, user) - else - to_chat(user, span_warning("There is already something in [src]!")) + if(!copier_empty()) + balloon_alert(user, "scanner tray occupied!") + return + if(!user.temporarilyRemoveItemFromInventory(object)) + return + object_copy = object + object.forceMove(src) + balloon_alert(user, "copy object inserted") + flick("photocopier1", src) /obj/machinery/photocopier/atom_break(damage_flag) . = ..() @@ -557,6 +703,15 @@ charges = 200 max_charges = 200 +#undef PHOTOCOPIER_FEE +#undef BLANKS_FILE_NAME +#undef PAPER_PAPER_USE +#undef PHOTO_PAPER_USE +#undef DOCUMENT_PAPER_USE +#undef ASS_PAPER_USE +#undef PAPERWORK_PAPER_USE +#undef MAX_PAPER_CAPACITY +#undef TONER_CHARGE_LOW_AMOUNT #undef PHOTO_GREYSCALE #undef PHOTO_COLOR #undef PAPER_TONER_USE diff --git a/config/blanks.json b/config/blanks.json index d564875137b..299fa67a594 100644 --- a/config/blanks.json +++ b/config/blanks.json @@ -1,10 +1,73 @@ [ + { + "code": "NT-CMD-AR", + "category": "Command Department", + "name": "Access Request", + "info": [ + "

Access Request

", + "
", + "

Name of requestee (with signature):

", + "

[___________________________________]

", + "

Access Grants:

", + "

[___________________________________]

", + "

[___________________________________]

", + "

Reason for Access Request:

", + "

[___________________________________]

", + "

[___________________________________]

", + "

[___________________________________]

", + "

[___________________________________]

", + "
", + "

Responsibility for proper handling of access to the restricted areas or equipment therein lies with the requestee. All empty fields must be filled with VOID before the form may be valid.

", + "
", + "

Request Review

", + "

Name of the Head of Personnel or deputy head (with signature):

", + "

[___________________________________]

", + "

Head of Personnel Decision:

", + "

[___________________________________]

", + "

[___________________________________]

", + "

Review Date:

", + "

[___].[___].[___]

", + "

[___]:[___]

", + "

Place for a stamp

", + "
", + "

Name of affected department head (with signature):

", + "

[___________________________________]

", + "

Department Head's Decision:

", + "

[___________________________________]

", + "

[___________________________________]

", + "

Review Date:

", + "

[___].[___].[___]

", + "

[___]:[___]

", + "

Place for a stamp

", + "
", + "

Name of affected department head (with signature):

", + "

[___________________________________]

", + "

Department Head's Decision:

", + "

[___________________________________]

", + "

[___________________________________]

", + "

Review Date:

", + "

[___].[___].[___]

", + "

[___]:[___]

", + "

Place for a stamp

", + "
", + "

Decision on Access Request

", + "

Request Decision:

", + "

[___________________________________]

", + "

[___________________________________]

", + "

Decision Date:

", + "

[___].[___].[___]

", + "

[___]:[___]

", + "

Place for a stamp

", + "
", + "

This document is the property of Nanotrasen Corporation. Without the signatures and stamps of the heads or their deputies, this document has no corporate validity and may not be used in decisions regarding access distribution.

" + ] + }, { "code": "NT-CMD-JA", "category": "Command Department", "name": "Job Application", "info": [ - "

Job Application

", + "

Job Application

", "
", "

Applicant's name (with signature):

", "

[___________________________________]

", @@ -13,10 +76,10 @@ "
", "

By signing this document, you agree to Nanotrasen's policies on labor protection, occupational health and safety, compensation, and confidentiality.

", "
", - "

Condemnation Review

", - "

Name of the Head of Personal or deputy head (with signature):

", + "

Condemnation Review

", + "

Name of the Head of Personnel or deputy head (with signature):

", "

[___________________________________]

", - "

Head of Personal Decision:

", + "

Head of Personnel Decision:

", "

[___________________________________]

", "

[___________________________________]

", "

Review Date:

", @@ -33,7 +96,7 @@ "

[___]:[___]

", "

Place for a stamp

", "
", - "

Appointment decision

", + "

Appointment decision

", "

[___________________________________]

", "

[___________________________________]

", "

[___________________________________]

", @@ -46,7 +109,7 @@ "category": "Command Department", "name": "Job termination", "info": [ - "

Job Termination

", + "

Job Termination

", "
", "

Name of the dismissed person:

", "

[___________________________________]

", @@ -56,9 +119,9 @@ "

[___________________________________]

", "

[___________________________________]

", "

[___________________________________]

", - "

Name of the Head of Personal or deputy head (with signature):

", + "

Name of the Head of Personnel or deputy head (with signature):

", "

[___________________________________]

", - "

Head of Personal Decision:

", + "

Head of Personnel Decision:

", "

[___________________________________]

", "

[___________________________________]

", "

Decision Date:

", @@ -86,7 +149,7 @@ "category": "Command Department", "name": "Job reassignment", "info": [ - "

Job Reassignment

", + "

Job Reassignment

", "
", "

Name of the person to be reassigned:

", "

[___________________________________]

", @@ -98,9 +161,9 @@ "

[___________________________________]

", "

[___________________________________]

", "

[___________________________________]

", - "

Name of the Head of Personal or deputy head (with signature):

", + "

Name of the Head of Personnel or deputy head (with signature):

", "

[___________________________________]

", - "

Head of Personal Decision:

", + "

Head of Personnel Decision:

", "

[___________________________________]

", "

[___________________________________]

", "

Decision Date:

", @@ -125,7 +188,7 @@ "category": "Command Department", "name": "Complaint form", "info": [ - "

Complaint form

", + "

Complaint form

", "
", "

Complainant's name (with signature):

", "

[___________________________________]

", @@ -161,7 +224,7 @@ "category": "Command Department", "name": "Order form", "info": [ - "

Order form

", + "

Order form

", "
", "

The name of the orderer (with signature):

", "

[___________________________________]

", @@ -202,7 +265,7 @@ "category": "Command Department", "name": "Permit to perform hazardous work", "info": [ - "

Permit to perform hazardous work

", + "

Permit to perform hazardous work

", "
", "

Permission requestor's name (with signature):

", "

[___________________________________]

", @@ -231,7 +294,7 @@ "category": "Security Department", "name": "Warrant", "info": [ - "

Warrant

", + "

Warrant

", "
", "

Arrestee's name:

", "

[___________________________________]

", @@ -257,7 +320,7 @@ "category": "Security Department", "name": "Search warrant", "info": [ - "

Search Warrant

", + "

Search Warrant

", "
", "

Name of the person being searched:

", "

[___________________________________]

", @@ -283,7 +346,7 @@ "category": "Security Department", "name": "Interrogation Report", "info": [ - "

Interrogation Report

", + "

Interrogation Report

", "
", "

Name of Interrogated:

", "

[___________________________________]

", @@ -328,7 +391,7 @@ "category": "Security Department", "name": "Security Report", "info": [ - "

Security Report

", + "

Security Report

", "
", "

Name of the reporting person (with signature):

", "

[___________________________________]

", @@ -366,7 +429,7 @@ "category": "Security Department", "name": "Execution order", "info": [ - "

Execution order

", + "

Execution order

", "
", "

The name of the condemned to the execution:

", "

[___________________________________]

", @@ -398,7 +461,7 @@ "category": "Security Department", "name": "Permission to possess", "info": [ - "

Permission to possess

", + "

Permission to possess

", "
", "

Owner's name:

", "

[___________________________________]

", @@ -435,7 +498,7 @@ "category": "Security Department", "name": "Court Summons", "info": [ - "

Court Order

", + "

Court Order

", "
", "

Requesting Party (with signature):

", "

[___________________________________]

", @@ -455,7 +518,7 @@ "category": "Security Department", "name": "Lawsuit Request Form", "info":[ - "

Lawsuit Request Form", + "

Lawsuit Request Form

", "

Prosecuters's name (with signature):

", "

[___________________________________]

", "

Affected Parties:

", @@ -502,7 +565,7 @@ "category": "Engineering Department", "name": "Permission to perform non-standard work", "info": [ - "

Permission to perform non-standard work

", + "

Permission to perform non-standard work

", "
", "

Permission requestor's name (with signature):

", "

[___________________________________]

", @@ -543,7 +606,7 @@ "category": "Engineering Department", "name": "Permit for the issuance of engineering equipment", "info": [ - "

Permit for the issuance of engineering equipment

", + "

Permit for the issuance of engineering equipment

", "
", "

Name of the requesting equipment (with signature):

", "

[___________________________________]

", @@ -588,7 +651,7 @@ "category": "Research Department", "name": "Permission to perform non-standard research", "info": [ - "

Permission to perform non-standard research

", + "

Permission to perform non-standard research

", "
", "

Permission requestor's name (with signature):

", "

[___________________________________]

", @@ -629,7 +692,7 @@ "category": "Research Department", "name": "Permit for the issuance of research equipment", "info": [ - "

Permit for the issuance of research equipment

", + "

Permit for the issuance of research equipment

", "
", "

Name of the requesting equipment (with signature):

", "

[___________________________________]

", @@ -674,7 +737,7 @@ "category": "Medical Department", "name": "Medical prescription", "info": [ - "

Medical Prescription

", + "

Medical Prescription

", "
", "

Patient name:

", "

[___________________________________]

", @@ -711,7 +774,7 @@ "category": "Medical Department", "name": "Autopsy report", "info": [ - "

Autopsy Report

", + "

Autopsy Report

", "
", "

Body Name:

", "

[___________________________________]

", @@ -745,7 +808,7 @@ "category": "Medical Department", "name": "Death Certificate", "info": [ - "

Death Certificate

", + "

Death Certificate

", "
", "

Body Name:

", "

[___________________________________]

", @@ -758,7 +821,7 @@ "

[___________________________________]

", "

[___________________________________]

", "
", - "
This is evidence that the designated person is permanently dead and cannot be resurrected. The person signing the certificate is responsible for the wrongful death.

", + "
This is evidence that the designated person is permanently dead and cannot be resurrected. The person signing the certificate is responsible for a wrongful death declaration.

", "
", "

Coroner's Name (with signature):

", "

[___________________________________]

", @@ -776,7 +839,7 @@ "category": "Supply Department", "name": "Permission to execute a non-standard order", "info": [ - "

Permission to execute a non-standard order

", + "

Permission to execute a non-standard order

", "
", "

The name of the person requesting the delivery (with signature):

", "

[___________________________________]

", @@ -821,7 +884,7 @@ "category": "Other", "name": "Lunch bill", "info": [ - "

Lunch Bill

", + "

Lunch Bill

", "
", "

Name of the place:

", "

[___________________________________]

", @@ -853,7 +916,7 @@ "category": "Other", "name": "Provision of paid services", "info": [ - "

Provision of paid services

", + "

Provision of paid services

", "
", "

Name of the place:

", "

[___________________________________]

", diff --git a/tgui/packages/tgui/interfaces/Photocopier.js b/tgui/packages/tgui/interfaces/Photocopier.js index 3e6b7e1f5cf..99c653be935 100644 --- a/tgui/packages/tgui/interfaces/Photocopier.js +++ b/tgui/packages/tgui/interfaces/Photocopier.js @@ -5,7 +5,14 @@ import { Window } from '../layouts'; export const Photocopier = (props, context) => { const { data } = useBackend(context); - const { isAI, has_toner, has_item, forms_exist } = data; + const { + isAI, + has_toner, + has_item, + categories = [], + paper_count, + copies_left, + } = data; return ( @@ -17,7 +24,13 @@ export const Photocopier = (props, context) => { No inserted toner cartridge. )} - {forms_exist ? ( +
+ Paper stored: {paper_count} + {!!copies_left && ( + Copies in queue: {copies_left} + )} +
+ {categories.length !== 0 ? ( ) : (
@@ -41,7 +54,7 @@ export const Photocopier = (props, context) => { const Toner = (props, context) => { const { act, data } = useBackend(context); - const { has_toner, max_toner, current_toner } = data; + const { max_toner, current_toner } = data; const average_toner = max_toner * 0.66; const bad_toner = max_toner * 0.33; @@ -50,10 +63,7 @@ const Toner = (props, context) => {
act('remove_toner')} - icon="eject"> + }> @@ -73,7 +83,7 @@ const Toner = (props, context) => { const Options = (props, context) => { const { act, data } = useBackend(context); - const { color_mode, is_photo, num_copies, has_enough_toner } = data; + const { color_mode, is_photo, num_copies } = data; return (
@@ -103,7 +113,6 @@ const Options = (props, context) => { ml={0.2} icon="copy" textAlign="center" - disabled={!has_enough_toner} onClick={() => act('make_copy')}> Copy @@ -153,16 +162,9 @@ const Options = (props, context) => { const Blanks = (props, context) => { const { act, data } = useBackend(context); - const { blanks, category, has_toner } = data; + const { blanks, categories, category } = data; - const sortedBlanks = sortBy((blank) => blanks.category)(blanks || []); - - const categories = []; - for (let blank of sortedBlanks) { - if (!categories.includes(blank.category)) { - categories.push(blank.category); - } - } + const sortedBlanks = sortBy((blank) => blank.name)(blanks || []); const selectedCategory = category ?? categories[0]; const visibleBlanks = sortedBlanks.filter( @@ -186,11 +188,9 @@ const Blanks = (props, context) => {