Library Update (#22120)

Updates the Library to TGUI and the new DBCore

The GUI was created using Claude Code

Public Library Computer
<img width="652" height="501" alt="image"
src="https://github.com/user-attachments/assets/eba1841a-5784-4fe4-b337-6a9e9503bb58"
/>


Library Management Computer:
Inventory Page:
<img width="750" height="605" alt="image"
src="https://github.com/user-attachments/assets/e4f38fcb-7299-4baf-a8a2-0974297a544c"
/>

Check Out Page with Book Picker active:
<img width="750" height="607" alt="image"
src="https://github.com/user-attachments/assets/e9189157-1617-4535-a053-942b17378614"
/>

Library Order Page with pagination:
<img width="747" height="598" alt="image"
src="https://github.com/user-attachments/assets/07d3abaf-5a16-4229-8ab8-e64e2f73de24"
/>

Emag action:
<img width="752" height="602" alt="image"
src="https://github.com/user-attachments/assets/285cd4bf-0ed3-4cf2-a9cb-4469e6466ad0"
/>

Book Uploader:
<img width="747" height="602" alt="image"
src="https://github.com/user-attachments/assets/b4b648ab-59e4-48f1-90a3-77e8440f01e4"
/>

And its orderable instantly:
<img width="750" height="602" alt="image"
src="https://github.com/user-attachments/assets/1c1c21fa-28fd-42fe-82b5-1c859cd74fdf"
/>

---------

Co-authored-by: Werner <Arrow768@users.noreply.github.com>
This commit is contained in:
Arrow768
2026-04-11 12:21:13 +00:00
committed by GitHub
co-authored by Werner
parent cfdcac3453
commit 0ccd900aac
11 changed files with 1733 additions and 556 deletions
+58
View File
@@ -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
+425
View File
@@ -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 = "<B>Library Inventory</B><BR><BR>"
var/printed = 0
for(var/datum/weakref/wref in inventory)
var/obj/item/book/book = wref.resolve()
if(book)
dat += "- [book.name]<BR>"
printed++
if(!printed)
dat += "No books in inventory.<BR>"
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 = "<B>Active Checkouts</B><BR><BR>"
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 += "- <B>[checkout.book_name]</B> ([checkout.mob_name]) — taken [taken_minutes] min ago, [status]<BR>"
else
dat += "No books currently checked out.<BR>"
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
+54 -38
View File
@@ -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
-517
View File
@@ -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 += {"<h2>Search Settings</h2><br>
<a href='byond://?src=[REF(src)];settitle=1'>Filter by Title: [title]</a><br>
<a href='byond://?src=[REF(src)];setcategory=1'>Filter by Category: [category]</a><br>
<a href='byond://?src=[REF(src)];setauthor=1'>Filter by Author: [author]</a><br>
<a href='byond://?src=[REF(src)];search=1'>\[Start Search\]</a><br>"}
if(1)
if(!establish_db_connection(GLOB.dbcon))
dat += "<font color=red><b>ERROR</b>: Unable to contact External Archive. Please contact your system administrator for assistance.</font><br>"
else if(!SQLquery)
dat += "<font color=red><b>ERROR</b>: Malformed search request. Please contact your system administrator for assistance.</font><br>"
else
dat += {"<table>
<tr><td>AUTHOR</td><td>TITLE</td><td>CATEGORY</td><td>SS<sup>13</sup>BN</td></tr>"}
var/DBQuery/query = GLOB.dbcon.NewQuery(SQLquery)
query.Execute()
while(query.NextRow())
var/author = query.item[1]
var/title = query.item[2]
var/category = query.item[3]
var/id = query.item[4]
dat += "<tr><td>[author]</td><td>[title]</td><td>[category]</td><td>[id]</td></tr>"
dat += "</table><br>"
dat += "<a href='byond://?src=[REF(src)];back=1'>\[Go Back\]</a><br>"
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 += "<a href='byond://?src=[REF(src)];switchscreen=1'>View Stock</a><br>"
dat += "<a href='byond://?src=[REF(src)];switchscreen=2'>View Checked Out Books</a><br>"
dat += "<a href='byond://?src=[REF(src)];switchscreen=3'>Check out a Book</a><br>"
dat += "<a href='byond://?src=[REF(src)];switchscreen=4'>Order From Library Database</a><br>"
if(!is_public)
dat += "<a href='byond://?src=[REF(src)];switchscreen=5'>Upload New Title to Library Database</a><br>"
dat += "<a href='byond://?src=[REF(src)];switchscreen=6'>Print a Bible</a><br>"
if(emagged)
dat += "<a href='byond://?src=[REF(src)];switchscreen=7'>7. Access the Forbidden Lore Vault</a><br>"
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 += "<H3>Inventory</H3><br>"
for(var/obj/item/book/b in inventory)
dat += "[b.name] <a href='byond://?src=[REF(src)];delbook=[REF(b)]'>(Delete)</a><br>"
dat += "<a href='byond://?src=[REF(src)];switchscreen=0'>(<-- Return to Main Menu)</a><br>"
if(2)
// Checked Out
dat += "<h3>Checked Out Books</h3><br>"
for(var/datum/borrowbook/b in checkouts)
var/timetaken = world.time - b.getdate
//timetaken *= 10
timetaken /= 600
timetaken = round(timetaken)
var/timedue = b.duedate - world.time
//timedue *= 10
timedue /= 600
if(timedue <= 0)
timedue = "<font color=red><b>(OVERDUE)</b> [timedue]</font>"
else
timedue = round(timedue)
dat += {"\"[b.bookname]\", Checked out to: [b.mobname]<br>--- Taken: [timetaken] minutes ago, Due: in [timedue] minutes<br>
<a href='byond://?src=[REF(src)];checkin=[REF(b)]'>(Check In)</a><br><br>"}
dat += "<a href='byond://?src=[REF(src)];switchscreen=0'>(<-- Return to Main Menu)</a><br>"
if(3)
// Check Out a Book
dat += {"<h3>Check Out a Book</h3><br>
Book: [src.buffer_book]
<a href='byond://?src=[REF(src)];editbook=1'>\[Edit\]</a><br>
Recipient: [src.buffer_mob]
<a href='byond://?src=[REF(src)];editmob=1'>\[Edit\]</a><br>
Checkout Date: [world.time / 600]<br>
Due Date: [(world.time + checkoutperiod) / 600]<br>
(Checkout Period: [checkoutperiod] minutes) (<a href='byond://?src=[REF(src)];increasetime=1'>+</a>/<a href='byond://?src=[REF(src)];decreasetime=1'>-</a>)<br>
<a href='byond://?src=[REF(src)];checkout=1'>(Commit Entry)</a><br>
<a href='byond://?src=[REF(src)];switchscreen=0'>(<-- Return to Main Menu)</a><br>"}
if(4)
dat += "<h3>External Archive</h3>"
if(!establish_db_connection(GLOB.dbcon))
dat += "<font color=red><b>ERROR</b>: Unable to contact External Archive. Please contact your system administrator for assistance.</font>"
else
dat += {"<a href='byond://?src=[REF(src)];orderbyid=1'>(Order Book by ISBN)</a><br><br>
<table>
<tr><td><a href='byond://?src=[REF(src)];sort=author'>AUTHOR</a></td><td><a href='byond://?src=[REF(src)];sort=title'>TITLE</a></td><td><a href='byond://?src=[REF(src)];sort=category'>CATEGORY</a></td><td></td></tr>"}
var/DBQuery/query = GLOB.dbcon.NewQuery("SELECT id, author, title, category FROM ss13_library ORDER BY [sortby]")
query.Execute()
while(query.NextRow())
var/id = query.item[1]
var/author = query.item[2]
var/title = query.item[3]
var/category = query.item[4]
dat += "<tr><td>[author]</td><td>[title]</td><td>[category]</td><td><a href='byond://?src=[REF(src)];targetid=[id]'>\[Order\]</a></td></tr>"
dat += "</table>"
dat += "<br><a href='byond://?src=[REF(src)];switchscreen=0'>(<-- Return to Main Menu)</a><br>"
if(5)
dat += "<H3>Upload a New Title</H3>"
if(!scanner)
for(var/obj/machinery/libraryscanner/S in range(9))
if(S.anchored)
scanner = S
break
if(!(scanner?.anchored))
dat += "<FONT color=red>No scanner found within wireless network range.</FONT><br>"
else if(!scanner.cache)
dat += "<FONT color=red>No data found in scanner memory.</FONT><br>"
else
dat += {"<TT>Data marked for upload...</TT><br>
<TT>Title: </TT>[scanner.cache.name]<br>"}
if(!scanner.cache.author)
scanner.cache.author = "Anonymous"
dat += {"<TT>Author: </TT><a href='byond://?src=[REF(src)];setauthor=1'>[scanner.cache.author]</a><br>
<TT>Category: </TT><a href='byond://?src=[REF(src)];setcategory=1'>[upload_category]</a><br>
<a href='byond://?src=[REF(src)];upload=1'>\[Upload\]</a><br>"}
dat += "<a href='byond://?src=[REF(src)];switchscreen=0'>(<-- Return to Main Menu)</a><br>"
if(7)
dat += {"<h3>Accessing Forbidden Lore Vault v 1.3</h3>
Are you absolutely sure you want to proceed? EldritchTomes Inc. takes no responsibilities for loss of sanity resulting from this action.<p>
<a href='byond://?src=[REF(src)];arccheckout=1'>Yes.</a><br>
<a href='byond://?src=[REF(src)];switchscreen=0'>No.</a><br>"}
//dat += "<a href='byond://?src=[REF(user)];mach_close=library'>Close</a><br><br>"
user << browse(HTML_SKELETON_TITLE(is_public ? "[SSatlas.current_map.station_name] Library" : "[SSatlas.current_map.station_name] Library Management", dat), "window=library")
onclose(user, "library")
/obj/machinery/librarycomp/emag_act(var/remaining_charges, var/mob/user)
if (src.density && !src.emagged)
src.emagged = 1
return 1
/obj/machinery/librarycomp/attackby(obj/item/attacking_item, mob/user)
if(istype(attacking_item, /obj/item/barcodescanner))
var/obj/item/barcodescanner/scanner = attacking_item
scanner.computer = src
to_chat(user, "[scanner]'s associated machine has been set to [src].")
for (var/mob/V in hearers(src))
V.show_message("[src] lets out a low, short blip.", 2)
else
..()
/obj/machinery/librarycomp/Topic(href, href_list)
if(..())
usr << browse(null, "window=library")
onclose(usr, "library")
return
if(href_list["switchscreen"])
switch(href_list["switchscreen"])
if("0")
screenstate = 0
if("1")
screenstate = 1
if("2")
screenstate = 2
if("3")
screenstate = 3
if("4")
screenstate = 4
if("5")
screenstate = 5
if("6")
if(!bibledelay)
var/obj/item/storage/bible/B = new /obj/item/storage/bible(src.loc)
B.verbs += /obj/item/storage/bible/verb/Set_Religion
var/randbook = "book" + pick("1", "2", "3", "4", "5", "6" , "7", "8", "9", "10", "11", "12", "13" , "14", "15" , "16")
B.icon_state = randbook
B.item_state = randbook
B.name = "religious book"
bibledelay = 1
spawn(60)
bibledelay = 0
else
for (var/mob/V in hearers(src))
V.show_message("<b>[src]</b>'s monitor flashes, \"Bible printer currently unavailable, please wait a moment.\"")
if("7")
screenstate = 7
if(href_list["arccheckout"])
if(src.emagged)
src.arcanecheckout = 1
src.screenstate = 0
if(href_list["increasetime"])
checkoutperiod += 1
if(href_list["decreasetime"])
checkoutperiod -= 1
if(checkoutperiod < 1)
checkoutperiod = 1
if(href_list["editbook"])
buffer_book = sanitizeSafe(input("Enter the book's title:") as text|null)
if(href_list["editmob"])
buffer_mob = sanitize(input("Enter the recipient's name:") as text|null, MAX_NAME_LEN)
if(href_list["checkout"])
var/datum/borrowbook/b = new /datum/borrowbook
b.bookname = sanitizeSafe(buffer_book)
b.mobname = sanitize(buffer_mob)
b.getdate = world.time
b.duedate = world.time + (checkoutperiod * 600)
checkouts.Add(b)
if(href_list["checkin"])
var/datum/borrowbook/b = locate(href_list["checkin"])
checkouts.Remove(b)
if(href_list["delbook"])
var/obj/item/book/b = locate(href_list["delbook"])
inventory.Remove(b)
if(href_list["setauthor"])
var/newauthor = sanitize(input("Enter the author's name: ") as text|null)
if(newauthor)
scanner.cache.author = newauthor
if(href_list["setcategory"])
var/newcategory = input("Choose a category: ") in list("Fiction", "Non-Fiction", "Reference", "Religion")
if(newcategory)
upload_category = newcategory
if(href_list["upload"])
if(scanner?.anchored)
if(scanner.cache)
var/choice = input("Are you certain you wish to upload this title to the Archive?") in list("Confirm", "Abort")
if(choice == "Confirm")
if(scanner.cache.unique)
alert("This book has been rejected from the database. Aborting!")
else
if(!establish_db_connection(GLOB.dbcon))
alert("Connection to Archive has been severed. Aborting.")
else
var/sqltitle = sanitizeSQL(scanner.cache.name)
var/sqlauthor = sanitizeSQL(scanner.cache.author)
var/sqlcontent = sanitizeSQL(scanner.cache.dat)
var/sqlcategory = sanitizeSQL(upload_category)
var/sqlckey = sanitizeSQL(ckey(usr.client.ckey))
var/DBQuery/query = GLOB.dbcon.NewQuery("INSERT INTO ss13_library (author, title, content, category, uploadtime, uploader) VALUES ('[sqlauthor]', '[sqltitle]', '[sqlcontent]', '[sqlcategory]', NOW(), '[sqlckey]')")
if(!query.Execute())
to_chat(usr, query.ErrorMsg())
else
log_and_message_admins("has uploaded the book titled [scanner.cache.name], [length(scanner.cache.dat)] signs")
log_game("[usr.name]/[usr.key] has uploaded the book titled [scanner.cache.name], [length(scanner.cache.dat)] signs")
alert("Upload Complete.")
if(href_list["targetid"])
var/sqlid = sanitizeSQL(href_list["targetid"])
if(!establish_db_connection(GLOB.dbcon))
alert("Connection to Archive has been severed. Aborting.")
if(bibledelay)
for (var/mob/V in hearers(src))
V.show_message("<b>[src]</b>'s monitor flashes, \"Printer unavailable. Please allow a short time before attempting to print.\"")
else
bibledelay = 1
spawn(60)
bibledelay = 0
var/DBQuery/query = GLOB.dbcon.NewQuery("SELECT * FROM ss13_library WHERE id=[sqlid]")
query.Execute()
while(query.NextRow())
var/author = query.item[2]
var/title = query.item[3]
var/content = query.item[4]
var/obj/item/book/B = new(src.loc)
B.name = "Book: [title]"
B.title = title
B.author = author
B.dat = content
B.icon_state = "book[rand(1,16)]"
B.item_state = B.icon_state
src.visible_message("\The [src]\s printer hums as it produces a book.")
break
if(href_list["orderbyid"])
var/orderid = input("Enter your order:") as num|null
if(orderid)
if(isnum(orderid))
var/nhref = "src=[REF(src)];targetid=[orderid]"
spawn() src.Topic(nhref, params2list(nhref), src)
if(href_list["sort"] in list("author", "title", "category"))
sortby = href_list["sort"]
src.add_fingerprint(usr)
src.updateUsrDialog()
return
// Public Related Code
/obj/machinery/librarycomp/public
name = "public library computer"
is_public = TRUE
/*
* Library Scanner
*/
/obj/machinery/libraryscanner
name = "book scanner"
desc = "A machine that scans books for upload to the library database."
icon = 'icons/obj/library.dmi'
icon_state = "bigscanner"
var/insert_anim = "bigscanner1"
anchored = TRUE
density = TRUE
var/obj/item/book/cache // Last scanned book
/obj/machinery/libraryscanner/attackby(obj/item/attacking_item, mob/user)
if(istype(attacking_item, /obj/item/book))
if(!anchored)
to_chat(user, SPAN_WARNING("\The [src] must be secured to the floor first!"))
return
user.drop_from_inventory(attacking_item,src)
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
/obj/machinery/libraryscanner/attack_hand(var/mob/user)
usr.set_machine(src)
var/dat = ""
if(cache)
dat += "<FONT color=#005500>Data stored in memory.</FONT><br>"
else
dat += "No data stored in memory.<br>"
dat += "<a href='byond://?src=[REF(src)];scan=1'>\[Scan\]</a>"
if(cache)
dat += " <a href='byond://?src=[REF(src)];clear=1'>\[Clear Memory\]</a><br><br><a href='byond://?src=[REF(src)];eject=1'>\[Remove Book\]</a>"
else
dat += "<br>"
user << browse(HTML_SKELETON_TITLE("Scanner Control Interface", dat), "window=scanner")
onclose(user, "scanner")
/obj/machinery/libraryscanner/Topic(href, href_list)
if(..())
usr << browse(null, "window=scanner")
onclose(usr, "scanner")
return
if(href_list["scan"])
flick(insert_anim, src)
playsound(loc, 'sound/items/bureaucracy/scan.ogg', 75, 1)
for(var/obj/item/book/B in contents)
cache = B
break
if(href_list["clear"])
cache = null
if(href_list["eject"])
for(var/obj/item/book/B in contents)
B.forceMove(src.loc)
src.add_fingerprint(usr)
src.updateUsrDialog()
return
/*
* 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/T = 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(T, 'sound/items/bureaucracy/binder.ogg', 75, 1)
binding = TRUE
sleep(rand(200,400))
binding = FALSE
if(!anchored)
visible_message(SPAN_WARNING("\The [src] buzzes and flashes an error light."))
paper.forceMove(T)
return
visible_message(SPAN_NOTICE("\The [src] whirs as it prints and binds a new book."))
playsound(T, 'sound/items/bureaucracy/print.ogg', 75, 1)
var/obj/item/book/b = new(T)
b.dat = paper.info
b.name = "blank book"
b.icon_state = "book[rand(1,7)]"
b.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
@@ -0,0 +1,97 @@
/*
* 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/list/search_results = list()
var/search_title = ""
var/search_author = ""
var/search_category = "Any"
var/db_loading = FALSE
var/db_error = FALSE
/obj/machinery/librarypubliccomp/attack_hand(var/mob/user)
. = ..()
if(.)
return TRUE
ui_interact(user)
/obj/machinery/librarypubliccomp/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "LibraryPublicComputer", "Public Library Terminal", 650, 500)
ui.open()
/obj/machinery/librarypubliccomp/ui_data(mob/user)
var/list/data = list()
data["search_title"] = search_title
data["search_author"] = search_author
data["search_category"] = search_category
data["db_loading"] = db_loading
data["db_error"] = db_error
data["search_results"] = search_results
return data
/obj/machinery/librarypubliccomp/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
. = ..()
if(.)
return
add_fingerprint(usr)
switch(action)
if("search")
if(!db_loading)
search_title = sanitize(params["title"])
search_author = sanitize(params["author"])
var/new_category = params["category"]
if(new_category in list("Any", "Fiction", "Non-Fiction", "Reference", "Religion"))
search_category = new_category
INVOKE_ASYNC(src, PROC_REF(async_search))
. = TRUE
/obj/machinery/librarypubliccomp/proc/async_search()
db_loading = TRUE
db_error = FALSE
search_results = list()
SStgui.update_uis(src)
var/sql
var/list/query_args
if(search_category != "Any")
sql = "SELECT author, title, category, id FROM ss13_library WHERE author LIKE :author AND title LIKE :title AND category = :category"
query_args = list(
"title" = "%[search_title]%",
"author" = "%[search_author]%",
"category" = search_category
)
else
sql = "SELECT author, title, category, id FROM ss13_library WHERE author LIKE :author AND title LIKE :title"
query_args = list(
"title" = "%[search_title]%",
"author" = "%[search_author]%"
)
var/datum/db_query/query = SSdbcore.NewQuery(sql, query_args)
if(!query.Execute())
db_error = TRUE
qdel(query)
db_loading = FALSE
SStgui.update_uis(src)
return
var/list/results = list()
while(query.NextRow())
results += list(list(
"author" = query.item[1],
"title" = query.item[2],
"category" = query.item[3],
"id" = text2num(query.item[4])
))
qdel(query)
search_results = results
db_loading = FALSE
SStgui.update_uis(src)
+74
View File
@@ -0,0 +1,74 @@
/*
* Library Scanner
*/
/obj/machinery/libraryscanner
name = "book scanner"
desc = "A machine that scans books for upload to the library database."
icon = 'icons/obj/library.dmi'
icon_state = "bigscanner"
var/insert_animation = "bigscanner1"
anchored = TRUE
density = TRUE
var/obj/item/book/cache // Last scanned book
/obj/machinery/libraryscanner/attackby(obj/item/attacking_item, mob/user)
if(istype(attacking_item, /obj/item/book))
if(!anchored)
to_chat(user, SPAN_WARNING("\The [src] must be secured to the floor first!"))
return
user.drop_from_inventory(attacking_item, src)
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
/obj/machinery/libraryscanner/attack_hand(var/mob/user)
. = ..()
if(.)
return TRUE
ui_interact(user)
/obj/machinery/libraryscanner/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "LibraryScanner", "Book Scanner", 400, 250)
ui.open()
/obj/machinery/libraryscanner/ui_data(mob/user)
var/list/data = list()
data["has_book"] = !!cache
data["book_title"] = cache ? cache.name : null
data["book_author"] = cache ? cache.author : null
data["is_anchored"] = anchored
return data
/obj/machinery/libraryscanner/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
. = ..()
if(.)
return
add_fingerprint(usr)
switch(action)
if("scan")
flick(insert_animation, src)
playsound(loc, 'sound/items/bureaucracy/scan.ogg', 75, 1)
for(var/obj/item/book/book in contents)
cache = book
break
. = TRUE
if("clear")
cache = null
. = TRUE
if("eject")
for(var/obj/item/book/book in contents)
book.forceMove(src.loc)
cache = null
. = TRUE