diff --git a/SQL/paradise_schema.sql b/SQL/paradise_schema.sql index 9af8a0e261d..36381013065 100644 --- a/SQL/paradise_schema.sql +++ b/SQL/paradise_schema.sql @@ -401,6 +401,7 @@ CREATE TABLE `library` ( `content` text NOT NULL, `category` text NOT NULL, `ckey` varchar(45) NOT NULL, + `flagged` int(11) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=MyISAM AUTO_INCREMENT=929 DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; diff --git a/SQL/paradise_schema_prefixed.sql b/SQL/paradise_schema_prefixed.sql index e2ea56d7582..e599326ba8e 100644 --- a/SQL/paradise_schema_prefixed.sql +++ b/SQL/paradise_schema_prefixed.sql @@ -401,6 +401,7 @@ CREATE TABLE `SS13_library` ( `content` text NOT NULL, `category` text NOT NULL, `ckey` varchar(45) NOT NULL, + `flagged` int(11) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=MyISAM AUTO_INCREMENT=929 DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; diff --git a/_maps/map_files/MetaStation/MetaStation.v41A.II.dmm b/_maps/map_files/MetaStation/MetaStation.v41A.II.dmm index 6324c5fb02d..79e904ed62f 100644 --- a/_maps/map_files/MetaStation/MetaStation.v41A.II.dmm +++ b/_maps/map_files/MetaStation/MetaStation.v41A.II.dmm @@ -152,7 +152,7 @@ "acV" = (/obj/structure/window/reinforced{dir = 4},/obj/structure/cable/yellow{d1 = 4; d2 = 8; icon_state = "4-8"},/obj/structure/sink/kitchen{desc = "A sink used for washing one's hands and face. It looks rusty and home-made"; name = "old sink"; pixel_y = 28},/turf/simulated/floor/plasteel{icon_state = "freezerfloor"},/area/security/prison) "acW" = (/obj/structure/cable/yellow{d1 = 4; d2 = 8; icon_state = "4-8"},/obj/structure/flora/kirbyplants{tag = "icon-plant-13"; icon_state = "plant-13"},/turf/simulated/floor/plasteel{icon_state = "floorgrime"},/area/security/prison) "acX" = (/obj/structure/table,/obj/item/weapon/folder,/obj/item/weapon/paper/hydroponics,/obj/structure/cable/yellow{d1 = 1; d2 = 2; icon_state = "1-2"},/obj/item/weapon/pen,/turf/simulated/floor/plasteel{icon_state = "floorgrime"},/area/security/prison) -"acY" = (/obj/structure/table,/obj/machinery/librarypubliccomp,/turf/simulated/floor/plasteel{icon_state = "floorgrime"},/area/security/prison) +"acY" = (/obj/structure/table,/obj/machinery/computer/library/public,/turf/simulated/floor/plasteel{icon_state = "floorgrime"},/area/security/prison) "acZ" = (/obj/machinery/computer/arcade,/obj/machinery/computer/security/telescreen/entertainment{pixel_x = 0; pixel_y = 32},/turf/simulated/floor/plasteel{icon_state = "floorgrime"},/area/security/prison) "ada" = (/obj/machinery/camera{c_tag = "Prison Chamber"; network = list("SS13","Prison")},/obj/item/device/radio/intercom/locked/prison{dir = 2; name = "Prison Intercom (General)"; pixel_x = 0; pixel_y = 24},/turf/simulated/floor/plasteel,/area/security/prison) "adb" = (/obj/structure/cable/yellow{d1 = 1; d2 = 2; icon_state = "1-2"},/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers,/obj/structure/cable/yellow{d1 = 2; d2 = 4; icon_state = "2-4"},/turf/simulated/floor/plasteel{icon_state = "floorgrime"},/area/security/prison) @@ -4147,7 +4147,7 @@ "bBM" = (/obj/structure/cable/yellow{d1 = 1; d2 = 2; icon_state = "1-2"},/obj/machinery/atmospherics/pipe/simple/hidden/supply,/turf/simulated/floor/plating{icon_state = "warnplate"; dir = 1},/area/maintenance/fpmaint2{name = "Port Maintenance"}) "bBN" = (/obj/structure/closet,/obj/effect/decal/cleanable/cobweb2,/obj/item/weapon/contraband/poster,/obj/effect/spawner/lootdrop/maintenance,/turf/simulated/floor/plating,/area/maintenance/fpmaint2{name = "Port Maintenance"}) "bBO" = (/obj/structure/table/woodentable,/obj/item/device/flashlight/lamp/green{pixel_x = 1; pixel_y = 5},/obj/machinery/computer/security/telescreen/entertainment{pixel_x = -32; pixel_y = 0},/turf/simulated/floor/wood,/area/library) -"bBP" = (/obj/structure/table/woodentable,/obj/machinery/librarypubliccomp,/turf/simulated/floor/wood,/area/library) +"bBP" = (/obj/structure/table/woodentable,/obj/machinery/computer/library/public,/turf/simulated/floor/wood,/area/library) "bBQ" = (/turf/simulated/floor/carpet,/area/library) "bBR" = (/obj/machinery/atmospherics/pipe/simple/hidden/supply{req_access_txt = 1},/turf/simulated/floor/carpet,/area/library) "bBS" = (/obj/structure/stool/bed/chair/comfy/black{dir = 8},/turf/simulated/floor/wood,/area/library) @@ -4886,7 +4886,7 @@ "bPX" = (/turf/simulated/floor/wood{tag = "icon-wood-broken5"; icon_state = "wood-broken5"},/area/library) "bPY" = (/obj/machinery/door/window/northright{base_state = "left"; dir = 8; icon_state = "left"; name = "Library Desk Door"; pixel_x = 3; req_access_txt = "37"},/turf/simulated/floor/wood,/area/library) "bPZ" = (/obj/effect/landmark/start{name = "Librarian"},/obj/structure/stool/bed/chair/office/dark{dir = 1},/turf/simulated/floor/wood,/area/library) -"bQa" = (/obj/structure/table/woodentable,/obj/structure/noticeboard{desc = "A board for pinning important notices upon. Probably helpful for keeping track of requests."; name = "requests board"; pixel_x = 32; pixel_y = 0},/obj/machinery/librarycomp,/turf/simulated/floor/wood,/area/library) +"bQa" = (/obj/structure/table/woodentable,/obj/structure/noticeboard{desc = "A board for pinning important notices upon. Probably helpful for keeping track of requests."; name = "requests board"; pixel_x = 32; pixel_y = 0},/obj/machinery/computer/library/checkout,/turf/simulated/floor/wood,/area/library) "bQb" = (/obj/structure/closet/firecloset,/turf/simulated/floor/plasteel{icon_state = "dark"},/area/library) "bQc" = (/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers,/obj/structure/extinguisher_cabinet{pixel_x = 27; pixel_y = 0},/turf/simulated/floor/plasteel{dir = 4; icon_state = "neutralcorner"},/area/hallway/primary/central) "bQd" = (/obj/machinery/suit_storage_unit/standard_unit,/turf/simulated/floor/plasteel{tag = "icon-vault (WEST)"; icon_state = "vault"; dir = 8},/area/ai_monitored/storage/eva{name = "E.V.A. Storage"}) diff --git a/_maps/map_files/cyberiad/cyberiad.dmm b/_maps/map_files/cyberiad/cyberiad.dmm index 1ed7089ef1b..ee8e6682456 100644 --- a/_maps/map_files/cyberiad/cyberiad.dmm +++ b/_maps/map_files/cyberiad/cyberiad.dmm @@ -3040,7 +3040,7 @@ "bgx" = (/obj/machinery/portable_atmospherics/hydroponics,/turf/simulated/floor/plasteel{icon_state = "dark"},/area/hydroponics) "bgy" = (/obj/machinery/door/window/northright{base_state = "right"; dir = 8; icon_state = "right"; name = "Library Desk Door"; req_access_txt = "37"},/obj/machinery/status_display{density = 0; layer = 4; pixel_x = 0; pixel_y = 32},/turf/simulated/floor/wood,/area/library) "bgz" = (/obj/machinery/newscaster{pixel_y = 32},/turf/simulated/floor/wood,/area/library) -"bgA" = (/obj/structure/table/woodentable,/obj/machinery/librarycomp{pixel_y = 0},/obj/machinery/light/small{dir = 4},/obj/machinery/light_switch{pixel_x = 27},/turf/simulated/floor/wood,/area/library) +"bgA" = (/obj/structure/table/woodentable,/obj/machinery/computer/library/checkout{pixel_y = 0},/obj/machinery/light/small{dir = 4},/obj/machinery/light_switch{pixel_x = 27},/turf/simulated/floor/wood,/area/library) "bgB" = (/obj/machinery/power/apc{dir = 8; name = "Chapel APC"; pixel_x = -25},/obj/structure/cable{icon_state = "0-2"; pixel_y = 1; d2 = 2},/obj/machinery/light{dir = 8},/turf/simulated/floor/plasteel{icon_state = "dark"},/area/chapel/main) "bgC" = (/obj/structure/stool,/obj/effect/landmark/start{name = "Civilian"},/turf/simulated/floor/plasteel{dir = 8; icon_state = "chapel"},/area/chapel/main) "bgD" = (/obj/structure/stool,/turf/simulated/floor/plasteel{icon_state = "chapel"},/area/chapel/main) @@ -3111,7 +3111,7 @@ "bhQ" = (/obj/machinery/portable_atmospherics/hydroponics,/obj/item/device/radio/intercom{frequency = 1459; name = "station intercom (General)"; pixel_x = 28},/turf/simulated/floor/plasteel{icon_state = "dark"},/area/hydroponics) "bhR" = (/obj/structure/bookcase{name = "bookcase (Adult)"},/obj/machinery/atmospherics/pipe/simple/hidden/supply{level = 1},/turf/simulated/floor/wood,/area/library) "bhS" = (/obj/structure/bookcase{name = "bookcase (Adult)"},/turf/simulated/floor/wood,/area/library) -"bhT" = (/obj/machinery/librarypubliccomp,/obj/structure/table/woodentable,/turf/simulated/floor/wood,/area/library) +"bhT" = (/obj/machinery/computer/library/public,/obj/structure/table/woodentable,/turf/simulated/floor/wood,/area/library) "bhU" = (/obj/structure/stool/bed/chair/comfy/black,/turf/simulated/floor/wood,/area/library) "bhV" = (/obj/structure/table/woodentable,/obj/item/device/flashlight/lamp/green{pixel_x = 1; pixel_y = 5},/turf/simulated/floor/wood,/area/library) "bhW" = (/obj/effect/landmark/start{name = "Librarian"},/obj/structure/stool/bed/chair/office/dark,/turf/simulated/floor/wood,/area/library) diff --git a/_maps/map_files/cyberiad/z2.dmm b/_maps/map_files/cyberiad/z2.dmm index d1aa08e97d4..a473111071f 100644 --- a/_maps/map_files/cyberiad/z2.dmm +++ b/_maps/map_files/cyberiad/z2.dmm @@ -425,7 +425,7 @@ "ii" = (/obj/structure/bookcase{name = "Forbidden Knowledge"},/obj/effect/decal/cleanable/cobweb,/turf/unsimulated/floor{dir = 8; icon_state = "wood"},/area/wizard_station) "ij" = (/obj/structure/bookcase{name = "Forbidden Knowledge"},/turf/unsimulated/floor{dir = 8; icon_state = "wood"},/area/wizard_station) "ik" = (/turf/unsimulated/floor{dir = 8; icon_state = "wood"},/area/wizard_station) -"il" = (/obj/machinery/librarycomp,/obj/structure/table/woodentable,/turf/unsimulated/floor{dir = 8; icon_state = "wood"},/area/wizard_station) +"il" = (/obj/machinery/computer/library/checkout,/obj/structure/table/woodentable,/turf/unsimulated/floor{dir = 8; icon_state = "wood"},/area/wizard_station) "im" = (/obj/machinery/vending/magivend,/obj/machinery/light/spot{tag = "icon-tube1 (NORTH)"; icon_state = "tube1"; dir = 1},/turf/unsimulated/floor{dir = 8; icon_state = "wood"},/area/wizard_station) "in" = (/obj/machinery/vending/snack,/turf/unsimulated/floor{dir = 8; icon_state = "wood"},/area/wizard_station) "io" = (/obj/structure/closet{icon_closed = "cabinet_closed"; icon_opened = "cabinet_open"; icon_state = "cabinet_closed"},/obj/item/weapon/storage/backpack/satchel,/turf/unsimulated/floor{dir = 9; icon_state = "carpetside"},/area/wizard_station) diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index a2d62b29c8a..a16a419d35b 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -62,6 +62,7 @@ var/list/admin_verbs_admin = list( /client/proc/man_up, /client/proc/global_man_up, /client/proc/delbook, + /client/proc/view_flagged_books, /client/proc/empty_ai_core_toggle_latejoin, /client/proc/aooc, /client/proc/freeze, @@ -125,6 +126,7 @@ var/list/admin_verbs_server = list( /client/proc/cmd_debug_del_all, /datum/admins/proc/toggle_aliens, /client/proc/delbook, + /client/proc/view_flagged_books, /client/proc/toggle_antagHUD_use, /client/proc/toggle_antagHUD_restrictions, /client/proc/set_ooc, diff --git a/code/modules/library/admin.dm b/code/modules/library/admin.dm new file mode 100644 index 00000000000..7e3abd91db9 --- /dev/null +++ b/code/modules/library/admin.dm @@ -0,0 +1,41 @@ +/client/proc/delbook() + set name = "Delete Book" + set desc = "Permamently deletes a book from the database." + set category = "Admin" + + if(!holder) + to_chat(src, "Only administrators may use this command.") + return + + var/isbn = input("ISBN number?", "Delete Book") as num | null + if(!isbn) + return + + var/DBQuery/query_delbook = dbcon.NewQuery("DELETE FROM [format_table_name("library")] WHERE id=[isbn]") + if(!query_delbook.Execute()) + var/err = query_delbook.ErrorMsg() + log_game("SQL ERROR deleting book. Error : \[[err]\]\n") + return + + log_admin("[key_name(usr)] has deleted the book [isbn].") + message_admins("[key_name_admin(usr)] has deleted the book [isbn].") + +/client/proc/view_flagged_books() + set name = "View Flagged Books" + set desc = "View books flagged for content." + set category = "Admin" + + if(!holder) + to_chat(src, "Only administrators may use this command.") + return + + var/DBQuery/query = dbcon.NewQuery("SELECT id, title, flagged FROM [format_table_name("library")] WHERE flagged > 0 ORDER BY flagged DESC") + if(!query.Execute()) + var/err = query.ErrorMsg() + log_game("SQL ERROR getting flagged books. Error : \[[err]\]\n") + return + + var/i + for(i = 0, query.NextRow(), i++) + to_chat(usr, "[add_zero(query.item[1], 4)] ([query.item[2]]) - flagged [query.item[3]] times.") + to_chat(usr, "[i] flagged books total.") \ No newline at end of file diff --git a/code/modules/library/computers/base.dm b/code/modules/library/computers/base.dm new file mode 100644 index 00000000000..fa0a4186191 --- /dev/null +++ b/code/modules/library/computers/base.dm @@ -0,0 +1,93 @@ +#define MAX_BOOK_FLAGS 3 // maximum number of times a book can be flagged before being removed from results + +/obj/machinery/computer/library + name = "visitor computer" + anchored = 1 + density = 1 + icon_keyboard = "" + icon_screen = "computer_on" + var/screenstate = 0 + var/page_num = 1 + var/num_pages = 0 + var/num_results = 0 + var/datum/library_query/query = new() + + icon = 'icons/obj/library.dmi' + icon_state = "computer" + +/obj/machinery/computer/library/proc/interact_check(var/mob/user) + if(stat & (BROKEN | NOPOWER)) + return 1 + + if (!Adjacent(user)) + if (!issilicon(user) && !isobserver(user)) + user.unset_machine() + user << browse(null, "window=library") + return 1 + + user.set_machine(src) + return 0 + +/obj/machinery/computer/library/proc/get_page(var/page_num) + var/searchquery = "" + var/where = 0 + if(query) + if(query.title && query.title != "") + searchquery += " WHERE title LIKE '%[query.title]%'" + where = 1 + if(query.author && query.author != "") + searchquery += " [!where ? "WHERE" : "AND"] author LIKE '%[query.author]%'" + where = 1 + if(query.category && query.category != "") + searchquery += " [!where ? "WHERE" : "AND"] category LIKE '%[query.category]%'" + if(query.category == "Fiction") + searchquery += " AND category NOT LIKE '%Non-Fiction%'" + where = 1 + searchquery += " [!where ? "WHERE" : "AND"] flagged < [MAX_BOOK_FLAGS]" + var/sql = "SELECT id, author, title, category, ckey, flagged FROM [format_table_name("library")] [searchquery] LIMIT [(page_num - 1) * LIBRARY_BOOKS_PER_PAGE], [LIBRARY_BOOKS_PER_PAGE]" + + // Pagination + var/DBQuery/_query = dbcon.NewQuery(sql) + _query.Execute() + if(_query.ErrorMsg()) + log_to_dd(_query.ErrorMsg()) + + var/list/results = list() + while(_query.NextRow()) + var/datum/cachedbook/CB = new() + CB.LoadFromRow(list( + "id" =_query.item[1], + "author" =_query.item[2], + "title" =_query.item[3], + "category"=_query.item[4], + "ckey" =_query.item[5], + "flagged" =text2num(_query.item[6]) + )) + results += CB + return results + +/obj/machinery/computer/library/proc/get_num_results() + var/sql = "SELECT COUNT(*) FROM [format_table_name("library")]" + + var/DBQuery/_query = dbcon.NewQuery(sql) + _query.Execute() + while(_query.NextRow()) + return text2num(_query.item[1]) + return 0 + +/obj/machinery/computer/library/proc/get_pagelist() + var/pagelist = "
" + var/start = max(1, page_num - 3) + var/end = min(num_pages, page_num + 3) + for(var/i = start to end) + var/dat = "[i]" + if(i == page_num) + dat = "[dat]" + if(i != end) + dat += " " + pagelist += dat + pagelist += "
" + return pagelist + +/obj/machinery/computer/library/proc/getBookByID(var/id as text) + return library_catalog.getBookByID(id) \ No newline at end of file diff --git a/code/modules/library/computers/checkout.dm b/code/modules/library/computers/checkout.dm new file mode 100644 index 00000000000..2746aed857e --- /dev/null +++ b/code/modules/library/computers/checkout.dm @@ -0,0 +1,465 @@ +/* + * Library Computer + */ +/obj/machinery/computer/library/checkout + name = "Check-In/Out Computer" + icon = 'icons/obj/library.dmi' + icon_state = "computer" + anchored = 1 + density = 1 + var/arcanecheckout = 0 + //var/screenstate = 0 // 0 - Main Menu, 1 - Inventory, 2 - Checked Out, 3 - Check Out a Book + var/buffer_book + var/buffer_mob + var/upload_category = "Fiction" + var/list/checkouts = list() + var/list/inventory = list() + var/checkoutperiod = 5 // In minutes + var/obj/machinery/libraryscanner/scanner // Book scanner that will be used when uploading books to the Archive + + var/bibledelay = 0 // LOL NO SPAM (1 minute delay) -- Doohl + var/booklist + +/obj/machinery/computer/library/checkout/attack_hand(var/mob/user as mob) + if(..()) + return + interact(user) + +/obj/machinery/computer/library/checkout/interact(var/mob/user) + if(interact_check(user)) + return + + var/dat = "" + switch(screenstate) + if(0) + // Main Menu + + dat += {"
    +
  1. View General Inventory
  2. +
  3. View Checked Out Inventory
  4. +
  5. Check out a Book
  6. +
  7. Connect to External Archive
  8. +
  9. Upload New Title to Archive
  10. +
  11. Print a Bible
  12. +
  13. Print a Manual
  14. "} + if(src.emagged) + dat += "
  15. Access the Forbidden Lore Vault
  16. " + dat += "
" + + if(src.arcanecheckout) + new /obj/item/weapon/tome(src.loc) + to_chat(user, "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("[user] stares at the blank screen for a few moments, his expression frozen in fear. When he finally awakens from it, he looks a lot older.", 2) + src.arcanecheckout = 0 + if(1) + // Inventory + dat += "

Inventory

" + for(var/obj/item/weapon/book/b in inventory) + dat += "[b.name] (Delete)
" + dat += "(Return to main menu)
" + if(2) + // Checked Out + dat += "

Checked Out Books


" + 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 = "(OVERDUE) [timedue]" + else + timedue = round(timedue) + + dat += {"\"[b.bookname]\", Checked out to: [b.mobname]
--- Taken: [timetaken] minutes ago, Due: in [timedue] minutes
+ (Check In)

"} + dat += "(Return to main menu)
" + if(3) + // Check Out a Book + + dat += {"

Check Out a Book


+ Book: [src.buffer_book] + \[Edit\]
+ Recipient: [src.buffer_mob] + \[Edit\]
+ Checkout Date : [world.time/600]
+ Due Date: [(world.time + checkoutperiod)/600]
+ (Checkout Period: [checkoutperiod] minutes) (+/-) + (Commit Entry)
+ (Return to main menu)
"} + if(4) + dat += "

External Archive

" + if(!dbcon.IsConnected()) + dat += "ERROR: Unable to contact External Archive. Please contact your system administrator for assistance." + else + num_results = src.get_num_results() + num_pages = Ceiling(num_results/LIBRARY_BOOKS_PER_PAGE) + dat += {""} + var/pagelist = get_pagelist() + + dat += {"

Search Settings


+ Filter by Title: [query.title]
+ Filter by Category: [query.category]
+ Filter by Author: [query.author]
+ \[Start Search\]
"} + dat += pagelist + + dat += {"
+ + + +
"} + + dat += {" + + + + + + "} + + for(var/datum/cachedbook/CB in get_page(page_num)) + var/author = CB.author + var/controls = "\[Order\]" + controls += {" \[Flag[CB.flagged ? "ged" : ""]\]"} + if(check_rights(R_ADMIN, user = user)) + controls += " \[Delete\]" + author += " ([ckey(CB.ckey)]))" + dat += {" + + + + + "} + + dat += "
AuthorTitleCategoryControls
[author][CB.title][CB.category][controls]

[pagelist]" + + dat += "
(Return to main menu)
" + if(5) + dat += "

Upload a New Title

" + if(!scanner) + for(var/obj/machinery/libraryscanner/S in range(9)) + scanner = S + break + if(!scanner) + 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 += "

Print a Manual

" + dat += "" + + var/list/forbidden = list( + /obj/item/weapon/book/manual + ) + + if(!emagged) + forbidden |= /obj/item/weapon/book/manual/nuclear + + var/manualcount = 1 + var/obj/item/weapon/book/manual/M = null + + for(var/manual_type in (typesof(/obj/item/weapon/book/manual) - forbidden)) + M = new manual_type() + dat += "" + manualcount++ + qdel(M) + M = null + dat += "
[M.title]
" + dat += "
(Return to main menu)
" + + if(8) + + 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.

+ Yes.
+ No.
"} + + var/datum/browser/B = new /datum/browser(user, "library", "Book Inventory Management") + B.set_content(dat) + B.open() + +/obj/machinery/computer/library/checkout/emag_act(mob/user) + if(density && !emagged) + emagged = 1 + to_chat(user, "You override the library computer's printing restrictions.") + +/obj/machinery/computer/library/checkout/attackby(obj/item/weapon/W as obj, mob/user as mob) + if(istype(W, /obj/item/weapon/barcodescanner)) + var/obj/item/weapon/barcodescanner/scanner = W + scanner.computer = src + to_chat(user, "[scanner]'s associated machine has been set to [src].") + audible_message("[src] lets out a low, short blip.", 2) + return 1 + else + return ..() + +/obj/machinery/computer/library/checkout/Topic(href, href_list) + if(..()) + usr << browse(null, "window=library") + onclose(usr, "library") + return 1 + + if(href_list["pagenum"]) + if(!num_pages) + page_num = 1 + else + var/pn = text2num(href_list["pagenum"]) + if(!isnull(pn)) + page_num = Clamp(pn, 1, num_pages) + + if(href_list["page"]) + if(num_pages == 0) + page_num = 1 + else + page_num = Clamp(text2num(href_list["page"]), 1, num_pages) + if(href_list["settitle"]) + var/newtitle = input("Enter a title to search for:") as text|null + if(newtitle) + query.title = sanitize(newtitle) + else + query.title = null + if(href_list["setcategory"]) + var/newcategory = input("Choose a category to search for:") in (list("Any") + library_section_names) + if(newcategory == "Any") + query.category = null + else if(newcategory) + query.category = sanitize(newcategory) + if(href_list["setauthor"]) + var/newauthor = input("Enter an author to search for:") as text|null + if(newauthor) + query.author = sanitize(newauthor) + else + query.author = null + + if(href_list["search"]) + num_results = src.get_num_results() + num_pages = Ceiling(num_results/LIBRARY_BOOKS_PER_PAGE) + page_num = 1 + + screenstate = 4 + if(href_list["del"]) + if(!check_rights(R_ADMIN)) + to_chat(usr, "You aren't an admin, piss off.") + return + var/datum/cachedbook/target = getBookByID(href_list["del"]) // Sanitized in getBookByID + var/ans = alert(usr, "Are you sure you wish to delete \"[target.title]\", by [target.author]? This cannot be undone.", "Library System", "Yes", "No") + if(ans=="Yes") + var/DBQuery/query = dbcon.NewQuery("DELETE FROM [format_table_name("library")] WHERE id=[target.id]") + var/response = query.Execute() + if(!response) + to_chat(usr, query.ErrorMsg()) + return + log_admin("LIBRARY: [usr.name]/[usr.key] has deleted \"[target.title]\", by [target.author] ([target.ckey])!") + message_admins("[key_name_admin(usr)] has deleted \"[target.title]\", by [target.author] ([target.ckey])!") + src.updateUsrDialog() + return + + if(href_list["delbyckey"]) + if(!check_rights(R_ADMIN)) + to_chat(usr, "You aren't an admin, piss off.") + return + var/tckey = ckey(href_list["delbyckey"]) + var/ans = alert(usr,"Are you sure you wish to delete all books by [tckey]? This cannot be undone.", "Library System", "Yes", "No") + if(ans=="Yes") + var/DBQuery/query = dbcon.NewQuery("DELETE FROM [format_table_name("library")] WHERE ckey='[sanitizeSQL(tckey)]'") + var/response = query.Execute() + if(!response) + to_chat(usr, query.ErrorMsg()) + return + var/affected=query.RowsAffected() + if(affected==0) + to_chat(usr, "Unable to find any matching rows.") + return + log_admin("LIBRARY: [usr.name]/[usr.key] has deleted [affected] books written by [tckey]!") + message_admins("[key_name_admin(usr)] has deleted [affected] books written by [tckey]!") + src.updateUsrDialog() + return + + if(href_list["flag"]) + if(!dbcon.IsConnected()) + alert("Connection to Archive has been severed. Aborting.") + return + var/id = href_list["flag"] + if(id) + var/datum/cachedbook/B = getBookByID(id) + if(B) + if((input(usr, "Are you sure you want to flag [B.title] as having inappropriate content?", "Flag Book #[B.id]") in list("Yes", "No")) == "Yes") + library_catalog.flag_book_by_id(usr, id) + + 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/weapon/storage/bible/B = new /obj/item/weapon/storage/bible(src.loc) + if(ticker && ( ticker.Bible_icon_state && ticker.Bible_item_state) ) + B.icon_state = ticker.Bible_icon_state + B.item_state = ticker.Bible_item_state + B.name = ticker.Bible_name + B.deity_name = ticker.Bible_deity_name + + bibledelay = 1 + spawn(60) + bibledelay = 0 + + else + visible_message("[src]'s monitor flashes, \"Bible printer currently unavailable, please wait a moment.\"") + + if("7") + screenstate = 7 + if("8") + screenstate = 8 + 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 = copytext(sanitize(input("Enter the book's title:") as text|null),1,MAX_MESSAGE_LEN) + if(href_list["editmob"]) + buffer_mob = copytext(sanitize(input("Enter the recipient's name:") as text|null),1,MAX_NAME_LEN) + if(href_list["checkout"]) + var/datum/borrowbook/b = new /datum/borrowbook + b.bookname = sanitize(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/weapon/book/b = locate(href_list["delbook"]) + inventory.Remove(b) + if(href_list["uploadauthor"]) + var/newauthor = copytext(sanitize(input("Enter the author's name: ") as text|null),1,MAX_MESSAGE_LEN) + if(newauthor && scanner) + scanner.cache.author = newauthor + if(href_list["uploadcategory"]) + var/newcategory = input("Choose a category: ") in list("Fiction", "Non-Fiction", "Adult", "Reference", "Religion") + if(newcategory) + upload_category = newcategory + if(href_list["upload"]) + if(scanner) + 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") + establish_db_connection() + if(!dbcon.IsConnected()) + 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/DBQuery/query = dbcon.NewQuery("INSERT INTO [format_table_name("library")] (author, title, content, category, ckey, flagged) VALUES ('[sqlauthor]', '[sqltitle]', '[sqlcontent]', '[sqlcategory]', '[ckey(usr.key)]', 0)") + var/response = query.Execute() + if(!response) + to_chat(usr, query.ErrorMsg()) + else + log_admin("[usr.name]/[usr.key] has uploaded the book titled [scanner.cache.name], [length(scanner.cache.dat)] characters in length") + message_admins("[key_name_admin(usr)] has uploaded the book titled [scanner.cache.name], [length(scanner.cache.dat)] characters in length") + + if(href_list["id"]) + if(href_list["id"]=="-1") + href_list["id"] = input("Enter your order:") as null|num + if(!href_list["id"]) + return + + if(!dbcon.IsConnected()) + alert("Connection to Archive has been severed. Aborting.") + return + + var/datum/cachedbook/newbook = getBookByID(href_list["id"]) // Sanitized in getBookByID + if(!newbook) + alert("No book found") + return + if((newbook.forbidden == 2 && !emagged) || newbook.forbidden == 1) + alert("This book is forbidden and cannot be printed.") + return + + if(bibledelay) + audible_message("[src]'s monitor flashes, \"Printer unavailable. Please allow a short time before attempting to print.\"") + else + bibledelay = 1 + spawn(60) + bibledelay = 0 + make_external_book(newbook) + if(href_list["manual"]) + if(!href_list["manual"]) return + var/bookid = href_list["manual"] + + if(!dbcon.IsConnected()) + alert("Connection to Archive has been severed. Aborting.") + return + + var/datum/cachedbook/newbook = getBookByID("M[bookid]") + if(!newbook) + alert("No book found") + return + if((newbook.forbidden == 2 && !emagged) || newbook.forbidden == 1) + alert("This book is forbidden and cannot be printed.") + return + + if(bibledelay) + for (var/mob/V in hearers(src)) + V.show_message("[src]'s monitor flashes, \"Printer unavailable. Please allow a short time before attempting to print.\"") + else + bibledelay = 1 + spawn(60) + bibledelay = 0 + make_external_book(newbook) + + src.add_fingerprint(usr) + src.updateUsrDialog() + return + +/* + * Library Scanner + */ + +/obj/machinery/computer/library/checkout/proc/make_external_book(var/datum/cachedbook/newbook) + if(!newbook || !newbook.id) + return + var/obj/item/weapon/book/B = new newbook.path(loc) + + if (!newbook.programmatic) + B.name = "Book: [newbook.title]" + B.title = newbook.title + B.author = newbook.author + B.dat = newbook.content + B.icon_state = "book[rand(1,9)]" + visible_message("[src]'s printer hums as it produces a completely bound book. How did it do that?") diff --git a/code/modules/library/computers/public.dm b/code/modules/library/computers/public.dm new file mode 100644 index 00000000000..d2b06e08baf --- /dev/null +++ b/code/modules/library/computers/public.dm @@ -0,0 +1,122 @@ +/obj/machinery/computer/library/public + name = "visitor computer" + +/obj/machinery/computer/library/public/attack_hand(var/mob/user as mob) + if(..()) + return + interact(user) + +/obj/machinery/computer/library/public/interact(var/mob/user) + if(interact_check(user)) + return + + var/dat = "" + switch(screenstate) + if(0) + + dat += {"

Search Settings


+ Filter by Title: [query.title]
+ Filter by Category: [query.category]
+ Filter by Author: [query.author]
+ \[Start Search\]
"} + if(1) + establish_db_connection() + if(!dbcon.IsConnected()) + dat += "ERROR: Unable to contact External Archive. Please contact your system administrator for assistance.
" + else if(num_results == 0) + dat += "No results found." + else + var/pagelist = get_pagelist() + + dat += pagelist + dat += {"
+ + + +
"} + dat += {" + + + + + + + "} + for(var/datum/cachedbook/CB in get_page(page_num)) + dat += {" + + + + + + "} + + dat += "
AuthorTitleCategorySS13BNControls
[CB.author][CB.title][CB.category][CB.id]\[Flag[CB.flagged ? "ged" : ""]\]

[pagelist]" + dat += "\[Go Back\]
" + var/datum/browser/B = new /datum/browser(user, "library", "Library Visitor") + B.set_content(dat) + B.open() + +/obj/machinery/computer/library/public/Topic(href, href_list) + if(..()) + usr << browse(null, "window=publiclibrary") + onclose(usr, "publiclibrary") + return + + if(href_list["pagenum"]) + if(!num_pages) + page_num = 1 + else + var/pn = text2num(href_list["pagenum"]) + if(!isnull(pn)) + page_num = Clamp(pn, 1, num_pages) + + if(href_list["settitle"]) + var/newtitle = input("Enter a title to search for:") as text|null + if(newtitle) + query.title = sanitize(newtitle) + else + query.title = null + if(href_list["setcategory"]) + var/newcategory = input("Choose a category to search for:") in (list("Any") + library_section_names) + if(newcategory == "Any") + query.category = null + else if(newcategory) + query.category = sanitize(newcategory) + if(href_list["setauthor"]) + var/newauthor = input("Enter an author to search for:") as text|null + if(newauthor) + query.author = sanitize(newauthor) + else + query.author = null + + if(href_list["page"]) + if(num_pages == 0) + page_num = 1 + else + page_num = Clamp(text2num(href_list["page"]), 1, num_pages) + + if(href_list["search"]) + num_results = src.get_num_results() + num_pages = Ceiling(num_results/LIBRARY_BOOKS_PER_PAGE) + page_num = 1 + + screenstate = 1 + + if(href_list["back"]) + screenstate = 0 + + if(href_list["flag"]) + if(!dbcon.IsConnected()) + alert("Connection to Archive has been severed. Aborting.") + return + var/id = href_list["flag"] + if(id) + var/datum/cachedbook/B = getBookByID(id) + if(B) + if((input(usr, "Are you sure you want to flag [B.title] as having inappropriate content?", "Flag Book #[B.id]") in list("Yes", "No")) == "Yes") + library_catalog.flag_book_by_id(usr, id) + + add_fingerprint(usr) + updateUsrDialog() + return \ No newline at end of file diff --git a/code/modules/library/lib_items.dm b/code/modules/library/lib_items.dm index 498b7b62a37..f7729af3578 100644 --- a/code/modules/library/lib_items.dm +++ b/code/modules/library/lib_items.dm @@ -19,86 +19,103 @@ density = 1 opacity = 1 var/health = 50 + var/tmp/busy = 0 + var/list/valid_types = list(/obj/item/weapon/book, \ + /obj/item/weapon/tome, \ + /obj/item/weapon/spellbook, \ + /obj/item/weapon/storage/bible) /obj/structure/bookcase/initialize() for(var/obj/item/I in loc) - if(istype(I, /obj/item/weapon/book)) - I.loc = src + if(is_type_in_list(I, valid_types)) + I.forceMove(src) update_icon() /obj/structure/bookcase/attackby(obj/O as obj, mob/user as mob, params) - if(istype(O, /obj/item/weapon/book) || istype(O, /obj/item/weapon/spellbook)) + if(busy) //So that you can't mess with it while deconstructing + return 1 + if(is_type_in_list(O, valid_types)) user.drop_item() - O.loc = src + O.forceMove(src) update_icon() + return 1 else if(istype(O, /obj/item/weapon/wrench)) - to_chat(user, "\blue Now disassembling bookcase") + user.visible_message("[user] starts disassembling \the [src].", \ + "You start disassembling \the [src].") playsound(get_turf(src), 'sound/items/Ratchet.ogg', 50, 1) + busy = 1 + if(do_after(user,50, target = src)) - new /obj/item/stack/sheet/wood(get_turf(src)) - new /obj/item/stack/sheet/wood(get_turf(src)) - new /obj/item/stack/sheet/wood(get_turf(src)) - new /obj/item/stack/sheet/wood(get_turf(src)) - new /obj/item/stack/sheet/wood(get_turf(src)) + playsound(get_turf(src), 'sound/items/Deconstruct.ogg', 75, 1) + user.visible_message("[user] disassembles \the [src].", \ + "You disassemble \the [src].") + busy = 0 + for(var/i = 1 to 5) + new /obj/item/stack/sheet/wood(get_turf(src)) density = 0 qdel(src) - return - else if(istype(O, /obj/item/weapon/pen)) - var/newname = stripped_input(usr, "What would you like to title this bookshelf?") - if(!newname) - return else + busy = 0 + return 1 + else if(istype(O, /obj/item/weapon/pen)) + var/newname = stripped_input(user, "What would you like to title this [name]?") + if(newname) name = ("bookcase ([sanitize(newname)])") + return 1 else switch(O.damtype) if("fire") - src.health -= O.force * 1 + health -= O.force * 1 if("brute") - src.health -= O.force * 0.75 + health -= O.force * 0.75 else - if (src.health <= 0) + if (health <= 0) visible_message("The bookcase is smashed apart!") new /obj/item/stack/sheet/wood(get_turf(src)) new /obj/item/stack/sheet/wood(get_turf(src)) new /obj/item/stack/sheet/wood(get_turf(src)) qdel(src) - ..() + return ..() /obj/structure/bookcase/attack_hand(var/mob/user as mob) if(contents.len) - var/obj/item/weapon/book/choice = input("Which book would you like to remove from the shelf?") in contents as obj|null + var/obj/item/weapon/book/choice = input("Which book would you like to remove from \the [src]?") in contents as obj|null if(choice) - if(!usr.canmove || usr.stat || usr.restrained() || !in_range(loc, usr)) + if(user.incapacitated() || user.lying || !Adjacent(user)) return - if(ishuman(user)) - if(!user.get_active_hand()) - user.put_in_hands(choice) + if(!user.get_active_hand()) + user.put_in_hands(choice) else - choice.loc = get_turf(src) + choice.forceMove(get_turf(src)) update_icon() /obj/structure/bookcase/ex_act(severity) switch(severity) if(1.0) - for(var/obj/item/weapon/book/b in contents) - qdel(b) + for(var/obj/item/I in contents) + qdel(I) qdel(src) return if(2.0) - for(var/obj/item/weapon/book/b in contents) - if (prob(50)) b.loc = (get_turf(src)) - else qdel(b) + for(var/obj/item/I in contents) + if(prob(50)) + qdel(I) qdel(src) return if(3.0) - if (prob(50)) - for(var/obj/item/weapon/book/b in contents) - b.loc = (get_turf(src)) + if(prob(50)) qdel(src) return - else return +/obj/structure/bookcase/Destroy() + for(var/i = 1 to 3) + new /obj/item/stack/sheet/wood(get_turf(src)) + for(var/obj/item/I in contents) + if(is_type_in_list(I, valid_types)) + I.forceMove(get_turf(src)) + return ..() + /obj/structure/bookcase/update_icon() if(contents.len < 5) icon_state = "book-[contents.len]" @@ -155,13 +172,14 @@ var/unique = 0 // 0 - Normal book, 1 - Should not be treated as normal book, unable to be copied, unable to be modified var/title // The real name of the book. var/carved = 0 // Has the book been hollowed out for use as a secret storage item? - var/obj/item/store //What's in the book? + var/forbidden = 0 // Prevent ordering of this book. (0=no, 1=yes, 2=emag only) + var/obj/item/store // What's in the book? /obj/item/weapon/book/attack_self(var/mob/user as mob) if(carved) if(store) to_chat(user, "[store] falls out of [title]!") - store.loc = get_turf(src.loc) + store.forceMove(get_turf(loc)) store = null return else @@ -169,7 +187,8 @@ return if(src.dat) user << browse("Penned by [author].
" + "[dat]", "window=book") - user.visible_message("[user] opens a book titled \"[src.title]\" and begins reading intently.") + if(!isobserver(user)) + user.visible_message("[user] opens a book titled \"[title]\" and begins reading intently.") onclose(user, "book") else to_chat(user, "This book is completely blank!") @@ -179,46 +198,45 @@ if(!store) if(W.w_class < 3) user.drop_item() - W.loc = src + W.forceMove(src) store = W to_chat(user, "You put [W] in [title].") - return + return 1 else to_chat(user, "[W] won't fit in [title].") - return + return 1 else to_chat(user, "There's already something in [title]!") - return + return 1 if(istype(W, /obj/item/weapon/pen)) if(unique) to_chat(user, "These pages don't seem to take the ink well. Looks like you can't modify it.") - return + return 1 var/choice = input("What would you like to change?") in list("Title", "Contents", "Author", "Cancel") switch(choice) if("Title") var/newtitle = reject_bad_text(stripped_input(usr, "Write a new title:")) if(!newtitle) to_chat(usr, "The title is invalid.") - return + return 1 else src.name = newtitle src.title = newtitle if("Contents") - var/content = strip_html(input(usr, "Write your book's contents (HTML NOT allowed):"),8192) as message|null + var/content = strip_html(input(usr, "Write your book's contents (HTML NOT allowed):"), MAX_BOOK_MESSAGE_LEN) as message|null if(!content) to_chat(usr, "The content is invalid.") - return + return 1 else src.dat += content if("Author") var/newauthor = stripped_input(usr, "Write the author's name:") if(!newauthor) to_chat(usr, "The name is invalid.") - return + return 1 else src.author = newauthor - else - return + return 1 else if(istype(W, /obj/item/weapon/barcodescanner)) var/obj/item/weapon/barcodescanner/scanner = W if(!scanner.computer) @@ -238,25 +256,27 @@ if(b.bookname == src.name) scanner.computer.checkouts.Remove(b) to_chat(user, "[W]'s screen flashes: 'Book stored in buffer. Book has been checked in.'") - return + return 1 to_chat(user, "[W]'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/weapon/book in scanner.computer.inventory) if(book == src) to_chat(user, "[W]'s screen flashes: 'Book stored in buffer. Title already present in inventory, aborting to avoid duplicate entry.'") - return + return 1 scanner.computer.inventory.Add(src) to_chat(user, "[W]'s screen flashes: 'Book stored in buffer. Title added to general inventory.'") - else if(istype(W, /obj/item/weapon/kitchen/knife) || istype(W, /obj/item/weapon/wirecutters)) - if(carved) return + return 1 + else if(istype(W, /obj/item/weapon/kitchen/knife) || iswirecutter(W)) + if(carved) + return 1 to_chat(user, "You begin to carve out [title].") if(do_after(user, 30, target = src)) to_chat(user, "You carve out the pages from [title]! You didn't want to read it anyway.") carved = 1 - return + return 1 else - ..() + return ..() /* @@ -269,7 +289,7 @@ throw_speed = 1 throw_range = 5 w_class = 1.0 - var/obj/machinery/librarycomp/computer // Associated computer - Modes 1 to 3 use this + var/obj/machinery/computer/library/checkout/computer // Associated computer - Modes 1 to 3 use this var/obj/item/weapon/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 diff --git a/code/modules/library/lib_machines.dm b/code/modules/library/lib_machines.dm index 57f1b85b227..2974fcf0264 100644 --- a/code/modules/library/lib_machines.dm +++ b/code/modules/library/lib_machines.dm @@ -1,407 +1,146 @@ -/* Library Machines - * - * Contains: - * Borrowbook datum - * Library Public Computer - * Library Computer - * Library Scanner - * Book Binder - */ +#define LIBRARY_BOOKS_PER_PAGE 25 + +var/global/datum/library_catalog/library_catalog = new() +var/global/list/library_section_names = list("Any", "Fiction", "Non-Fiction", "Adult", "Reference", "Religion") + + +/hook/startup/proc/load_manuals() + library_catalog.initialize() + return 1 /* * Borrowbook datum */ -datum/borrowbook // Datum used to keep track of who has borrowed what when and for how long. +/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 + * Cachedbook datum */ -/obj/machinery/librarypubliccomp - name = "visitor computer" - icon = 'icons/obj/library.dmi' - icon_state = "computer" - anchored = 1 - density = 1 - var/screenstate = 0 +/datum/cachedbook // Datum used to cache the SQL DB books locally in order to achieve a performance gain. + var/id var/title - var/category = "Any" var/author - var/SQLquery + var/ckey // ADDED 24/2/2015 - N3X + var/category + var/content + var/programmatic=0 // Is the book programmatically added to the catalog? + var/forbidden=0 + var/path = /obj/item/weapon/book // Type path of the book to generate + var/flagged = 0 -/obj/machinery/librarypubliccomp/attack_hand(var/mob/user as mob) - usr.set_machine(src) - var/dat = "Library Visitor\n" // - switch(screenstate) - if(0) - dat += {"

Search Settings


- Filter by Title: [title]
- Filter by Category: [category]
" - Filter by Author: [author]
- \[Start Search\]
"} - if(1) - if(!dbcon.IsConnected()) - 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 += "" +/datum/cachedbook/proc/LoadFromRow(var/list/row) + id = row["id"] + author = row["author"] + title = row["title"] + category = row["category"] + ckey = row["ckey"] + flagged = row["flagged"] + if("content" in row) + content = row["content"] + programmatic=0 - var/DBQuery/query = dbcon.NewQuery(SQLquery) - query.Execute() +// Builds a SQL statement +/datum/library_query + var/author + var/category + var/title - while(query.NextRow()) - var/author = query.item[1] - var/title = query.item[2] - var/category = query.item[3] - var/id = query.item[4] - dat += "" - dat += "
AUTHORTITLECATEGORYSS13BN
[author][title][category][id]

" - dat += "\[Go Back\]
" - user << browse(dat, "window=publiclibrary") - onclose(user, "publiclibrary") +/datum/library_query/proc/toSQL() + var/list/where = list() + if(author || title || category) + if(author) + where.Add("author LIKE '%[sanitizeSQL(author)]%'") + if(category) + where.Add("category = '[sanitizeSQL(category)]'") + if(title) + where.Add("title LIKE '%[sanitizeSQL(title)]%'") + return " WHERE " + jointext(where, " AND ") + return "" -/obj/machinery/librarypubliccomp/Topic(href, href_list) - if(..()) - usr << browse(null, "window=publiclibrary") - onclose(usr, "publiclibrary") +// So we can have catalogs of books that are programmatic, and ones that aren't. +/datum/library_catalog + var/list/cached_books = list() + +/datum/library_catalog/proc/initialize() + var/newid=1 + for(var/typepath in subtypesof(/obj/item/weapon/book/manual)) + var/obj/item/weapon/book/B = new typepath(null) + var/datum/cachedbook/CB = new() + CB.forbidden = B.forbidden + CB.title = B.name + CB.author = B.author + CB.programmatic=1 + CB.path=typepath + CB.id = "M[newid]" + newid++ + cached_books["[CB.id]"]=CB + +/datum/library_catalog/proc/flag_book_by_id(mob/user, id) + var/global/books_flagged_this_round[0] + + if("[id]" in cached_books) + var/datum/cachedbook/CB = cached_books["[id]"] + if(CB.programmatic) + to_chat(user, "That book cannot be flagged in the system, as it does not actually exist in the database.") + return + + if("[id]" in books_flagged_this_round) + to_chat(user, "This book has already been flagged this shift.") 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", "Adult", "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 [format_table_name("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 + books_flagged_this_round["[id]"] = 1 + message_admins("[key_name_admin(user)] has flagged book #[id] as inappropriate.") - if(href_list["back"]) - screenstate = 0 - - src.add_fingerprint(usr) - src.updateUsrDialog() - return - - -/* - * Library 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. -/obj/machinery/librarycomp - name = "check-in/out computer" - icon = 'icons/obj/library.dmi' - icon_state = "computer" - anchored = 1 - density = 1 - var/arcanecheckout = 0 - var/screenstate = 0 // 0 - Main Menu, 1 - Inventory, 2 - Checked Out, 3 - Check Out a Book - var/buffer_book - var/buffer_mob - var/upload_category = "Fiction" - var/list/checkouts = list() - var/list/inventory = list() - var/checkoutperiod = 5 // In minutes - var/obj/machinery/libraryscanner/scanner // Book scanner that will be used when uploading books to the Archive - - var/bibledelay = 0 // LOL NO SPAM (1 minute delay) -- Doohl - -/obj/machinery/librarycomp/attack_hand(var/mob/user as mob) - usr.set_machine(src) - var/dat = "Book Inventory Management\n" // - switch(screenstate) - if(0) - // Main Menu - dat += {"1. View General Inventory
- 2. View Checked Out Inventory
- 3. Check out a Book
- 4. Connect to External Archive
- 5. Upload New Title to Archive
- 6. Print a Bible
"} - if(src.emagged) - dat += "7. Access the Forbidden Lore Vault
" - if(src.arcanecheckout) - new /obj/item/weapon/tome(src.loc) - to_chat(user, "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("[user] stares at the blank screen for a few moments, his expression frozen in fear. When he finally awakens from it, he looks a lot older.", 2) - src.arcanecheckout = 0 - if(1) - // Inventory - dat += "

Inventory


" - for(var/obj/item/weapon/book/b in inventory) - dat += "[b.name] (Delete)
" - dat += "(Return to main menu)
" - if(2) - // Checked Out - dat += "

Checked Out Books


" - 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 = "(OVERDUE) [timedue]" - else - timedue = round(timedue) - dat += "\"[b.bookname]\", Checked out to: [b.mobname]
--- Taken: [timetaken] minutes ago, Due: in [timedue] minutes
" - dat += "(Check In)

" - dat += "(Return to main menu)
" - if(3) - // Check Out a Book - dat += {"

Check Out a Book


- Book: [src.buffer_book] - \[Edit\]
- Recipient: [src.buffer_mob] - \[Edit\]
- Checkout Date : [world.time/600]
- Due Date: [(world.time + checkoutperiod)/600]
- (Checkout Period: [checkoutperiod] minutes) (+/-) - (Commit Entry)
- (Return to main menu)
"} - if(4) - dat += "

External Archive

" - if(!dbcon.IsConnected()) - dat += "ERROR: Unable to contact External Archive. Please contact your system administrator for assistance." - else - dat += "(Order book by SS13BN)

" - dat += "" - dat += "" - - var/DBQuery/query = dbcon.NewQuery("SELECT id, author, title, category FROM [format_table_name("library")]") - if(!query.Execute()) - var/err = query.ErrorMsg() - log_game("SQL ERROR accessing book. Error : \[[err]\]\n") - return - - while(query.NextRow()) - var/id = query.item[1] - var/author = query.item[2] - var/title = query.item[3] - var/category = query.item[4] - dat += "" - dat += "
AUTHORTITLECATEGORYID
[author][title][category][id]\[Order\]
" - dat += "
(Return to main menu)
" - if(5) - dat += "

Upload a New Title

" - if(!scanner) - for(var/obj/machinery/libraryscanner/S in range(9)) - scanner = S - break - if(!scanner) - 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...
" - dat += "Title: [scanner.cache.name]
" - if(!scanner.cache.author) - scanner.cache.author = "Anonymous" - dat += "Author: [scanner.cache.author]
" - dat += "Category: [upload_category]
" - dat += "\[Upload\]
" - dat += "OOC Upload Rules: No erotica, No real world literature. Only IC guides, stories and accounts." - dat += "Failure to follow these simple rules will lead to bans." - dat += "(Return to main menu)
" - if(7) - dat += "

Accessing Forbidden Lore Vault v 1.3

" - dat += "Are you absolutely sure you want to proceed? EldritchTomes Inc. takes no responsibilities for loss of sanity resulting from this action.

" - dat += "Yes.
" - dat += "No.
" - - //dat += "Close

" - user << browse(dat, "window=library") - onclose(user, "library") - -/obj/machinery/librarycomp/attackby(obj/item/weapon/W as obj, mob/user as mob, params) - if(istype(W, /obj/item/weapon/barcodescanner)) - var/obj/item/weapon/barcodescanner/scanner = W - 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/emag_act(user as mob) - if (src.density) - src.emagged = 1 - -/obj/machinery/librarycomp/Topic(href, href_list) - if(..()) - usr << browse(null, "window=library") - onclose(usr, "library") + var/sqlid = text2num(id) + if(!sqlid) return + var/DBQuery/query = dbcon.NewQuery("UPDATE [format_table_name("library")] SET flagged = flagged + 1 WHERE id=[sqlid]") + query.Execute() - 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) +/datum/library_catalog/proc/rmBookByID(mob/user, id) + if("[id]" in cached_books) + var/datum/cachedbook/CB = cached_books["[id]"] + if(CB.programmatic) + to_chat(user, "That book cannot be removed from the system, as it does not actually exist in the database.") + return - playsound(loc, "sound/goonstation/machines/printer_dotmatrix.ogg", 50, 1) - var/obj/item/weapon/storage/bible/B = new /obj/item/weapon/storage/bible(src.loc) - if(ticker && ( ticker.Bible_icon_state && ticker.Bible_item_state) ) - B.icon_state = ticker.Bible_icon_state - B.item_state = ticker.Bible_item_state - B.name = ticker.Bible_name - B.deity_name = ticker.Bible_deity_name + var/sqlid = text2num(id) + if(!sqlid) + return + var/DBQuery/query = dbcon.NewQuery("DELETE FROM [format_table_name("library")] WHERE id=[sqlid]") + query.Execute() - bibledelay = 1 - spawn(60) - bibledelay = 0 +/datum/library_catalog/proc/getBookByID(id) + if("[id]" in cached_books) + return cached_books["[id]"] - else - for (var/mob/V in hearers(src)) - V.show_message("[src]'s monitor flashes, \"Bible printer currently unavailable, please wait a moment.\"") + var/sqlid = text2num(id) + if(!sqlid) + return + var/DBQuery/query = dbcon.NewQuery("SELECT id, author, title, category, ckey, flagged FROM [format_table_name("library")] WHERE id=[sqlid]") + query.Execute() - 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 = sanitize(copytext(input("Enter the book's title:") as text|null,1,MAX_MESSAGE_LEN)) - if(href_list["editmob"]) - buffer_mob = sanitize(copytext(input("Enter the recipient's name:") as text|null,1,MAX_NAME_LEN)) - if(href_list["checkout"]) - var/datum/borrowbook/b = new /datum/borrowbook - b.bookname = sanitize(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/weapon/book/b = locate(href_list["delbook"]) - inventory.Remove(b) - if(href_list["setauthor"]) - var/newauthor = sanitize(copytext(input("Enter the author's name: ") as text|null,1,MAX_MESSAGE_LEN)) - if(newauthor) - scanner.cache.author = newauthor - if(href_list["setcategory"]) - var/newcategory = input("Choose a category: ") in list("Fiction", "Non-Fiction", "Adult", "Reference", "Religion") - if(newcategory) - upload_category = newcategory - if(href_list["upload"]) - if(scanner) - 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(!dbcon.IsConnected()) - alert("Connection to Archive has been severed. Aborting.") - else - /* - var/sqltitle = dbcon.Quote(scanner.cache.name) - var/sqlauthor = dbcon.Quote(scanner.cache.author) - var/sqlcontent = dbcon.Quote(scanner.cache.dat) - var/sqlcategory = dbcon.Quote(upload_category) - */ - var/sqltitle = sanitizeSQL(scanner.cache.name) - var/sqlauthor = sanitizeSQL(scanner.cache.author) - var/sqlcontent = sanitizeSQL(scanner.cache.dat) - var/sqlcategory = sanitizeSQL(upload_category) - var/DBQuery/query = dbcon.NewQuery("INSERT INTO [format_table_name("library")] (author, title, content, category, ckey) VALUES ('[sqlauthor]', '[sqltitle]', '[sqlcontent]', '[sqlcategory]', '[usr.key]')") - if(!query.Execute()) - var/err = query.ErrorMsg() - log_game("SQL ERROR adding new book. Error : \[[err]\]\n") - return - else - log_game("[usr.name]/[usr.key] has uploaded the book titled [scanner.cache.name], [length(scanner.cache.dat)] signs") - message_admins("[key_name_admin(usr)] has uploaded the book titled [scanner.cache.name], [length(scanner.cache.dat)] signs") - alert("Upload Complete.") + var/list/results=list() + while(query.NextRow()) + var/datum/cachedbook/CB = new() + CB.LoadFromRow(list( + "id" =query.item[1], + "author" =query.item[2], + "title" =query.item[3], + "category"=query.item[4], + "ckey" =query.item[5], + "flagged" =query.item[6] + )) + results += CB + cached_books["[id]"]=CB + return CB + return results - if(href_list["targetid"]) - var/sqlid = sanitizeSQL(href_list["targetid"]) - if(!dbcon.IsConnected()) - alert("Connection to Archive has been severed. Aborting.") - -// reused working printing call from bible code.. needs testing ((Lazureus)) - - if(!bibledelay) - bibledelay = 1 - spawn(60) - bibledelay = 0 - var/DBQuery/query = dbcon.NewQuery("SELECT * FROM [format_table_name("library")] WHERE id=[sqlid]") - query.Execute() - - while(query.NextRow()) - var/author = query.item[2] - var/title = query.item[3] - var/content = query.item[4] - playsound(loc, "sound/goonstation/machines/printer_dotmatrix.ogg", 50, 1) - var/obj/item/weapon/book/B = new(src.loc) - B.name = "Book: [title]" - B.title = title - B.author = author - B.dat = content - B.icon_state = "book[rand(1,7)]" - src.visible_message("[src]'s printer hums as it produces a completely bound book. How did it do that?") - break - else - for (var/mob/V in hearers(src)) - V.show_message("[src]'s monitor flashes, \"Printer unavailable. Please allow a short time before attempting to print.\"") - - 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) - src.add_fingerprint(usr) - src.updateUsrDialog() - return - -/* - * Library Scanner - */ +/** Scanner **/ /obj/machinery/libraryscanner name = "scanner" icon = 'icons/obj/library.dmi' @@ -410,12 +149,18 @@ datum/borrowbook // Datum used to keep track of who has borrowed what when and f density = 1 var/obj/item/weapon/book/cache // Last scanned book -/obj/machinery/libraryscanner/attackby(var/obj/O as obj, var/mob/user as mob, params) - if(istype(O, /obj/item/weapon/book)) +/obj/machinery/libraryscanner/attackby(obj/item/I, mob/user) + if(istype(I, /obj/item/weapon/book)) user.drop_item() - O.loc = src + I.forceMove(src) + return 1 + else + return ..() -/obj/machinery/libraryscanner/attack_hand(var/mob/user as mob) +/obj/machinery/libraryscanner/attack_hand(mob/user) + if(istype(user,/mob/dead)) + to_chat(user, "Nope.") + return usr.set_machine(src) var/dat = "Scanner Control Interface\n" // if(cache) @@ -460,39 +205,19 @@ datum/borrowbook // Datum used to keep track of who has borrowed what when and f anchored = 1 density = 1 -/obj/machinery/bookbinder/attackby(var/obj/O as obj, var/mob/user as mob, params) - if(istype(O, /obj/item/weapon/paper)) +/obj/machinery/bookbinder/attackby(obj/item/I, mob/user) + var/obj/item/weapon/paper/P = I + if(istype(P)) user.drop_item() - O.loc = src user.visible_message("[user] loads some paper into [src].", "You load some paper into [src].") src.visible_message("[src] begins to hum as it warms up its printing drums.") sleep(rand(200,400)) src.visible_message("[src] whirs as it prints and binds a new book.") - var/obj/item/weapon/book/b = new(src.loc) - b.dat = O:info - b.name = "Print Job #" + "[rand(100, 999)]" + var/obj/item/weapon/book/b = new(loc) + b.dat = P.info + b.name = "Print Job #[rand(100, 999)]" b.icon_state = "book[rand(1,7)]" - qdel(O) + qdel(P) + return 1 else - ..() - -/client/proc/delbook() - set name = "Delete Book" - set desc = "Permamently deletes a book from the database." - set category = "Admin" - if(!src.holder) - to_chat(src, "Only administrators may use this command.") - return - - var/isbn = input("ISBN number?", "Delete Book") as num | null - if(!isbn) - return - - var/DBQuery/query_delbook = dbcon.NewQuery("DELETE FROM [format_table_name("library")] WHERE id=[isbn]") - if(!query_delbook.Execute()) - var/err = query_delbook.ErrorMsg() - log_game("SQL ERROR deleting book. Error : \[[err]\]\n") - return - - log_admin("[key_name(usr)] has deleted the book [isbn].") - message_admins("[key_name_admin(usr)] has deleted the book [isbn].") + return ..() \ No newline at end of file diff --git a/icons/obj/library.dmi b/icons/obj/library.dmi index 2501c48f23a..5404c91b3b9 100644 Binary files a/icons/obj/library.dmi and b/icons/obj/library.dmi differ diff --git a/paradise.dme b/paradise.dme index 9736e6d070c..d4f61d749d3 100644 --- a/paradise.dme +++ b/paradise.dme @@ -1311,9 +1311,13 @@ #include "code\modules\hydroponics\trays\tray_tools.dm" #include "code\modules\hydroponics\trays\tray_update_icons.dm" #include "code\modules\karma\karma.dm" +#include "code\modules\library\admin.dm" #include "code\modules\library\lib_items.dm" #include "code\modules\library\lib_machines.dm" #include "code\modules\library\lib_readme.dm" +#include "code\modules\library\computers\base.dm" +#include "code\modules\library\computers\checkout.dm" +#include "code\modules\library\computers\public.dm" #include "code\modules\lighting\__lighting_docs.dm" #include "code\modules\lighting\light_source.dm" #include "code\modules\lighting\lighting_atom.dm"