diff --git a/aurorastation.dme b/aurorastation.dme
index 36a4ac3d44a..4cf8d5c0685 100644
--- a/aurorastation.dme
+++ b/aurorastation.dme
@@ -2551,8 +2551,11 @@
#include "code\modules\law\laws\high_severity.dm"
#include "code\modules\law\laws\low_severity.dm"
#include "code\modules\law\laws\med_severity.dm"
+#include "code\modules\library\lib_binder.dm"
+#include "code\modules\library\lib_computer.dm"
#include "code\modules\library\lib_items.dm"
-#include "code\modules\library\lib_machines.dm"
+#include "code\modules\library\lib_public_computer.dm"
+#include "code\modules\library\lib_scanner.dm"
#include "code\modules\lighting\emissive_blocker.dm"
#include "code\modules\lighting\lighting_area.dm"
#include "code\modules\lighting\lighting_atom.dm"
diff --git a/code/modules/library/lib_binder.dm b/code/modules/library/lib_binder.dm
new file mode 100644
index 00000000000..65402009455
--- /dev/null
+++ b/code/modules/library/lib_binder.dm
@@ -0,0 +1,58 @@
+/*
+ * Book Binder
+ */
+/obj/machinery/bookbinder
+ name = "book binder"
+ desc = "A machine that takes paper and binds them into books. Fascinating!"
+ icon = 'icons/obj/library.dmi'
+ icon_state = "binder"
+ anchored = TRUE
+ density = TRUE
+ var/binding = FALSE
+
+/obj/machinery/bookbinder/attackby(obj/item/attacking_item, mob/user)
+ if(istype(attacking_item, /obj/item/paper))
+ var/obj/item/paper/paper = attacking_item
+ if(!anchored)
+ to_chat(user, SPAN_WARNING("\The [src] must be secured to the floor first!"))
+ return
+ if(binding)
+ to_chat(user, SPAN_WARNING("You must wait for \the [src] to finish its current operation!"))
+ return
+ var/turf/work_turf = get_turf(src)
+ user.drop_from_inventory(paper, src)
+ user.visible_message(
+ SPAN_NOTICE("\The [user] loads some paper into \the [src]."),
+ SPAN_NOTICE("You load some paper into \the [src]."))
+ visible_message(SPAN_NOTICE("\The [src] begins to hum as it warms up its printing drums."))
+ playsound(work_turf, 'sound/items/bureaucracy/binder.ogg', 75, 1)
+ binding = TRUE
+ sleep(rand(20 SECONDS, 40 SECONDS))
+ binding = FALSE
+ if(!anchored)
+ visible_message(SPAN_WARNING("\The [src] buzzes and flashes an error light."))
+ paper.forceMove(work_turf)
+ return
+ visible_message(SPAN_NOTICE("\The [src] whirs as it prints and binds a new book."))
+ playsound(work_turf, 'sound/items/bureaucracy/print.ogg', 75, 1)
+ var/obj/item/book/bound_book = new(work_turf)
+ bound_book.dat = paper.info
+ bound_book.name = "blank book"
+ bound_book.icon_state = "book[rand(1,7)]"
+ bound_book.item_state = icon_state
+ qdel(paper)
+ return
+
+ if(attacking_item.tool_behaviour == TOOL_WRENCH)
+ attacking_item.play_tool_sound(get_turf(src), 75)
+ if(anchored)
+ user.visible_message(
+ SPAN_NOTICE("\The [user] unsecures \the [src] from the floor."),
+ SPAN_NOTICE("You unsecure \the [src] from the floor."),
+ SPAN_WARNING("You hear a ratcheting noise."))
+ else
+ user.visible_message(
+ SPAN_NOTICE("\The [user] secures \the [src] to the floor."),
+ SPAN_NOTICE("You secure \the [src] to the floor."),
+ SPAN_WARNING("You hear a ratcheting noise."))
+ anchored = !anchored
diff --git a/code/modules/library/lib_computer.dm b/code/modules/library/lib_computer.dm
new file mode 100644
index 00000000000..51a98667214
--- /dev/null
+++ b/code/modules/library/lib_computer.dm
@@ -0,0 +1,425 @@
+/*
+ * Borrowbook datum
+ */
+/// Tracks a single book checkout: what was borrowed, by whom, and when it is due.
+/datum/borrowbook
+ var/book_name
+ var/mob_name
+ var/get_date
+ var/due_date
+
+/*
+ * Library Computer
+ */
+/obj/machinery/librarycomp
+ name = "library computer"
+ desc = "A computer."
+ icon = 'icons/obj/library.dmi'
+ icon_state = "computer"
+ anchored = TRUE
+ density = TRUE
+ var/upload_category = "Fiction"
+ /// Active book checkouts
+ var/list/datum/borrowbook/checkouts = list()
+ /// Weakrefs to physical books currently in the library's physical inventory
+ var/list/datum/weakref/inventory = list()
+ /// How long a checkout lasts, in minutes
+ var/checkout_period_minutes = 5
+ var/bible_on_cooldown = FALSE
+ var/is_public = FALSE
+ var/buffer_book // Set by barcode scanner mode 1
+ /// Title of the last successfully uploaded book, shown in the UI as confirmation
+ var/last_uploaded_title
+ var/datum/weakref/scanner_ref // Weakref to the nearest anchored book scanner
+ /// Current page of archive results (one page = 20 entries)
+ var/list/archive_results = list()
+ var/archive_loading = FALSE
+ var/archive_error = FALSE
+ var/archive_page = 1
+ /// Total number of archive entries matching the current search
+ var/archive_total = 0
+ var/archive_search = ""
+ var/archive_sort_field = "title"
+ var/archive_sort_dir = "asc"
+
+/obj/machinery/librarycomp/attack_hand(var/mob/user)
+ . = ..()
+ if(.)
+ return TRUE
+ ui_interact(user)
+
+/obj/machinery/librarycomp/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ var/title = is_public ? "[SSatlas.current_map.station_name] Library" : "[SSatlas.current_map.station_name] Library Management"
+ ui = new(user, src, "LibraryComputer", title, 750, 600)
+ ui.open()
+
+/obj/machinery/librarycomp/ui_data(mob/user)
+ var/obj/machinery/libraryscanner/scanner = scanner_ref?.resolve()
+ if(!scanner)
+ scanner_ref = null
+ for(var/obj/machinery/libraryscanner/nearby_scanner in range(9))
+ if(nearby_scanner.anchored)
+ scanner_ref = WEAKREF(nearby_scanner)
+ scanner = nearby_scanner
+ break
+
+ var/list/data = list()
+ data["is_public"] = is_public
+ data["is_emagged"] = emagged
+ data["bible_on_cooldown"] = bible_on_cooldown
+ data["checkout_period_minutes"] = checkout_period_minutes
+ data["archive_loading"] = archive_loading
+ data["archive_error"] = archive_error
+ data["archive_results"] = archive_results
+ data["archive_page"] = archive_page
+ data["archive_total"] = archive_total
+ data["archive_search"] = archive_search
+ data["archive_sort_field"] = archive_sort_field
+ data["archive_sort_dir"] = archive_sort_dir
+ data["archive_page_size"] = 20
+ data["upload_category"] = upload_category
+ data["buffer_book"] = buffer_book
+ data["last_uploaded_title"] = last_uploaded_title
+
+ var/list/inv = list()
+ var/list/stale = list()
+ for(var/datum/weakref/wref in inventory)
+ var/obj/item/book/book = wref.resolve()
+ if(book)
+ inv += list(list("ref" = REF(book), "name" = book.name))
+ else
+ stale += wref
+ inventory -= stale
+ data["inventory"] = inv
+
+ var/list/co = list()
+ for(var/datum/borrowbook/checkout in checkouts)
+ var/taken_minutes = round((world.time - checkout.get_date) / 1 MINUTES)
+ var/due_raw = (checkout.due_date - world.time) / 1 MINUTES
+ co += list(list(
+ "ref" = REF(checkout),
+ "book_name" = checkout.book_name,
+ "mob_name" = checkout.mob_name,
+ "taken_minutes" = taken_minutes,
+ "due_minutes" = abs(round(due_raw)),
+ "overdue" = (due_raw <= 0)
+ ))
+ data["checkouts"] = co
+
+ var/list/scanner_data = list()
+ scanner_data["found"] = !!(scanner?.anchored)
+ scanner_data["title"] = scanner?.cache ? scanner.cache.name : null
+ scanner_data["author"] = scanner?.cache ? (scanner.cache.author ? scanner.cache.author : "Anonymous") : null
+ data["scanner"] = scanner_data
+
+ var/list/crew_names = list()
+ var/list/manifest = SSrecords.get_manifest_list()
+ for(var/dept in manifest)
+ for(var/list/entry in manifest[dept])
+ if(!entry["ooc_role"] && entry["name"])
+ crew_names |= entry["name"]
+ data["crew_names"] = crew_names
+
+ return data
+
+/obj/machinery/librarycomp/emag_act(var/remaining_charges, var/mob/user)
+ if(src.density && !src.emagged)
+ src.emagged = TRUE
+ return 1
+
+/obj/machinery/librarycomp/attackby(obj/item/attacking_item, mob/user)
+ if(istype(attacking_item, /obj/item/barcodescanner))
+ var/obj/item/barcodescanner/barcode_scanner = attacking_item
+ barcode_scanner.computer_ref = REF(src)
+ to_chat(user, "[barcode_scanner]'s associated machine has been set to [src].")
+ for(var/mob/hearer in hearers(src))
+ hearer.show_message("[src] lets out a low, short blip.", 2)
+ else
+ ..()
+
+/obj/machinery/librarycomp/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
+ . = ..()
+ if(.)
+ return
+ add_fingerprint(usr)
+ switch(action)
+ if("print_bible")
+ if(!bible_on_cooldown)
+ var/obj/item/storage/bible/bible = new /obj/item/storage/bible(src.loc)
+ bible.verbs += /obj/item/storage/bible/verb/Set_Religion
+ var/rand_book = "book" + pick("1","2","3","4","5","6","7","8","9","10","11","12","13","14","15","16")
+ bible.icon_state = rand_book
+ bible.item_state = rand_book
+ bible.name = "religious book"
+ bible_on_cooldown = TRUE
+ addtimer(CALLBACK(src, PROC_REF(end_bible_cooldown)), 6 SECONDS, TIMER_UNIQUE|TIMER_STOPPABLE)
+ . = TRUE
+ if("arcane_confirm")
+ if(emagged && !bible_on_cooldown)
+ new /obj/item/book/tome(get_turf(src))
+ to_chat(usr, SPAN_WARNING("Your sanity barely endures the seconds spent in the vault's browsing window. The only thing to remind you of this when you stop browsing is a dusty old tome sitting on the desk. You don't really remember printing it."))
+ usr.visible_message(
+ SPAN_NOTICE("\The [usr] stares at the blank screen for a few moments, [usr.get_pronoun("his")] expression frozen in fear. When [usr.get_pronoun("he")] finally awakens from it, [usr.get_pronoun("he")] looks a lot older."),
+ range = 2)
+ bible_on_cooldown = TRUE
+ addtimer(CALLBACK(src, PROC_REF(end_bible_cooldown)), 6 SECONDS, TIMER_UNIQUE|TIMER_STOPPABLE)
+ . = TRUE
+ if("increase_checkout_period")
+ checkout_period_minutes += 1
+ . = TRUE
+ if("decrease_checkout_period")
+ checkout_period_minutes = max(1, checkout_period_minutes - 1)
+ . = TRUE
+ if("checkout_book")
+ var/book_title = sanitizeSafe(params["book_title"])
+ var/recipient = sanitize(params["recipient"], MAX_NAME_LEN)
+ if(book_title && recipient)
+ var/datum/borrowbook/new_checkout = new /datum/borrowbook
+ new_checkout.book_name = book_title
+ new_checkout.mob_name = recipient
+ new_checkout.get_date = world.time
+ new_checkout.due_date = world.time + (checkout_period_minutes * 1 MINUTES)
+ checkouts.Add(new_checkout)
+ . = TRUE
+ if("checkin_book")
+ var/datum/borrowbook/checkout = locate(params["ref"])
+ if(checkout && (checkout in checkouts))
+ checkouts.Remove(checkout)
+ . = TRUE
+ if("delete_inventory_book")
+ var/obj/item/book/book = locate(params["ref"])
+ if(book && !QDELETED(book))
+ inventory.Remove(WEAKREF(book))
+ . = TRUE
+ if("set_upload_category")
+ var/new_category = params["value"]
+ if(new_category in list("Fiction", "Non-Fiction", "Reference", "Religion"))
+ upload_category = new_category
+ . = TRUE
+ if("set_upload_author")
+ var/obj/machinery/libraryscanner/sc = get_scanner()
+ if(sc?.cache)
+ sc.cache.author = sanitize(params["value"])
+ . = TRUE
+ if("clear_scanner_cache")
+ var/obj/machinery/libraryscanner/sc = get_scanner()
+ if(sc)
+ sc.cache = null
+ SStgui.update_uis(sc)
+ last_uploaded_title = null
+ . = TRUE
+ if("upload_book")
+ var/obj/machinery/libraryscanner/sc = get_scanner()
+ if(!is_public && sc?.anchored && sc.cache)
+ if(sc.cache.unique)
+ to_chat(usr, SPAN_WARNING("This book has been rejected from the database."))
+ else
+ INVOKE_ASYNC(src, PROC_REF(async_upload_book), usr)
+ . = TRUE
+ if("fetch_archive")
+ if(!archive_loading)
+ archive_page = 1
+ archive_search = ""
+ INVOKE_ASYNC(src, PROC_REF(async_fetch_archive))
+ . = TRUE
+ if("archive_go_to_page")
+ var/new_page = text2num(params["page"])
+ if(isnum(new_page) && new_page >= 1 && !archive_loading)
+ archive_page = round(new_page)
+ INVOKE_ASYNC(src, PROC_REF(async_fetch_archive))
+ . = TRUE
+ if("archive_set_search")
+ if(!archive_loading)
+ archive_search = sanitize(params["query"])
+ archive_page = 1
+ INVOKE_ASYNC(src, PROC_REF(async_fetch_archive))
+ . = TRUE
+ if("archive_set_sort")
+ var/field = params["field"]
+ if(field in list("title", "author", "category"))
+ var/direction = params["dir"]
+ archive_sort_field = field
+ archive_sort_dir = (direction == "desc") ? "desc" : "asc"
+ archive_page = 1
+ if(!archive_loading)
+ INVOKE_ASYNC(src, PROC_REF(async_fetch_archive))
+ . = TRUE
+ if("order_book")
+ var/book_id = text2num(params["id"])
+ if(isnum(book_id) && book_id > 0)
+ if(!bible_on_cooldown)
+ INVOKE_ASYNC(src, PROC_REF(async_order_book), book_id)
+ else
+ visible_message("\The [src]'s monitor flashes: \"Printer unavailable. Please allow a short time before attempting to print.\"")
+ . = TRUE
+ if("extend_checkout")
+ var/datum/borrowbook/checkout = locate(params["ref"])
+ if(checkout && (checkout in checkouts))
+ checkout.due_date += checkout_period_minutes * 1 MINUTES
+ . = TRUE
+ if("print_inventory")
+ var/dat = "Library Inventory
"
+ var/printed = 0
+ for(var/datum/weakref/wref in inventory)
+ var/obj/item/book/book = wref.resolve()
+ if(book)
+ dat += "- [book.name] "
+ printed++
+ if(!printed)
+ dat += "No books in inventory. "
+ var/obj/item/paper/printout = new /obj/item/paper(src.loc)
+ printout.info = dat
+ printout.name = "paper- 'Library Inventory'"
+ . = TRUE
+ if("print_checkouts")
+ var/dat = "Active Checkouts
"
+ if(checkouts.len)
+ for(var/datum/borrowbook/checkout in checkouts)
+ var/taken_minutes = round((world.time - checkout.get_date) / 1 MINUTES)
+ var/due_raw = (checkout.due_date - world.time) / 1 MINUTES
+ var/status = due_raw <= 0 ? "OVERDUE by [abs(round(due_raw))] min" : "due in [round(due_raw)] min"
+ dat += "- [checkout.book_name] ([checkout.mob_name]) — taken [taken_minutes] min ago, [status] "
+ else
+ dat += "No books currently checked out. "
+ var/obj/item/paper/printout = new /obj/item/paper(src.loc)
+ printout.info = dat
+ printout.name = "paper- 'Active Checkouts'"
+ . = TRUE
+
+/obj/machinery/librarycomp/proc/end_bible_cooldown()
+ bible_on_cooldown = FALSE
+ SStgui.update_uis(src)
+
+/obj/machinery/librarycomp/proc/get_scanner()
+ var/obj/machinery/libraryscanner/scanner = scanner_ref?.resolve()
+ if(!scanner)
+ scanner_ref = null
+ for(var/obj/machinery/libraryscanner/nearby_scanner in range(9))
+ if(nearby_scanner.anchored)
+ scanner_ref = WEAKREF(nearby_scanner)
+ return nearby_scanner
+ return null
+ return scanner
+
+/obj/machinery/librarycomp/proc/async_fetch_archive()
+ archive_loading = TRUE
+ archive_error = FALSE
+ archive_total = 0
+ archive_results = list()
+ SStgui.update_uis(src)
+
+ // Build WHERE clause — keep % wildcards out of params via CONCAT so
+ // the param value is the raw search term without special characters
+ var/sql_where = ""
+ var/list/count_args
+ var/list/select_args
+ if(length(archive_search))
+ sql_where = "WHERE (title LIKE CONCAT('%', :search, '%') OR author LIKE CONCAT('%', :search, '%') OR category LIKE CONCAT('%', :search, '%'))"
+ count_args = list("search" = archive_search)
+ select_args = list("search" = archive_search)
+ else
+ count_args = list()
+ select_args = list()
+
+ // Validate sort field and direction before interpolating into SQL
+ var/safe_field
+ switch(archive_sort_field)
+ if("author") safe_field = "author"
+ if("category") safe_field = "category"
+ else safe_field = "title"
+ var/safe_dir = (archive_sort_dir == "desc") ? "DESC" : "ASC"
+
+ // COUNT query to get total matching entries
+ var/datum/db_query/count_query = SSdbcore.NewQuery(
+ "SELECT COUNT(*) FROM ss13_library [sql_where]",
+ count_args)
+ if(!count_query.Execute())
+ archive_error = TRUE
+ qdel(count_query)
+ archive_loading = FALSE
+ SStgui.update_uis(src)
+ return
+ archive_total = count_query.NextRow() ? text2num(count_query.item[1]) : 0
+ qdel(count_query)
+
+ // Clamp page to valid range now that we know the total
+ var/total_pages = max(1, CEILING(archive_total, 20) / 20)
+ archive_page = clamp(archive_page, 1, total_pages)
+ var/sql_offset = (archive_page - 1) * 20
+
+ // Paginated SELECT
+ var/datum/db_query/query = SSdbcore.NewQuery(
+ "SELECT id, author, title, category FROM ss13_library [sql_where] ORDER BY [safe_field] [safe_dir] LIMIT 20 OFFSET [sql_offset]",
+ select_args)
+ if(!query.Execute())
+ archive_error = TRUE
+ qdel(query)
+ archive_loading = FALSE
+ SStgui.update_uis(src)
+ return
+
+ var/list/results = list()
+ while(query.NextRow())
+ results += list(list(
+ "id" = text2num(query.item[1]),
+ "author" = query.item[2],
+ "title" = query.item[3],
+ "category" = query.item[4]
+ ))
+ qdel(query)
+ archive_results = results
+ archive_loading = FALSE
+ SStgui.update_uis(src)
+
+/obj/machinery/librarycomp/proc/async_upload_book(mob/uploader)
+ var/obj/machinery/libraryscanner/scanner = get_scanner()
+ var/obj/item/book/book = scanner?.cache
+ if(!book)
+ return
+ var/datum/db_query/query = SSdbcore.NewQuery(
+ "INSERT INTO ss13_library (author, title, content, category, uploadtime, uploader) VALUES (:author, :title, :content, :category, NOW(), :uploader)",
+ list(
+ "author" = book.author ? book.author : "Anonymous",
+ "title" = book.name,
+ "content" = book.dat,
+ "category" = upload_category,
+ "uploader" = ckey(uploader.client.ckey)
+ ))
+ if(!query.Execute())
+ to_chat(uploader, SPAN_WARNING("Upload failed: [query.last_error]"))
+ qdel(query)
+ return
+ qdel(query)
+ log_and_message_admins("has uploaded the book titled [book.name], [length(book.dat)] signs")
+ log_game("[uploader.name]/[uploader.key] has uploaded the book titled [book.name], [length(book.dat)] signs")
+ to_chat(uploader, SPAN_NOTICE("Upload complete."))
+ last_uploaded_title = book.name
+ SStgui.update_uis(src)
+
+/obj/machinery/librarycomp/proc/async_order_book(var/book_id)
+ bible_on_cooldown = TRUE
+ addtimer(CALLBACK(src, PROC_REF(end_bible_cooldown)), 6 SECONDS, TIMER_UNIQUE|TIMER_STOPPABLE)
+ SStgui.update_uis(src)
+
+ var/datum/db_query/query = SSdbcore.NewQuery(
+ "SELECT id, author, title, content FROM ss13_library WHERE id = :id",
+ list("id" = book_id))
+ if(!query.Execute() || !query.NextRow())
+ qdel(query)
+ return
+ var/obj/item/book/ordered_book = new(src.loc)
+ ordered_book.author = query.item[2]
+ ordered_book.title = query.item[3]
+ ordered_book.name = "Book: [query.item[3]]"
+ ordered_book.dat = query.item[4]
+ ordered_book.icon_state = "book[rand(1,16)]"
+ ordered_book.item_state = ordered_book.icon_state
+ qdel(query)
+ src.visible_message("\The [src]'s printer hums as it produces a book.")
+
+// Public Related Code
+/obj/machinery/librarycomp/public
+ name = "public library computer"
+ is_public = TRUE
diff --git a/code/modules/library/lib_items.dm b/code/modules/library/lib_items.dm
index 8d71b3380ca..47bbf60631d 100644
--- a/code/modules/library/lib_items.dm
+++ b/code/modules/library/lib_items.dm
@@ -183,6 +183,14 @@
update_icon()
+/*
+ * Barcode Scanner mode defines — used in book/attackby and barcodescanner/attack_self
+ */
+#define BARCODE_SCAN_BUFFER_ONLY 0
+#define BARCODE_SCAN_BUFFER_COMPUTER 1
+#define BARCODE_SCAN_CHECKIN 2
+#define BARCODE_SCAN_ADD_INVENTORY 3
+
/*
* Book
*/
@@ -273,35 +281,37 @@
return
else if(istype(attacking_item, /obj/item/barcodescanner))
var/obj/item/barcodescanner/scanner = attacking_item
- if(!scanner.computer)
+ var/obj/machinery/librarycomp/comp = locate(scanner.computer_ref)
+ if(!comp || QDELETED(comp))
to_chat(user, "[attacking_item]'s screen flashes: 'No associated computer found!'")
else
switch(scanner.mode)
- if(0)
- scanner.book = src
+ if(BARCODE_SCAN_BUFFER_ONLY)
+ scanner.book_ref = REF(src)
to_chat(user, "[attacking_item]'s screen flashes: 'Book stored in buffer.'")
- if(1)
- scanner.book = src
- scanner.computer.buffer_book = src.name
+ if(BARCODE_SCAN_BUFFER_COMPUTER)
+ scanner.book_ref = REF(src)
+ comp.buffer_book = src.name
to_chat(user, "[attacking_item]'s screen flashes: 'Book stored in buffer. Book title stored in associated computer buffer.'")
- if(2)
- scanner.book = src
- for(var/datum/borrowbook/b in scanner.computer.checkouts)
- if(b.bookname == src.name)
- scanner.computer.checkouts.Remove(b)
+ if(BARCODE_SCAN_CHECKIN)
+ scanner.book_ref = REF(src)
+ for(var/datum/borrowbook/checkout in comp.checkouts)
+ if(checkout.book_name == src.name)
+ comp.checkouts.Remove(checkout)
to_chat(user, "[attacking_item]'s screen flashes: 'Book stored in buffer. Book has been checked in.'")
return
to_chat(user, "[attacking_item]'s screen flashes: 'Book stored in buffer. No active check-out record found for current title.'")
- if(3)
- scanner.book = src
- for(var/obj/item/book in scanner.computer.inventory)
- if(book == src)
- to_chat(user, "[attacking_item]'s screen flashes: 'Book stored in buffer. Title already present in inventory, aborting to avoid duplicate entry.'")
- return
- scanner.computer.inventory.Add(src)
+ if(BARCODE_SCAN_ADD_INVENTORY)
+ scanner.book_ref = REF(src)
+ var/datum/weakref/src_wref = WEAKREF(src)
+ if(src_wref in comp.inventory)
+ to_chat(user, "[attacking_item]'s screen flashes: 'Book stored in buffer. Title already present in inventory, aborting to avoid duplicate entry.'")
+ return
+ comp.inventory.Add(src_wref)
to_chat(user, "[attacking_item]'s screen flashes: 'Book stored in buffer. Title added to general inventory.'")
else if(istype(attacking_item, /obj/item/material/knife) || attacking_item.tool_behaviour == TOOL_WIRECUTTER)
- if(carved) return
+ if(carved)
+ return
to_chat(user, SPAN_NOTICE("You begin to carve out [title]."))
if(attacking_item.use_tool(src, user, 30, volume = 50))
to_chat(user, SPAN_NOTICE("You carve out the pages from [title]! You didn't want to read it anyway."))
@@ -326,34 +336,40 @@
/obj/item/barcodescanner
name = "book scanner"
icon = 'icons/obj/library.dmi'
- icon_state ="scanner"
+ icon_state = "scanner"
throw_speed = 1
throw_range = 5
w_class = WEIGHT_CLASS_SMALL
- var/obj/machinery/librarycomp/computer // Associated computer - Modes 1 to 3 use this
- var/obj/item/book/book // Currently scanned book
- var/mode = 0 // 0 - Scan only, 1 - Scan and Set Buffer, 2 - Scan and Attempt to Check In, 3 - Scan and Attempt to Add to Inventory
+ var/computer_ref // REF string of the associated library computer
+ var/book_ref // REF string of the currently scanned book
+ var/mode = BARCODE_SCAN_BUFFER_ONLY
-/obj/item/barcodescanner/attack_self(mob/user as mob)
+/obj/item/barcodescanner/attack_self(mob/user)
mode += 1
- if(mode > 3)
- mode = 0
+ if(mode > BARCODE_SCAN_ADD_INVENTORY)
+ mode = BARCODE_SCAN_BUFFER_ONLY
to_chat(user, "[src] Status Display:")
- var/modedesc
+ var/mode_desc
switch(mode)
- if(0)
- modedesc = "Scan book to local buffer."
- if(1)
- modedesc = "Scan book to local buffer and set associated computer buffer to match."
- if(2)
- modedesc = "Scan book to local buffer, attempt to check in scanned book."
- if(3)
- modedesc = "Scan book to local buffer, attempt to add book to general inventory."
+ if(BARCODE_SCAN_BUFFER_ONLY)
+ mode_desc = "Scan book to local buffer."
+ if(BARCODE_SCAN_BUFFER_COMPUTER)
+ mode_desc = "Scan book to local buffer and set associated computer buffer to match."
+ if(BARCODE_SCAN_CHECKIN)
+ mode_desc = "Scan book to local buffer, attempt to check in scanned book."
+ if(BARCODE_SCAN_ADD_INVENTORY)
+ mode_desc = "Scan book to local buffer, attempt to add book to general inventory."
else
- modedesc = "ERROR"
- to_chat(user, " - Mode [mode] : [modedesc]")
- if(src.computer)
+ mode_desc = "ERROR"
+ to_chat(user, " - Mode [mode] : [mode_desc]")
+ var/obj/machinery/librarycomp/comp = locate(src.computer_ref)
+ if(comp && !QDELETED(comp))
to_chat(user, SPAN_NOTICE("Computer has been associated with this unit."))
else
to_chat(user, SPAN_WARNING("No associated computer found. Only local scans will function properly."))
to_chat(user, "\n")
+
+#undef BARCODE_SCAN_BUFFER_ONLY
+#undef BARCODE_SCAN_BUFFER_COMPUTER
+#undef BARCODE_SCAN_CHECKIN
+#undef BARCODE_SCAN_ADD_INVENTORY
diff --git a/code/modules/library/lib_machines.dm b/code/modules/library/lib_machines.dm
deleted file mode 100644
index 88c05cad061..00000000000
--- a/code/modules/library/lib_machines.dm
+++ /dev/null
@@ -1,517 +0,0 @@
-/* Library Machines
- *
- * Contains:
- * * Borrowbook datum
- * * Library Public Computer
- * * Library Computer
- * * Library Scanner
- * * Book Binder
- */
-
-/*
- * Borrowbook datum
- */
-/datum/borrowbook // Datum used to keep track of who has borrowed what when and for how long.
- var/bookname
- var/mobname
- var/getdate
- var/duedate
-
-/*
- * Library Public Computer
- */
-/obj/machinery/librarypubliccomp
- name = "public library computer"
- desc = "A computer."
- icon = 'icons/obj/library.dmi'
- icon_state = "computer"
- anchored = TRUE
- density = TRUE
- var/screenstate = 0
- var/title
- var/category = "Any"
- var/author
- var/SQLquery
-
-/obj/machinery/librarypubliccomp/attack_hand(var/mob/user)
- usr.set_machine(src)
- var/dat = ""
- switch(screenstate)
- if(0)
- dat += {"
Search Settings
- Filter by Title: [title]
- Filter by Category: [category]
- Filter by Author: [author]
- \[Start Search\] "}
- if(1)
- if(!establish_db_connection(GLOB.dbcon))
- dat += "ERROR: Unable to contact External Archive. Please contact your system administrator for assistance. "
- else if(!SQLquery)
- dat += "ERROR: Malformed search request. Please contact your system administrator for assistance. "
- else
- dat += {"
"
- dat += "\[Go Back\] "
- user << browse(HTML_SKELETON_TITLE("Library Visitor", dat), "window=publiclibrary")
- onclose(user, "publiclibrary")
-
-/obj/machinery/librarypubliccomp/Topic(href, href_list)
- if(..())
- usr << browse(null, "window=publiclibrary")
- onclose(usr, "publiclibrary")
- return
-
- if(href_list["settitle"])
- var/newtitle = input("Enter a title to search for:") as text|null
- if(newtitle)
- title = sanitize(newtitle)
- else
- title = null
- title = sanitizeSQL(title)
- if(href_list["setcategory"])
- var/newcategory = input("Choose a category to search for:") in list("Any", "Fiction", "Non-Fiction", "Reference", "Religion")
- if(newcategory)
- category = sanitize(newcategory)
- else
- category = "Any"
- category = sanitizeSQL(category)
- if(href_list["setauthor"])
- var/newauthor = input("Enter an author to search for:") as text|null
- if(newauthor)
- author = sanitize(newauthor)
- else
- author = null
- author = sanitizeSQL(author)
- if(href_list["search"])
- SQLquery = "SELECT author, title, category, id FROM ss13_library WHERE "
- if(category == "Any")
- SQLquery += "author LIKE '%[author]%' AND title LIKE '%[title]%'"
- else
- SQLquery += "author LIKE '%[author]%' AND title LIKE '%[title]%' AND category='[category]'"
- screenstate = 1
-
- if(href_list["back"])
- screenstate = 0
-
- src.add_fingerprint(usr)
- src.updateUsrDialog()
- return
-
-/*
- * Library Computer
- */
-/obj/machinery/librarycomp
- name = "library computer"
- desc = "A computer."
- icon = 'icons/obj/library.dmi'
- icon_state = "computer"
- anchored = TRUE
- density = TRUE
- var/arcanecheckout = FALSE
- var/screenstate = 0 // 0: Main Menu - 1: Inventory - 2: Checked Out - 3: Check Out
- var/sortby = "author"
- var/buffer_book
- var/buffer_mob
- var/upload_category = "Fiction"
- var/list/checkouts = list()
- var/list/inventory = list()
- var/checkoutperiod = 5 // In minutes
- var/bibledelay = 0
- var/is_public = FALSE
- var/obj/machinery/libraryscanner/scanner // Book scanner that will be used when uploading books to the Archive
-
-/obj/machinery/librarycomp/attack_hand(var/mob/user)
- user.set_machine(src)
- var/dat = ""
- switch(screenstate)
- if(0)
- // Main Menu
- dat += "View Stock "
- dat += "View Checked Out Books "
- dat += "Check out a Book "
- dat += "Order From Library Database "
- if(!is_public)
- dat += "Upload New Title to Library Database "
- dat += "Print a Bible "
- if(emagged)
- dat += "7. Access the Forbidden Lore Vault "
- if(arcanecheckout)
- new /obj/item/book/tome(get_turf(src))
- to_chat(user, SPAN_WARNING("Your sanity barely endures the seconds spent in the vault's browsing window. The only thing to remind you of this when you stop browsing is a dusty old tome sitting on the desk. You don't really remember printing it."))
- user.visible_message(SPAN_NOTICE("\The [user] stares at the blank screen for a few moments, [user.get_pronoun("his")] expression frozen in fear. When [user.get_pronoun("he")] finally awakens from it, [user.get_pronoun("he")] looks a lot older."), range = 2)
- arcanecheckout = FALSE
- if(1)
- // Inventory
- dat += "
Inventory
"
- for(var/obj/item/book/b in inventory)
- dat += "[b.name] (Delete) "
- dat += "(<-- Return to Main Menu) "
- if(2)
- // Checked Out
- dat += "
"
- if(!establish_db_connection(GLOB.dbcon))
- dat += "ERROR: Unable to contact External Archive. Please contact your system administrator for assistance."
- else
- dat += {"(Order Book by ISBN)
"
- if(!scanner)
- for(var/obj/machinery/libraryscanner/S in range(9))
- if(S.anchored)
- scanner = S
- break
- if(!(scanner?.anchored))
- dat += "No scanner found within wireless network range. "
- else if(!scanner.cache)
- dat += "No data found in scanner memory. "
- else
- dat += {"Data marked for upload...
- Title: [scanner.cache.name] "}
- if(!scanner.cache.author)
- scanner.cache.author = "Anonymous"
- dat += {"Author: [scanner.cache.author]
- Category: [upload_category]
- \[Upload\] "}
- dat += "(<-- Return to Main Menu) "
- if(7)
- dat += {"
Accessing Forbidden Lore Vault v 1.3
- Are you absolutely sure you want to proceed? EldritchTomes Inc. takes no responsibilities for loss of sanity resulting from this action.