mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2025-12-10 09:42:29 +00:00
* Icons folder cleaning wave two * Merge conflict resolution * Modular path hell * hmm * Update 2022-10.yml * Another modular thing --------- Co-authored-by: YesterdaysPromise <122572637+YesterdaysPromise@users.noreply.github.com> Co-authored-by: Giz <vinylspiders@gmail.com>
842 lines
29 KiB
Plaintext
842 lines
29 KiB
Plaintext
/* Library Machines
|
|
*
|
|
* Contains:
|
|
* Borrowbook datum
|
|
* Library Public Computer
|
|
* Book Info datum
|
|
* Library Computer
|
|
* Library Scanner
|
|
* Book Binder
|
|
*/
|
|
|
|
GLOBAL_VAR_INIT(library_table_modified, 0)
|
|
|
|
/// Increments every time WE update the library db table, causes all existing consoles to repull when they next check
|
|
/proc/library_updated()
|
|
GLOB.library_table_modified = (GLOB.library_table_modified + 1) % (SHORT_REAL_LIMIT - 1)
|
|
|
|
/*
|
|
* Library Public Computer
|
|
*/
|
|
/obj/machinery/computer/libraryconsole
|
|
name = "library visitor console"
|
|
icon_state = "oldcomp"
|
|
icon_screen = "library"
|
|
icon_keyboard = null
|
|
circuit = /obj/item/circuitboard/computer/libraryconsole
|
|
desc = "Checked out books MUST be returned on time."
|
|
anchored_tabletop_offset = 8
|
|
///The current book id we're searching for
|
|
var/book_id = null
|
|
///The current title we're searching for
|
|
var/title = ""
|
|
///The category we're searching for
|
|
var/category = ""
|
|
///The author we're searching for
|
|
var/author = ""
|
|
///The results of our last query
|
|
var/list/page_content = list()
|
|
///The the total pages last we checked
|
|
var/page_count = 0
|
|
///The page of our current query
|
|
var/search_page = 0
|
|
///Can we connect to the db?
|
|
var/can_connect = FALSE
|
|
///A hash of the last search we did, prevents spam in a different way then the cooldown
|
|
var/last_search_hash = ""
|
|
///Have the search params changed at all since the last search?
|
|
var/params_changed = FALSE
|
|
///Are we currently sending a db request for books?
|
|
var/sending_request = FALSE
|
|
///Prevents spamming requests, acts as a second layer of protection against spam
|
|
COOLDOWN_DECLARE(db_request_cooldown)
|
|
///Interface name for the ui_interact call for different subtypes.
|
|
var/interface_type = "LibraryVisitor"
|
|
|
|
/obj/machinery/computer/libraryconsole/Initialize(mapload)
|
|
. = ..()
|
|
category = DEFAULT_SEARCH_CATAGORY
|
|
INVOKE_ASYNC(src, PROC_REF(update_db_info))
|
|
|
|
/obj/machinery/computer/libraryconsole/ui_interact(mob/user, datum/tgui/ui)
|
|
. = ..()
|
|
ui = SStgui.try_update_ui(user, src, ui)
|
|
if(!ui)
|
|
ui = new(user, src, interface_type)
|
|
ui.open()
|
|
|
|
/obj/machinery/computer/libraryconsole/ui_data(mob/user)
|
|
var/list/data = list()
|
|
data["can_db_request"] = can_db_request()
|
|
data["search_categories"] = SSlibrary.search_categories
|
|
data["category"] = category
|
|
data["author"] = author
|
|
data["title"] = title
|
|
data["book_id"] = book_id
|
|
data["page_count"] = page_count + 1 //Increase these by one so it looks like we're not indexing at 0
|
|
data["our_page"] = search_page + 1
|
|
data["pages"] = page_content
|
|
data["can_connect"] = can_connect
|
|
data["params_changed"] = params_changed
|
|
return data
|
|
|
|
/obj/machinery/computer/libraryconsole/ui_act(action, params)
|
|
. = ..()
|
|
if(.)
|
|
return
|
|
switch(action)
|
|
if("set_search_id")
|
|
var/newid = text2num(params["id"])
|
|
if(newid != book_id)
|
|
params_changed = TRUE
|
|
book_id = newid
|
|
return TRUE
|
|
if("set_search_title")
|
|
var/newtitle = params["title"]
|
|
if(newtitle != title)
|
|
params_changed = TRUE
|
|
title = newtitle
|
|
return TRUE
|
|
if("set_search_category")
|
|
var/newcategory = params["category"]
|
|
if(!(newcategory in SSlibrary.search_categories)) //Nice try
|
|
newcategory = DEFAULT_SEARCH_CATAGORY
|
|
if(newcategory != category)
|
|
params_changed = TRUE
|
|
category = newcategory
|
|
return TRUE
|
|
if("set_search_author")
|
|
var/newauthor = params["author"]
|
|
if(newauthor != author)
|
|
params_changed = TRUE
|
|
author = newauthor
|
|
return TRUE
|
|
if("search")
|
|
if(!prevent_db_spam())
|
|
say("Database cables refreshing. Please wait a moment.")
|
|
return
|
|
INVOKE_ASYNC(src, PROC_REF(update_db_info))
|
|
return TRUE
|
|
if("switch_page")
|
|
if(!prevent_db_spam())
|
|
say("Database cables refreshing. Please wait a moment.")
|
|
return
|
|
search_page = sanitize_page_input(params["page"], search_page, page_count)
|
|
INVOKE_ASYNC(src, PROC_REF(update_db_info))
|
|
return TRUE
|
|
if("clear_data") //The cap just walked in on your browsing, quick! delete it!
|
|
if(!prevent_db_spam())
|
|
say("Database cables refreshing. Please wait a moment.")
|
|
return
|
|
title = initial(title)
|
|
author = initial(author)
|
|
category = DEFAULT_SEARCH_CATAGORY
|
|
search_page = 0
|
|
INVOKE_ASYNC(src, PROC_REF(update_db_info))
|
|
return TRUE
|
|
|
|
///Checks if the machine is alloweed to make another db request yet. TRUE if so, FALSE otherwise
|
|
/obj/machinery/computer/libraryconsole/proc/prevent_db_spam()
|
|
var/allowed = can_db_request()
|
|
if(!allowed)
|
|
return FALSE
|
|
COOLDOWN_START(src, db_request_cooldown, 1 SECONDS)
|
|
return TRUE
|
|
|
|
/obj/machinery/computer/libraryconsole/proc/can_db_request()
|
|
if(sending_request) //Absolutely not
|
|
return FALSE
|
|
if(!COOLDOWN_FINISHED(src, db_request_cooldown))
|
|
return FALSE
|
|
return TRUE
|
|
|
|
///Returns a santized page input, so converted from num/text to num, and properly maxed
|
|
/obj/machinery/computer/libraryconsole/proc/sanitize_page_input(input, default, max)
|
|
input = convert_ambiguous_input(input, default + 1) // + 1 to invert the below reasons
|
|
//We expect the search page to be one greater then it should be, because we're lying about indexing at 1
|
|
return clamp(input - 1, 0, max)
|
|
|
|
///Takes input that could either be a number, or a string that represents a number and returns a number
|
|
/obj/machinery/computer/libraryconsole/proc/convert_ambiguous_input(input, default)
|
|
if(isnum(input))
|
|
return input
|
|
if(!istext(input))
|
|
return default
|
|
var/hidden_number = text2num(input)
|
|
if(!isnum(hidden_number))
|
|
return default
|
|
return hidden_number
|
|
|
|
/obj/machinery/computer/libraryconsole/proc/update_db_info()
|
|
if(!has_anything_changed()) //You're not allowed to make the same search twice, waste of resources
|
|
return
|
|
if (!SSdbcore.Connect())
|
|
can_connect = FALSE
|
|
page_count = 0
|
|
page_content = list()
|
|
return
|
|
can_connect = TRUE
|
|
params_changed = FALSE
|
|
last_search_hash = hash_search_info()
|
|
|
|
update_page_count()
|
|
update_page_contents()
|
|
SStgui.update_uis(src) //We need to do this because we sleep here, so we've gotta update manually
|
|
|
|
//Returns true if there's been an update worth refreshing our pages for, false otherwise
|
|
/obj/machinery/computer/libraryconsole/proc/has_anything_changed()
|
|
if(last_search_hash == hash_search_info())
|
|
return FALSE
|
|
return TRUE
|
|
|
|
/obj/machinery/computer/libraryconsole/proc/hash_search_info()
|
|
return "[GLOB.library_table_modified]-[book_id]-[title]-[author]-[category]-[search_page]-[page_count]"
|
|
|
|
/obj/machinery/computer/libraryconsole/proc/update_page_contents()
|
|
if(sending_request) //Final defense against nerds spamming db requests
|
|
return
|
|
sending_request = TRUE
|
|
search_page = clamp(search_page, 0, page_count)
|
|
var/datum/db_query/query_library_list_books = SSdbcore.NewQuery({"
|
|
SELECT author, title, category, id
|
|
FROM [format_table_name("library")]
|
|
WHERE isnull(deleted)
|
|
AND author LIKE CONCAT('%',:author,'%')
|
|
AND title LIKE CONCAT('%',:title,'%')
|
|
AND (:category = 'Any' OR category = :category)
|
|
[book_id ? "AND id LIKE CONCAT('%', :book_id, '%')" : ""]
|
|
ORDER BY id DESC
|
|
LIMIT :skip, :take
|
|
"}, list("author" = author, "title" = title, "book_id" = book_id, "category" = category, "skip" = BOOKS_PER_PAGE * search_page, "take" = BOOKS_PER_PAGE))
|
|
|
|
var/query_succeeded = query_library_list_books.Execute()
|
|
sending_request = FALSE
|
|
page_content.Cut()
|
|
if(!query_succeeded)
|
|
qdel(query_library_list_books)
|
|
return
|
|
while(query_library_list_books.NextRow())
|
|
page_content += list(list(
|
|
"author" = html_decode(query_library_list_books.item[1]),
|
|
"title" = html_decode(query_library_list_books.item[2]),
|
|
"category" = query_library_list_books.item[3],
|
|
"id" = query_library_list_books.item[4]
|
|
))
|
|
qdel(query_library_list_books)
|
|
|
|
/obj/machinery/computer/libraryconsole/proc/update_page_count()
|
|
var/bookcount = 0
|
|
var/datum/db_query/query_library_count_books = SSdbcore.NewQuery({"
|
|
SELECT COUNT(id) FROM [format_table_name("library")]
|
|
WHERE isnull(deleted)
|
|
AND author LIKE CONCAT('%',:author,'%')
|
|
AND title LIKE CONCAT('%',:title,'%')
|
|
AND (:category = 'Any' OR category = :category)
|
|
[book_id ? "AND id LIKE CONCAT('%', :book_id, '%')" : ""]
|
|
"}, list("author" = author, "title" = title, "book_id" = book_id, "category" = category))
|
|
|
|
if(!query_library_count_books.warn_execute())
|
|
qdel(query_library_count_books)
|
|
return
|
|
if(query_library_count_books.NextRow())
|
|
bookcount = text2num(query_library_count_books.item[1])
|
|
qdel(query_library_count_books)
|
|
|
|
page_count = round(max(bookcount - 1, 0) / BOOKS_PER_PAGE) //This is just floor()
|
|
search_page = clamp(search_page, 0, page_count)
|
|
|
|
/*
|
|
* Borrowbook datum
|
|
*/
|
|
/datum/borrowbook // Datum used to keep track of who has borrowed what when and for how long.
|
|
var/datum/book_info/book_data
|
|
var/loanedto
|
|
var/checkout
|
|
var/duedate
|
|
|
|
#define PRINTER_COOLDOWN (6 SECONDS)
|
|
#define NEWSCASTER_COOLDOWN (10 SECONDS)
|
|
#define LIBRARY_NEWSFEED "Nanotrasen Book Club"
|
|
//The different states the computer can be in, only send the info we need yeah?
|
|
#define LIBRARY_INVENTORY 1
|
|
#define LIBRARY_CHECKOUT 2
|
|
#define LIBRARY_ARCHIVE 3
|
|
#define LIBRARY_UPLOAD 4
|
|
#define LIBRARY_PRINT 5
|
|
#define LIBRARY_TOP_SNEAKY 6
|
|
#define MIN_LIBRARY LIBRARY_INVENTORY
|
|
#define MAX_LIBRARY LIBRARY_TOP_SNEAKY
|
|
|
|
/*
|
|
* Library Computer
|
|
* After 860 days, it's finally a buildable computer.
|
|
*/
|
|
// TODO: Make this an actual /obj/machinery/computer that can be crafted from circuit boards and such
|
|
// It is August 22nd, 2012... This TODO has already been here for months.. I wonder how long it'll last before someone does something about it.
|
|
// It's December 25th, 2014, and this is STILL here, and it's STILL relevant. Kill me
|
|
/obj/machinery/computer/libraryconsole/bookmanagement
|
|
name = "book inventory management console"
|
|
desc = "Librarian's command station."
|
|
verb_say = "beeps"
|
|
verb_ask = "beeps"
|
|
verb_exclaim = "beeps"
|
|
pass_flags = PASSTABLE
|
|
|
|
icon_state = "oldcomp"
|
|
icon_screen = "library"
|
|
icon_keyboard = null
|
|
circuit = /obj/item/circuitboard/computer/libraryconsole
|
|
interface_type = "LibraryConsole"
|
|
///Can spawn secret lore item
|
|
var/can_spawn_lore = TRUE
|
|
///The screen we're currently on, sent to the ui
|
|
var/screen_state = LIBRARY_INVENTORY
|
|
///Should we show the buttons required for changing screens?
|
|
var/show_dropdown = TRUE
|
|
///List of checked out books, /datum/borrowbook
|
|
var/list/checkouts = list()
|
|
///The current max amount of checkout pages allowed
|
|
var/checkout_page_count = 0
|
|
///The current page we're on in the checkout listing
|
|
var/checkout_page = 0
|
|
///List of book info datums to display to the user as our "inventory"
|
|
var/list/inventory = list()
|
|
///The current max amount of inventory pages allowed
|
|
var/inventory_page_count = 0
|
|
///The current page we're on in the inventory
|
|
var/inventory_page = 0
|
|
///Should we load our inventory from the bookselves in our area?
|
|
var/dynamic_inv_load = FALSE
|
|
///Book scanner that will be used when uploading books to the Archive
|
|
var/datum/weakref/scanner
|
|
///Our cooldown on using the printer
|
|
COOLDOWN_DECLARE(printer_cooldown)
|
|
///Our cooldown on publishing books to the newscaster's "book club" channel
|
|
COOLDOWN_DECLARE(newscaster_cooldown)
|
|
|
|
/obj/machinery/computer/libraryconsole/bookmanagement/Initialize(mapload)
|
|
. = ..()
|
|
if(mapload)
|
|
dynamic_inv_load = TRUE //Only load in stuff if we were placed during mapload
|
|
|
|
/obj/machinery/computer/libraryconsole/bookmanagement/ui_static_data(mob/user)
|
|
var/list/data = list()
|
|
data["inventory"] = list()
|
|
var/inventory_len = length(inventory)
|
|
if(inventory_len)
|
|
for(var/id in ((INVENTORY_PER_PAGE * inventory_page) + 1) to min(INVENTORY_PER_PAGE * (inventory_page + 1), inventory_len))
|
|
var/book_ref = inventory[id]
|
|
var/datum/book_info/info = inventory[book_ref]
|
|
data["inventory"] += list(list(
|
|
"id" = id,
|
|
"ref" = book_ref,
|
|
"title" = info.get_title(),
|
|
"author" = info.get_author(),
|
|
))
|
|
data["has_inventory"] = !!inventory_len
|
|
data["inventory_page"] = inventory_page + 1
|
|
data["inventory_page_count"] = inventory_page_count + 1
|
|
return data
|
|
|
|
/obj/machinery/computer/libraryconsole/bookmanagement/ui_data(mob/user)
|
|
var/list/data = list()
|
|
data["can_db_request"] = can_db_request()
|
|
data["screen_state"] = screen_state
|
|
data["show_dropdown"] = show_dropdown
|
|
data["display_lore"] = (obj_flags & EMAGGED && can_spawn_lore)
|
|
if(dynamic_inv_load) //Time to load those area books in
|
|
dynamic_inv_load = FALSE
|
|
load_nearby_books()
|
|
|
|
switch(screen_state)
|
|
if(LIBRARY_CHECKOUT)
|
|
data["checkouts"] = list()
|
|
var/checkout_len = length(checkouts)
|
|
if(checkout_len)
|
|
for(var/id in ((CHECKOUTS_PER_PAGE * checkout_page) + 1) to min(CHECKOUTS_PER_PAGE * (checkout_page + 1), checkout_len))
|
|
var/checkout_ref = checkouts[id]
|
|
var/datum/borrowbook/loan = checkouts[checkout_ref]
|
|
var/timedue = (loan.duedate - world.time) / (1 MINUTES)
|
|
timedue = max(round(timedue, 0.1), 0)
|
|
data["checkouts"] += list(list(
|
|
"id" = id,
|
|
"ref" = checkout_ref,
|
|
"borrower" = loan.loanedto || "N/A",
|
|
"overdue" = (timedue <= 0),
|
|
"due_in_minutes" = timedue,
|
|
"title" = loan.book_data.get_title(),
|
|
"author" = loan.book_data.get_author(),
|
|
))
|
|
data["has_checkout"] = !!checkout_len
|
|
data["checkout_page"] = checkout_page + 1
|
|
data["checkout_page_count"] = checkout_page_count + 1
|
|
|
|
//Copypasta from the visitor console
|
|
if(LIBRARY_ARCHIVE)
|
|
data |= ..() //I am so sorry
|
|
|
|
if(LIBRARY_UPLOAD)
|
|
data["upload_categories"] = SSlibrary.upload_categories
|
|
data["default_category"] = DEFAULT_UPLOAD_CATAGORY
|
|
var/obj/machinery/libraryscanner/scan = get_scanner()
|
|
data["has_scanner"] = !!(scan)
|
|
data["has_cache"] = !!(scan?.cache)
|
|
|
|
data["cooldown_string"] = "[DisplayTimeText(COOLDOWN_TIMELEFT(src, newscaster_cooldown))]"
|
|
data["active_newscaster_cooldown"] = COOLDOWN_FINISHED(src, newscaster_cooldown)
|
|
|
|
if(scan?.cache)
|
|
data["cache_title"] = scan.cache.get_title()
|
|
data["cache_author"] = scan.cache.get_author()
|
|
data["cache_content"] = scan.cache.get_content()
|
|
|
|
if(LIBRARY_PRINT)
|
|
data["deity"] = GLOB.deity || DEFAULT_DEITY
|
|
var/reigion = GLOB.religion || DEFAULT_RELIGION
|
|
data["religion"] = reigion
|
|
var/bible_name = GLOB.bible_name
|
|
if(!bible_name)
|
|
bible_name = DEFAULT_BIBLE_REPLACE(reigion)
|
|
data["bible_name"] = bible_name
|
|
data["bible_sprite"] = "display-[GLOB.bible_icon_state || "bible"]"
|
|
data["posters"] = list()
|
|
for(var/poster_name in SSlibrary.printable_posters)
|
|
data["posters"] += poster_name
|
|
|
|
return data
|
|
|
|
/obj/machinery/computer/libraryconsole/bookmanagement/ui_assets(mob/user)
|
|
return list(get_asset_datum(/datum/asset/spritesheet/bibles))
|
|
|
|
/obj/machinery/computer/libraryconsole/bookmanagement/proc/load_nearby_books()
|
|
for(var/datum/book_info/book as anything in SSlibrary.get_area_books(get_area(src)))
|
|
inventory[ref(book)] = book
|
|
inventory_update()
|
|
|
|
/obj/machinery/computer/libraryconsole/bookmanagement/proc/get_scanner(viewrange)
|
|
if(scanner)
|
|
var/obj/machinery/libraryscanner/potential_scanner = scanner.resolve()
|
|
if(potential_scanner)
|
|
return potential_scanner
|
|
scanner = null
|
|
|
|
for(var/obj/machinery/libraryscanner/foundya in range(viewrange, get_turf(src)))
|
|
scanner = WEAKREF(foundya)
|
|
return foundya
|
|
|
|
/obj/machinery/computer/libraryconsole/bookmanagement/ui_act(action, params)
|
|
//The parent call takes care of stuff like searching, don't forget about that yeah?
|
|
. = ..()
|
|
if(.)
|
|
return
|
|
switch(action)
|
|
if("set_screen")
|
|
var/window = params["screen_index"]
|
|
set_screen_state(window)
|
|
return TRUE
|
|
if("toggle_dropdown")
|
|
show_dropdown = !show_dropdown
|
|
return TRUE
|
|
if("inventory_remove")
|
|
var/id = params["book_id"]
|
|
inventory -= id
|
|
inventory_update()
|
|
return TRUE
|
|
if("switch_inventory_page")
|
|
inventory_page = sanitize_page_input(params["page"], inventory_page, inventory_page_count)
|
|
inventory_update()
|
|
return TRUE
|
|
if("checkout")
|
|
var/list/available = list()
|
|
for(var/id in inventory)
|
|
var/datum/book_info/book_infos = inventory[id]
|
|
available[book_infos.title] = book_infos
|
|
var/book_name = params["book_name"]
|
|
if(QDELETED(src) || !book_name)
|
|
return
|
|
var/datum/book_info/book_info = available[book_name]
|
|
if(!istype(book_info))
|
|
return
|
|
var/datum/borrowbook/loan = new /datum/borrowbook
|
|
|
|
var/loan_to = copytext(sanitize(params["loaned_to"]), 1, MAX_NAME_LEN)
|
|
var/checkoutperiod = max(params["checkout_time"], 1)
|
|
|
|
loan.book_data = book_info.return_copy()
|
|
loan.loanedto = loan_to
|
|
loan.checkout = world.time
|
|
loan.duedate = world.time + (checkoutperiod MINUTES)
|
|
checkouts[ref(loan)] = loan
|
|
checkout_update()
|
|
return TRUE
|
|
if("checkin")
|
|
var/id = params["checked_out_id"]
|
|
checkouts -= id
|
|
checkout_update()
|
|
return TRUE
|
|
if("switch_checkout_page")
|
|
checkout_page = sanitize_page_input(params["page"], checkout_page, checkout_page_count)
|
|
checkout_update()
|
|
return TRUE
|
|
if("set_cache_title")
|
|
var/obj/machinery/libraryscanner/scan = get_scanner()
|
|
if(scan?.cache && params["title"])
|
|
scan.cache.set_title(params["title"])
|
|
return TRUE
|
|
if("set_cache_author")
|
|
var/obj/machinery/libraryscanner/scan = get_scanner()
|
|
if(scan?.cache && params["author"])
|
|
scan.cache.set_author(params["author"])
|
|
return TRUE
|
|
if("upload")
|
|
if(!prevent_db_spam())
|
|
say("Database cables refreshing. Please wait a moment.")
|
|
return
|
|
var/upload_category = params["category"]
|
|
if(!(upload_category in SSlibrary.upload_categories)) //Nice try
|
|
upload_category = DEFAULT_UPLOAD_CATAGORY
|
|
|
|
INVOKE_ASYNC(src, PROC_REF(upload_from_scanner), upload_category)
|
|
return TRUE
|
|
if("news_post")
|
|
// We grey out the button UI-side, but let's just be safe to guard against spammy spammers.
|
|
if(!COOLDOWN_FINISHED(src, newscaster_cooldown))
|
|
say("Not enough time has passed since the last news post. Please wait.")
|
|
return
|
|
if(!GLOB.news_network)
|
|
say("No news network found on station. Aborting.")
|
|
var/channelexists = FALSE
|
|
for(var/datum/feed_channel/feed in GLOB.news_network.network_channels)
|
|
if(feed.channel_name == LIBRARY_NEWSFEED)
|
|
channelexists = TRUE
|
|
break
|
|
if(!channelexists)
|
|
GLOB.news_network.create_feed_channel(LIBRARY_NEWSFEED, "Library", "The official station book club!", null)
|
|
|
|
var/obj/machinery/libraryscanner/scan = get_scanner()
|
|
if(!scan)
|
|
say("No nearby scanner detected. Aborting.")
|
|
return
|
|
GLOB.news_network.submit_article(scan.cache.content, "[scan.cache.author]: [scan.cache.title]", LIBRARY_NEWSFEED, null)
|
|
say("Upload complete. Your uploaded title is now available on station newscasters.")
|
|
COOLDOWN_START(src, newscaster_cooldown, NEWSCASTER_COOLDOWN)
|
|
return TRUE
|
|
if("print_book")
|
|
var/id = params["book_id"]
|
|
attempt_print(CALLBACK(src, PROC_REF(print_book), id))
|
|
return TRUE
|
|
if("print_bible")
|
|
attempt_print(CALLBACK(src, PROC_REF(print_bible)))
|
|
return TRUE
|
|
if("print_poster")
|
|
var/poster_name = params["poster_name"]
|
|
attempt_print(CALLBACK(src, PROC_REF(print_poster), poster_name))
|
|
return TRUE
|
|
if("lore_spawn")
|
|
if(obj_flags & EMAGGED && can_spawn_lore)
|
|
print_forbidden_lore(usr)
|
|
set_screen_state(MIN_LIBRARY)
|
|
return TRUE
|
|
if("lore_deny")
|
|
if(obj_flags & EMAGGED && can_spawn_lore)
|
|
shun_the_corp(usr)
|
|
set_screen_state(MIN_LIBRARY)
|
|
return TRUE
|
|
|
|
/obj/machinery/computer/libraryconsole/bookmanagement/attackby(obj/item/weapon, mob/user, params)
|
|
if(!istype(weapon, /obj/item/barcodescanner))
|
|
return ..()
|
|
var/obj/item/barcodescanner/scanner = weapon
|
|
if(scanner.computer_ref?.resolve() == src)
|
|
balloon_alert(user, "already connected!")
|
|
return
|
|
scanner.computer_ref = WEAKREF(src)
|
|
balloon_alert(user, "scanner connected")
|
|
audible_message(span_hear("[src] lets out a low, short blip."))
|
|
|
|
/obj/machinery/computer/libraryconsole/bookmanagement/emag_act(mob/user, obj/item/card/emag/emag_card)
|
|
if(!density || obj_flags & EMAGGED)
|
|
return FALSE
|
|
obj_flags |= EMAGGED
|
|
balloon_alert(user, "forbidden knowledge unlocked")
|
|
return TRUE
|
|
|
|
/obj/machinery/computer/libraryconsole/bookmanagement/proc/set_screen_state(new_state)
|
|
screen_state = clamp(new_state, MIN_LIBRARY, MAX_LIBRARY)
|
|
|
|
/obj/machinery/computer/libraryconsole/bookmanagement/proc/inventory_update()
|
|
inventory_page_count = round(max(length(inventory) - 1, 0) / INVENTORY_PER_PAGE) //This is just floor()
|
|
inventory_page = clamp(inventory_page, 0, inventory_page_count)
|
|
|
|
/obj/machinery/computer/libraryconsole/bookmanagement/proc/checkout_update()
|
|
checkout_page_count = round(max(length(checkouts) - 1, 0) / CHECKOUTS_PER_PAGE) //This is just floor()
|
|
checkout_page = clamp(checkout_page, 0, checkout_page_count)
|
|
|
|
/obj/machinery/computer/libraryconsole/bookmanagement/proc/print_forbidden_lore(mob/user)
|
|
can_spawn_lore = FALSE
|
|
new /obj/item/melee/cultblade/dagger(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 sinister dagger sitting on the desk. You don't even remember where it came from..."))
|
|
user.visible_message(span_warning("[user] stares at the blank screen for a few moments, [user.p_their()] expression frozen in fear. When [user.p_they()] finally awaken[user.p_s()] from it, [user.p_they()] look[user.p_s()] a lot older."), vision_distance = 2)
|
|
if(ishuman(user))
|
|
var/mob/living/carbon/human/fool = user
|
|
fool.age = clamp(fool.age + 10, AGE_MIN, AGE_MAX) //Fuck you
|
|
|
|
/obj/machinery/computer/libraryconsole/bookmanagement/proc/shun_the_corp(mob/user)
|
|
can_spawn_lore = FALSE
|
|
to_chat(user, span_warning("You click off the page in a rush, and the machine hums back to normal, the tab gone..."))
|
|
|
|
/obj/machinery/computer/libraryconsole/bookmanagement/proc/upload_from_scanner(upload_category)
|
|
var/obj/machinery/libraryscanner/scan = get_scanner()
|
|
if(!scan)
|
|
say("No nearby scanner detected.")
|
|
return
|
|
if(!scan.cache)
|
|
say("No cached book found. Aborting upload.")
|
|
return
|
|
if (!SSdbcore.Connect())
|
|
say("Connection to Archive has been severed. Aborting.")
|
|
return
|
|
var/datum/book_info/book = scan.cache
|
|
if(!book.title)
|
|
say("No title detected. Aborting")
|
|
return
|
|
if(!book.author)
|
|
say("No author detected. Aborting")
|
|
return
|
|
if(!book.content)
|
|
say("No content detected. Aborting")
|
|
return
|
|
var/msg = "has uploaded the book titled [book.title], [length(book.content)] signs"
|
|
var/datum/db_query/query_library_upload = SSdbcore.NewQuery({"
|
|
INSERT INTO [format_table_name("library")] (author, title, content, category, ckey, datetime, round_id_created)
|
|
VALUES (:author, :title, :content, :category, :ckey, Now(), :round_id)
|
|
"}, list("title" = book.title, "author" = book.author, "content" = book.content, "category" = upload_category, "ckey" = usr.ckey, "round_id" = GLOB.round_id))
|
|
if(!query_library_upload.Execute())
|
|
qdel(query_library_upload)
|
|
say("Database error encountered uploading to Archive")
|
|
return
|
|
usr.log_message(msg, LOG_GAME)
|
|
qdel(query_library_upload)
|
|
library_updated()
|
|
say("Upload Complete. Uploaded title will be available for printing in a moment")
|
|
update_db_info()
|
|
|
|
/// Call this proc to attempt a print. It will return false if the print failed, true otherwise, longside some ux
|
|
/// Accepts a callback to call when the print "finishes"
|
|
/obj/machinery/computer/libraryconsole/bookmanagement/proc/attempt_print(datum/callback/call_after)
|
|
if(!COOLDOWN_FINISHED(src, printer_cooldown))
|
|
say("Printer currently unavailable, please wait a moment.")
|
|
return FALSE
|
|
COOLDOWN_START(src, printer_cooldown, PRINTER_COOLDOWN)
|
|
playsound(src, 'sound/machines/printer.ogg', 50)
|
|
addtimer(call_after, 4.1 SECONDS)
|
|
return TRUE
|
|
|
|
/obj/machinery/computer/libraryconsole/bookmanagement/proc/print_bible()
|
|
var/obj/item/book/bible/holy_book = new(loc)
|
|
if(!GLOB.bible_icon_state || !GLOB.bible_inhand_icon_state)
|
|
return
|
|
holy_book.icon_state = GLOB.bible_icon_state
|
|
holy_book.inhand_icon_state = GLOB.bible_inhand_icon_state
|
|
holy_book.name = GLOB.bible_name
|
|
holy_book.deity_name = GLOB.deity
|
|
|
|
/obj/machinery/computer/libraryconsole/bookmanagement/proc/print_poster(poster_name)
|
|
var/poster_type = SSlibrary.printable_posters[poster_name]
|
|
if(!poster_type)
|
|
return
|
|
new /obj/item/poster(loc, new poster_type)
|
|
|
|
/obj/machinery/computer/libraryconsole/bookmanagement/proc/print_book(id)
|
|
if (!SSdbcore.Connect())
|
|
say("Connection to Archive has been severed. Aborting.")
|
|
can_connect = FALSE
|
|
return
|
|
|
|
var/datum/db_query/query_library_print = SSdbcore.NewQuery(
|
|
"SELECT * FROM [format_table_name("library")] WHERE id=:id AND isnull(deleted)",
|
|
list("id" = id)
|
|
)
|
|
if(!query_library_print.Execute())
|
|
qdel(query_library_print)
|
|
say("PRINTER ERROR! Failed to print document (0x0000000F)")
|
|
return
|
|
|
|
while(query_library_print.NextRow())
|
|
var/author = query_library_print.item[2]
|
|
var/title = query_library_print.item[3]
|
|
var/content = query_library_print.item[4]
|
|
if(!QDELETED(src))
|
|
var/obj/item/book/printed_book = new(get_turf(src))
|
|
printed_book.name = "Book: [title]"
|
|
printed_book.book_data = new()
|
|
var/datum/book_info/fill = printed_book.book_data
|
|
fill.set_title(title, trusted = TRUE)
|
|
fill.set_author(author, trusted = TRUE)
|
|
fill.set_content(content, trusted = TRUE)
|
|
printed_book.gen_random_icon_state()
|
|
visible_message(span_notice("[src]'s printer hums as it produces a completely bound book. How did it do that?"))
|
|
log_paper("[key_name(usr)] has printed \"[title]\" (id: [id]) by [author] from a book management console.")
|
|
break
|
|
qdel(query_library_print)
|
|
|
|
/*
|
|
* Library Scanner
|
|
*/
|
|
/obj/machinery/libraryscanner
|
|
name = "scanner control interface"
|
|
icon = 'icons/obj/service/library.dmi'
|
|
icon_state = "bigscanner"
|
|
desc = "It's an industrial strength book scanner. Perfect!"
|
|
density = TRUE
|
|
var/obj/item/book/held_book
|
|
///Our scanned-in book
|
|
var/datum/book_info/cache
|
|
|
|
/obj/machinery/libraryscanner/Destroy()
|
|
held_book = null
|
|
cache = null
|
|
return ..()
|
|
|
|
/obj/machinery/libraryscanner/attackby(obj/hitby, mob/user, params)
|
|
if(istype(hitby, /obj/item/book))
|
|
user.transferItemToLoc(hitby, src)
|
|
if(held_book)
|
|
user.put_in_hands(held_book)
|
|
held_book = hitby
|
|
playsound(src, 'sound/machines/eject.ogg', 70)
|
|
return TRUE
|
|
return ..()
|
|
|
|
/obj/machinery/libraryscanner/Exited(atom/movable/gone, direction)
|
|
. = ..()
|
|
if(gone == held_book)
|
|
held_book = null
|
|
|
|
/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")
|
|
ui.open()
|
|
|
|
/obj/machinery/libraryscanner/ui_data()
|
|
var/list/data = list()
|
|
var/list/cached_info = list()
|
|
data["has_book"] = !!held_book
|
|
data["has_cache"] = !!cache
|
|
if(cache)
|
|
cached_info["title"] = cache.get_title()
|
|
cached_info["author"] = cache.get_author()
|
|
data["book"] = cached_info
|
|
|
|
return data
|
|
|
|
/obj/machinery/libraryscanner/ui_act(action, params, datum/tgui/ui, datum/ui_state/state)
|
|
. = ..()
|
|
if(.)
|
|
return
|
|
switch(action)
|
|
if("scan")
|
|
if(cache?.compare(held_book.book_data))
|
|
say("This book is already in my internal cache")
|
|
return
|
|
cache = held_book.book_data.return_copy()
|
|
flick("bigscanner1", src)
|
|
playsound(src, 'sound/machines/scanner.ogg', vol = 50, vary = TRUE)
|
|
return TRUE
|
|
if("clear")
|
|
cache = null
|
|
return TRUE
|
|
if("eject")
|
|
ui.user.put_in_hands(held_book)
|
|
playsound(src, 'sound/machines/eject.ogg', 70)
|
|
return TRUE
|
|
|
|
/*
|
|
* Book binder
|
|
*/
|
|
/obj/machinery/bookbinder
|
|
name = "book binder"
|
|
icon = 'icons/obj/service/library.dmi'
|
|
icon_state = "binder"
|
|
desc = "Only intended for binding paper products."
|
|
density = TRUE
|
|
|
|
/// Are we currently binding a book?
|
|
var/busy = FALSE
|
|
|
|
/// Name of the author for the book, set by scanning your ID.
|
|
var/scanned_name
|
|
|
|
/obj/machinery/bookbinder/wrench_act(mob/living/user, obj/item/tool)
|
|
. = ..()
|
|
default_unfasten_wrench(user, tool)
|
|
return TOOL_ACT_TOOLTYPE_SUCCESS
|
|
|
|
/obj/machinery/bookbinder/attackby(obj/hitby, mob/user, params)
|
|
if(istype(hitby, /obj/item/paper))
|
|
prebind_book(user, hitby)
|
|
return TRUE
|
|
|
|
if(isidcard(hitby))
|
|
var/obj/item/card/id/idcard = hitby
|
|
scanned_name = idcard.registered_name
|
|
balloon_alert(user, "scanned")
|
|
return TRUE
|
|
|
|
return ..()
|
|
|
|
/obj/machinery/bookbinder/proc/prebind_book(mob/user, obj/item/paper/draw_from)
|
|
if(machine_stat)
|
|
return
|
|
|
|
if(busy)
|
|
to_chat(user, span_warning("The book binder is busy. Please wait for completion of previous operation."))
|
|
return
|
|
|
|
if(!scanned_name)
|
|
scanned_name = "unknown author"
|
|
say("No ID detected. Please scan your ID if you would like to be credited for this book. Otherwise please enter your paper again.")
|
|
return
|
|
|
|
if(!user.transferItemToLoc(draw_from, src))
|
|
return
|
|
|
|
user.visible_message(span_notice("[user] loads some paper into [src]."), span_notice("You load some paper into [src]."))
|
|
audible_message(span_hear("[src] begins to hum as it warms up its printing drums."))
|
|
busy = TRUE
|
|
playsound(src, 'sound/machines/printer.ogg', 50)
|
|
flick("binder1", src)
|
|
addtimer(CALLBACK(src, PROC_REF(bind_book), draw_from), 4.1 SECONDS)
|
|
|
|
/obj/machinery/bookbinder/proc/bind_book(obj/item/paper/draw_from)
|
|
busy = FALSE
|
|
if(!draw_from) //What the fuck did you do
|
|
return
|
|
|
|
if(machine_stat)
|
|
draw_from.forceMove(drop_location())
|
|
return
|
|
|
|
visible_message(span_notice("[src] whirs as it prints and binds a new book."))
|
|
var/obj/item/book/bound_book = new(loc)
|
|
bound_book.book_data.set_content_using_paper(draw_from)
|
|
bound_book.book_data.set_author(scanned_name, trusted = FALSE)
|
|
bound_book.name = "Print Job #" + "[rand(100, 999)]"
|
|
bound_book.gen_random_icon_state()
|
|
scanned_name = null
|
|
|
|
qdel(draw_from)
|
|
|
|
#undef LIBRARY_ARCHIVE
|
|
#undef LIBRARY_CHECKOUT
|
|
#undef LIBRARY_INVENTORY
|
|
#undef LIBRARY_NEWSFEED
|
|
#undef LIBRARY_PRINT
|
|
#undef LIBRARY_TOP_SNEAKY
|
|
#undef LIBRARY_UPLOAD
|
|
#undef MAX_LIBRARY
|
|
#undef MIN_LIBRARY
|
|
#undef NEWSCASTER_COOLDOWN
|
|
#undef PRINTER_COOLDOWN
|