diff --git a/code/game/machinery/flatpacker.dm b/code/game/machinery/flatpacker.dm index 6c90e45e4f6..93bb14323aa 100644 --- a/code/game/machinery/flatpacker.dm +++ b/code/game/machinery/flatpacker.dm @@ -476,3 +476,9 @@ return ITEM_INTERACT_SUCCESS #undef MAX_FLAT_PACKS + +/obj/item/flatpack/flatpacker // a roundstart flatpacker is NICE you can gahdamn tell the time and everythin' + board = /obj/item/circuitboard/machine/flatpacker + +/obj/item/flatpack/mailsorter // to have a roundstart mail sorter at cargo + board = /obj/item/circuitboard/machine/mailsorter diff --git a/code/game/objects/items/circuitboards/machines/machine_circuitboards.dm b/code/game/objects/items/circuitboards/machines/machine_circuitboards.dm index a0685695cfa..e677d160980 100644 --- a/code/game/objects/items/circuitboards/machines/machine_circuitboards.dm +++ b/code/game/objects/items/circuitboards/machines/machine_circuitboards.dm @@ -1475,6 +1475,16 @@ /datum/stock_part/scanning_module = 1, /datum/stock_part/card_reader = 1) +/obj/item/circuitboard/machine/mailsorter + name = "Mail Sorter" + greyscale_colors = CIRCUIT_COLOR_SUPPLY + build_path = /obj/machinery/mailsorter + req_components = list( + /obj/item/stack/sheet/glass = 1, + /datum/stock_part/matter_bin = 2, + /datum/stock_part/scanning_module = 1) + needs_anchored = TRUE + //Tram /obj/item/circuitboard/machine/crossing_signal name = "Crossing Signal" diff --git a/code/modules/research/designs/machine_designs.dm b/code/modules/research/designs/machine_designs.dm index 15a5fed08eb..74e00231035 100644 --- a/code/modules/research/designs/machine_designs.dm +++ b/code/modules/research/designs/machine_designs.dm @@ -1347,3 +1347,13 @@ RND_CATEGORY_MACHINE + RND_SUBCATEGORY_MACHINE_ENGINEERING ) departmental_flags = DEPARTMENT_BITFLAG_ENGINEERING | DEPARTMENT_BITFLAG_CARGO + +/datum/design/board/mailsorter + name = "Mail Sorter" + desc = "The circuit board for a mail sorting unit." + id = "mailsorter" + build_path = /obj/item/circuitboard/machine/mailsorter + category = list( + RND_CATEGORY_MACHINE + RND_SUBCATEGORY_MACHINE_CARGO + ) + departmental_flags = DEPARTMENT_BITFLAG_CARGO | DEPARTMENT_BITFLAG_ENGINEERING diff --git a/code/modules/research/techweb/nodes/engi_nodes.dm b/code/modules/research/techweb/nodes/engi_nodes.dm index 4ef55e21bc9..75c9459771c 100644 --- a/code/modules/research/techweb/nodes/engi_nodes.dm +++ b/code/modules/research/techweb/nodes/engi_nodes.dm @@ -155,6 +155,7 @@ "manulathe", "manusorter", "manurouter", + "mailsorter", ) /datum/techweb_node/energy_manipulation diff --git a/code/modules/vending/mail.dm b/code/modules/vending/mail.dm new file mode 100644 index 00000000000..6bc3648b056 --- /dev/null +++ b/code/modules/vending/mail.dm @@ -0,0 +1,314 @@ +#define STATE_SORTING "sorting" +#define STATE_IDLE "idle" +#define STATE_YES "yes" +#define STATE_NO "no" +#define MAIL_CAPACITY 100 + +/obj/machinery/mailsorter + name = "mail sorter" + desc = "A large mail sorting unit. Sorting mail since 1987!" + icon = 'icons/obj/machines/mailsorter.dmi' + icon_state = "mailsorter" + base_icon_state = "mailsorter" + layer = BELOW_OBJ_LAYER + density = TRUE + max_integrity = 300 + integrity_failure = 0.33 + req_access = list(ACCESS_CARGO) + circuit = /obj/item/circuitboard/machine/mailsorter + + var/light_mask = "mailsorter-light-mask" + var/panel_type = "panel" + + /// What the machine is currently doing. Can be "sorting", "idle", "yes", "no". + var/currentstate = STATE_IDLE + /// List of all mail that's inside the mailbox. + var/list/mail_list = list() + /// The direction in which the mail will be unloaded. + var/output_dir = SOUTH + /// List of the departments to sort the mail for. + var/static/list/sorting_departments = list( + DEPARTMENT_ENGINEERING, + DEPARTMENT_SECURITY, + DEPARTMENT_MEDICAL, + DEPARTMENT_SCIENCE, + DEPARTMENT_CARGO, + DEPARTMENT_SERVICE, + DEPARTMENT_COMMAND, + ) + var/static/list/choices = list( + "Eject" = icon('icons/hud/radial.dmi', "radial_eject"), + "Dump" = icon('icons/hud/radial.dmi', "mail_dump"), + "Sort" = icon('icons/hud/radial.dmi', "mail_sort"), + ) + +/// Steps one tile in the `output_dir`. Returns `turf`. +/obj/machinery/mailsorter/proc/get_unload_turf() + return get_step(src, output_dir) + +/// Opening the maintenance panel. +/obj/machinery/mailsorter/screwdriver_act(mob/living/user, obj/item/tool) + default_deconstruction_screwdriver(user, "[base_icon_state]-off", base_icon_state, tool) + update_appearance(UPDATE_OVERLAYS) + return ITEM_INTERACT_SUCCESS + +/// Deconstructing the mail sorter. +/obj/machinery/mailsorter/crowbar_act(mob/living/user, obj/item/tool) + default_deconstruction_crowbar(tool) + return ITEM_INTERACT_SUCCESS + +/obj/machinery/mailsorter/examine(mob/user) + . = ..() + . += span_notice("There is[length(mail_list) < 100 ? " " : " no more "]space for [length(mail_list) < 100 ? "[100 - length(mail_list)] " : ""]envelope\s inside.") + . += span_notice("There [length(mail_list) >= 2 ? "are" : "is"] [length(mail_list) ? length(mail_list) : "no"] envelope\s inside.") + if(panel_open) + . += span_notice("Alt-click to rotate the output direction.") + +/obj/machinery/mailsorter/Destroy() + QDEL_LIST(mail_list) + . = ..() + +/obj/machinery/mailsorter/on_deconstruction(disassembled) + drop_all_mail() + . = ..() + +/// Drops all enevlopes on the machine turf. +/obj/machinery/mailsorter/proc/drop_all_mail() + if(!isturf(get_turf(src))) + QDEL_LIST(mail_list) + return + for(var/obj/item/mail in mail_list) + mail.forceMove(src) + mail_list -= mail + +/// Dumps all envelopes on the `unload_turf`. +/obj/machinery/mailsorter/proc/dump_all_mail() + if(!isturf(get_turf(src))) + QDEL_LIST(mail_list) + return + var/turf/unload_turf = get_unload_turf() + for(var/obj/item/mail in mail_list) + mail.forceMove(unload_turf) + mail.throw_at(unload_turf, 2, 3) + mail_list -= mail + +/// Validates whether the inserted item is acceptable. +/obj/machinery/mailsorter/proc/accept_check(obj/item/weapon) + var/static/list/accepted_items = list( + /obj/item/mail, + /obj/item/paper, + ) + return is_type_in_list(weapon, accepted_items) + +/obj/machinery/mailsorter/interact(mob/user) + if (!allowed(user)) + to_chat(user, span_warning("Access denied.")) + return + if (currentstate != STATE_IDLE) + return + if (length(mail_list) == 0) + to_chat(user, span_warning("There's no mail inside!")) + return + var/choice = show_radial_menu( + user, + src, + choices, + require_near = !HAS_SILICON_ACCESS(user), + autopick_single_option = FALSE, + ) + if (!choice) + return + switch (choice) + if ("Eject") + pick_mail(user) + if ("Dump") + playsound(src, 'sound/machines/buzz/buzz-sigh.ogg', 20, TRUE) + to_chat(user, span_notice("[src] dumps [length(mail_list)] envelope\s on the floor.")) + dump_all_mail() + if ("Sort") + sort_mail(user) + +/// Prompts the player to select a department to sort the mail for. Returns if `null`. +/obj/machinery/mailsorter/proc/sort_mail(mob/user) + var/sorting_dept = tgui_input_list(user, "Choose the department to sort mail for","Mail Sorting", sorting_departments) + if (!sorting_dept) + return + currentstate = STATE_SORTING + update_appearance(UPDATE_OVERLAYS) + playsound(src, 'sound/machines/mail_sort.ogg', 20, TRUE) + addtimer(CALLBACK(src, PROC_REF(continue_sort), user, sorting_dept), 5 SECONDS) + +/// Sorts the mail based on the picked department. Ejects the sorted envelopes onto the `unload_turf`. +/obj/machinery/mailsorter/proc/continue_sort(mob/user, sorting_dept) + var/list/sorted_mail = list() + var/total_to_sort = length(mail_list) + var/sorted = 0 + var/unable_to_sort = 0 + + for (var/obj/item/mail/some_mail in mail_list) + if (!some_mail.recipient_ref) + unable_to_sort ++ + continue + var/datum/mind/some_recipient = some_mail.recipient_ref.resolve() + if (some_recipient) + var/datum/job/recipient_job = some_recipient.assigned_role + var/datum/job_department/primary_department = recipient_job.departments_list?[1] + if (primary_department == null) // permabrig is temporary, tide is forever + unable_to_sort ++ + else + var/datum/job_department/main_department = primary_department.department_name + if (main_department == sorting_dept) + sorted_mail.Add(some_mail) + sorted ++ + else + unable_to_sort ++ + if (length(sorted_mail) == 0) + currentstate = STATE_NO + update_appearance(UPDATE_OVERLAYS) + playsound(src, 'sound/machines/buzz/buzz-sigh.ogg', 20, TRUE) + say("No mail for the following department: [sorting_dept].") + else + currentstate = STATE_YES + update_appearance(UPDATE_OVERLAYS) + say("[sorted] envelope\s sorted successfully.") + playsound(src, 'sound/machines/ping.ogg', 20, TRUE) + to_chat(user, span_notice("[src] ejects [length(sorted_mail)] envelope\s.")) + var/turf/unload_turf = get_unload_turf() + for (var/obj/item/mail/mail_in_list in sorted_mail) + mail_in_list.forceMove(unload_turf) + sorted_mail -= mail_in_list + mail_list -= mail_in_list + addtimer(CALLBACK(src, PROC_REF(check_sorted), unable_to_sort, total_to_sort), 1 SECONDS) + +/// Informs the player of the amount of processed envelopes. +/obj/machinery/mailsorter/proc/check_sorted(mob/user, unable_to_sort, total_to_sort) + if (unable_to_sort > 0) + playsound(src, 'sound/machines/buzz/buzz-sigh.ogg', 20, TRUE) + say("Couldn't sort [unable_to_sort] envelope\s.") + else + playsound(src, 'sound/machines/ping.ogg', 20, TRUE) + say("[total_to_sort] envelope\s processed.") + addtimer(CALLBACK(src, PROC_REF(update_state_after_sorting)), 1 SECONDS) + +/obj/machinery/mailsorter/proc/update_state_after_sorting() + currentstate = STATE_IDLE + update_appearance(UPDATE_OVERLAYS) + +/obj/machinery/mailsorter/item_interaction(mob/user, obj/item/thingy, params) + if (istype(thingy, /obj/item/storage/bag/mail)) + if (length(thingy.contents) < 1) + to_chat(user, span_warning("The [thingy] is empty!")) + return + var/loaded = 0 + for (var/obj/item/mail in thingy.contents) + if (!(mail.item_flags & ABSTRACT) && \ + !(mail.flags_1 & HOLOGRAM_1) && \ + accept_check(mail) \ + ) + if (length(mail_list) + 1 > MAIL_CAPACITY ) + to_chat(user, span_warning("There is no space for more mail in [src]!")) + return FALSE + else if (load(mail, user)) + loaded++ + mail_list += mail + if(loaded) + user.visible_message(span_notice("[user] loads \the [src] with \the [thingy]."), \ + span_notice("You load \the [src] with \the [thingy].")) + if(length(thingy.contents)) + to_chat(user, span_warning("Some items are refused.")) + return TRUE + else + to_chat(user, span_warning("There is nothing in \the [thingy] to put in the [src]!")) + return FALSE + else if (istype(thingy, /obj/item/mail)) + if (length(mail_list) + 1 > MAIL_CAPACITY ) + to_chat(user, span_warning("There is no space for more mail in [src]!")) + else + thingy.forceMove(src) + mail_list += thingy + to_chat(user, span_notice("The [src] whizzles as it accepts the [thingy].")) + +/// Prompts the user to select an anvelope from the list of all the envelopes inside. +/obj/machinery/mailsorter/proc/pick_mail(mob/user) + if(!length(mail_list)) + return + var/obj/item/mail/mail_throw = tgui_input_list(user, "Choose the envelope to eject","Mail Sorting", mail_list) + if(!mail_throw) + return + currentstate = STATE_SORTING + update_appearance(UPDATE_OVERLAYS) + playsound(src, 'sound/machines/mail_sort.ogg', 20, TRUE) + addtimer(CALLBACK(src, PROC_REF(pick_envelope), user, mail_throw), 50) + +/// Ejects a single envelope the player has picked onto the `unload_turf`. +/obj/machinery/mailsorter/proc/pick_envelope(mob/user, obj/item/mail/mail_throw) + to_chat(user, span_notice("[src] reluctantly spits out [mail_throw].")) + var/turf/unload_turf = get_unload_turf() + mail_throw.forceMove(unload_turf) + mail_throw.throw_at(unload_turf, 2, 3) + mail_list -= mail_throw + currentstate = STATE_IDLE + update_appearance(UPDATE_OVERLAYS) + +/// Tries to load something into the machine. +/obj/machinery/mailsorter/proc/load(obj/item/thingy, mob/user) + if(ismob(thingy.loc)) + var/mob/owner = thingy.loc + if(!owner.transferItemToLoc(thingy, src)) + to_chat(owner, span_warning("\the [thingy] is stuck to your hand, you cannot put it in \the [src]!")) + return FALSE + return TRUE + else + if(thingy.loc.atom_storage) + return thingy.loc.atom_storage.attempt_remove(thingy, src, silent = TRUE) + else + thingy.forceMove(src) + return TRUE + +/obj/machinery/mailsorter/click_alt(mob/living/user) + if(!panel_open) + return CLICK_ACTION_BLOCKING + output_dir = turn(output_dir, -90) + to_chat(user, span_notice("You change [src]'s I/O settings, setting the output to [dir2text(output_dir)].")) + update_appearance(UPDATE_OVERLAYS) + return CLICK_ACTION_SUCCESS + + +/obj/machinery/mailsorter/update_overlays() + . = ..() + if(!powered()) + return + if(!(machine_stat & BROKEN)) + var/image/mail_output = image(icon='icons/obj/doors/airlocks/station/overlays.dmi', icon_state="unres_[output_dir]") + switch(output_dir) + if(NORTH) + mail_output.pixel_y = 32 + if(SOUTH) + mail_output.pixel_y = -32 + if(EAST) + mail_output.pixel_x = 32 + if(WEST) + mail_output.pixel_x = -32 + mail_output.color = COLOR_CRAYON_ORANGE + var/mutable_appearance/light_out = emissive_appearance(mail_output.icon, mail_output.icon_state, offset_spokesman = src, alpha = mail_output.alpha) + light_out.pixel_y = mail_output.pixel_y + light_out.pixel_x = mail_output.pixel_x + . += mail_output + . += light_out + . += mutable_appearance(base_icon_state, currentstate) + if(panel_open) + . += panel_type + if(light_mask && !(machine_stat & BROKEN)) + . += emissive_appearance(icon, light_mask, src) + +/obj/machinery/mailsorter/update_icon_state() + icon_state = "[base_icon_state][powered() ? null : "-off"]" + if(machine_stat & BROKEN) + icon_state = "[base_icon_state]-broken" + return ..() + +#undef STATE_SORTING +#undef STATE_IDLE +#undef STATE_YES +#undef STATE_NO +#undef MAIL_CAPACITY diff --git a/code/modules/vending/wardrobes.dm b/code/modules/vending/wardrobes.dm index 4d37e2eb646..bec7da995df 100644 --- a/code/modules/vending/wardrobes.dm +++ b/code/modules/vending/wardrobes.dm @@ -204,6 +204,7 @@ GLOBAL_VAR_INIT(roaches_deployed, FALSE) /obj/item/storage/bag/mail = 3, /obj/item/radio/headset/headset_cargo = 3, /obj/item/clothing/accessory/pocketprotector = 3, + /obj/item/flatpack/mailsorter = 1, ) premium = list( /obj/item/clothing/head/costume/mailman = 1, diff --git a/html/changelogs/AutoChangeLog-pr-88288.yml b/html/changelogs/AutoChangeLog-pr-88288.yml new file mode 100644 index 00000000000..d785607b372 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88288.yml @@ -0,0 +1,5 @@ +author: "mcbalaam" +delete-after: True +changes: + - rscadd: "Added the mail sorting unit - working with mail has never been simpler!" + - rscadd: "Added two flatpack pre-defined subtypes for the flatpacker and the mail sorter." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88562.yml b/html/changelogs/AutoChangeLog-pr-88562.yml new file mode 100644 index 00000000000..9405b08a5a2 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88562.yml @@ -0,0 +1,4 @@ +author: "mcbalaam" +delete-after: True +changes: + - bugfix: "The mail sorter no longer runtimes processing assistant mail" \ No newline at end of file diff --git a/icons/hud/radial.dmi b/icons/hud/radial.dmi index f6e141ab685..5e32a89fe5d 100644 Binary files a/icons/hud/radial.dmi and b/icons/hud/radial.dmi differ diff --git a/icons/obj/machines/mailsorter.dmi b/icons/obj/machines/mailsorter.dmi new file mode 100644 index 00000000000..8d09e36796f Binary files /dev/null and b/icons/obj/machines/mailsorter.dmi differ diff --git a/icons/obj/machines/vending.dmi b/icons/obj/machines/vending.dmi index 319771e4e7f..8c39296a155 100644 Binary files a/icons/obj/machines/vending.dmi and b/icons/obj/machines/vending.dmi differ diff --git a/sound/machines/license.txt b/sound/machines/license.txt index dbccfd7ea09..69e52c94e4b 100644 --- a/sound/machines/license.txt +++ b/sound/machines/license.txt @@ -4,3 +4,5 @@ This is licensed under CC-BY 4.0, found at https://creativecommons.org/licenses/ shutter.ogg adapted from Joseph Sardin on BigSoundBank https://bigsoundbank.com/detail-2475-manual-roller-shutter-closing-out-2.html + +mail_sort.ogg adapted from csigusz_foxoup ob Freesound https://freesound.org/people/csigusz_foxoup/sounds/711428/ \ No newline at end of file diff --git a/sound/machines/mail_sort.ogg b/sound/machines/mail_sort.ogg new file mode 100644 index 00000000000..66ec79468d5 Binary files /dev/null and b/sound/machines/mail_sort.ogg differ diff --git a/tgstation.dme b/tgstation.dme index 1d5ca6537e9..a4d6a34c688 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -6400,6 +6400,7 @@ #include "code\modules\vending\liberation.dm" #include "code\modules\vending\liberation_toy.dm" #include "code\modules\vending\magivend.dm" +#include "code\modules\vending\mail.dm" #include "code\modules\vending\medical.dm" #include "code\modules\vending\medical_wall.dm" #include "code\modules\vending\megaseed.dm"