diff --git a/_maps/RandomRuins/SpaceRuins/cloning_facility.dmm b/_maps/RandomRuins/SpaceRuins/cloning_facility.dmm index d440e2d15b..1f258fad77 100644 --- a/_maps/RandomRuins/SpaceRuins/cloning_facility.dmm +++ b/_maps/RandomRuins/SpaceRuins/cloning_facility.dmm @@ -294,7 +294,7 @@ /turf/template_noop, /area/space/nearstation) "N" = ( -/obj/item/book/random/triple, +/obj/item/book/random, /turf/open/floor/plasteel, /area/ruin/space/has_grav/powered/ancient_shuttle) "O" = ( diff --git a/_maps/map_files/CogStation/CogStation.dmm b/_maps/map_files/CogStation/CogStation.dmm index a62dfcf0a2..c690f7ea88 100644 --- a/_maps/map_files/CogStation/CogStation.dmm +++ b/_maps/map_files/CogStation/CogStation.dmm @@ -42867,7 +42867,7 @@ /area/science/xenobiology) "bPd" = ( /obj/structure/table/reinforced, -/obj/item/book/random/triple, +/obj/item/book/random, /turf/open/floor/engine, /area/science/xenobiology) "bPe" = ( diff --git a/code/__DEFINES/is_helpers.dm b/code/__DEFINES/is_helpers.dm index ff68f18408..02227dc737 100644 --- a/code/__DEFINES/is_helpers.dm +++ b/code/__DEFINES/is_helpers.dm @@ -240,3 +240,9 @@ GLOBAL_LIST_INIT(glass_sheet_types, typecacheof(list( #define isshuttleturf(T) (length(T.baseturfs) && (/turf/baseturf_skipover/shuttle in T.baseturfs)) #define isProbablyWallMounted(O) (O.pixel_x > 20 || O.pixel_x < -20 || O.pixel_y > 20 || O.pixel_y < -20) +#define isbook(O) (is_type_in_typecache(O, GLOB.book_types)) + +GLOBAL_LIST_INIT(book_types, typecacheof(list( + /obj/item/book, + /obj/item/spellbook, + /obj/item/storage/book))) diff --git a/code/modules/library/lib_items.dm b/code/modules/library/lib_items.dm index a4d88158e2..9d5dbe8f63 100644 --- a/code/modules/library/lib_items.dm +++ b/code/modules/library/lib_items.dm @@ -1,3 +1,7 @@ +#define BOOKCASE_UNANCHORED 0 +#define BOOKCASE_ANCHORED 1 +#define BOOKCASE_FINISHED 2 + /* Library Items * * Contains: @@ -17,69 +21,85 @@ desc = "A great place for storing knowledge." anchored = FALSE density = TRUE - opacity = 0 + opacity = FALSE resistance_flags = FLAMMABLE max_integrity = 200 armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 0) - var/state = 0 - var/list/allowed_books = list(/obj/item/book, /obj/item/spellbook, /obj/item/storage/book, /obj/item/gun/magic/wand/book) //Things allowed in the bookcase + var/state = BOOKCASE_UNANCHORED + /// When enabled, books_to_load number of random books will be generated for this bookcase when first interacted with. + var/load_random_books = FALSE + /// The category of books to pick from when populating random books. + var/random_category = null + /// How many random books to generate. + var/books_to_load = 0 /obj/structure/bookcase/examine(mob/user) . = ..() if(!anchored) . += "The bolts on the bottom are unsecured." - if(anchored) + else . += "It's secured in place with bolts." switch(state) - if(0) + if(BOOKCASE_UNANCHORED) . += "There's a small crack visible on the back panel." - if(1) + if(BOOKCASE_ANCHORED) . += "There's space inside for a wooden shelf." - if(2) + if(BOOKCASE_FINISHED) . += "There's a small crack visible on the shelf." /obj/structure/bookcase/Initialize(mapload) . = ..() if(!mapload) return - state = 2 - icon_state = "book-0" - anchored = TRUE + set_anchored(TRUE) + state = BOOKCASE_FINISHED for(var/obj/item/I in loc) - if(istype(I, /obj/item/book)) - I.forceMove(src) + if(!isbook(I)) + continue + I.forceMove(src) + update_icon() + +/obj/structure/bookcase/set_anchored(anchorvalue) + . = ..() + if(isnull(.)) + return + state = anchorvalue + if(!anchorvalue) //in case we were vareditted or uprooted by a hostile mob, ensure we drop all our books instead of having them disappear till we're rebuild. + var/atom/Tsec = drop_location() + for(var/obj/I in contents) + if(!isbook(I)) + continue + I.forceMove(Tsec) update_icon() /obj/structure/bookcase/attackby(obj/item/I, mob/user, params) switch(state) - if(0) - if(istype(I, /obj/item/wrench)) + if(BOOKCASE_UNANCHORED) + if(I.tool_behaviour == TOOL_WRENCH) if(I.use_tool(src, user, 20, volume=50)) to_chat(user, "You wrench the frame into place.") - anchored = TRUE - state = 1 - if(istype(I, /obj/item/crowbar)) + set_anchored(TRUE) + else if(I.tool_behaviour == TOOL_CROWBAR) if(I.use_tool(src, user, 20, volume=50)) to_chat(user, "You pry the frame apart.") deconstruct(TRUE) - if(1) + if(BOOKCASE_ANCHORED) if(istype(I, /obj/item/stack/sheet/mineral/wood)) var/obj/item/stack/sheet/mineral/wood/W = I if(W.get_amount() >= 2) W.use(2) to_chat(user, "You add a shelf.") - state = 2 - icon_state = "book-0" - if(istype(I, /obj/item/wrench)) + state = BOOKCASE_FINISHED + update_icon() + else if(I.tool_behaviour == TOOL_WRENCH) I.play_tool_sound(src, 100) to_chat(user, "You unwrench the frame.") - anchored = FALSE - state = 0 + set_anchored(FALSE) - if(2) + if(BOOKCASE_FINISHED) var/datum/component/storage/STR = I.GetComponent(/datum/component/storage) - if(is_type_in_list(I, allowed_books)) + if(isbook(I)) if(!user.transferItemToLoc(I, src)) return update_icon() @@ -100,26 +120,29 @@ return else name = "bookcase ([sanitize(newname)])" - else if(istype(I, /obj/item/crowbar)) + else if(I.tool_behaviour == TOOL_CROWBAR) if(contents.len) to_chat(user, "You need to remove the books first!") else I.play_tool_sound(src, 100) to_chat(user, "You pry the shelf out.") new /obj/item/stack/sheet/mineral/wood(drop_location(), 2) - state = 1 - icon_state = "bookempty" + state = BOOKCASE_ANCHORED + update_icon() else return ..() -/obj/structure/bookcase/on_attack_hand(mob/living/user, act_intent = user.a_intent, unarmed_attack_flags) - . = ..() - if(. || !istype(user)) + +/obj/structure/bookcase/on_attack_hand(mob/living/user) + if(!istype(user)) return + if(load_random_books) + create_random_books(books_to_load, src, FALSE, random_category) + load_random_books = FALSE if(contents.len) - var/obj/item/book/choice = input("Which book would you like to remove from the shelf?") as null|obj in contents + var/obj/item/book/choice = input(user, "Which book would you like to remove from the shelf?") as null|obj in sortNames(contents.Copy()) if(choice) - if(!CHECK_MOBILITY(user, MOBILITY_USE) || !in_range(loc, user)) + if(!(user.mobility_flags & MOBILITY_USE) || user.stat != CONSCIOUS || !in_range(loc, user)) return if(ishuman(user)) if(!user.get_active_held_item()) @@ -128,36 +151,25 @@ choice.forceMove(drop_location()) update_icon() -/obj/structure/bookcase/attack_ghost(mob/dead/observer/user) - . = ..() - if(!length(contents)) - to_chat(user, "It's empty!") - return - var/obj/item/book/choice = input("Which book would you like to read?") as null|obj in contents - if(choice) - if(!istype(choice)) //spellbook, cult tome, or the one weird bible storage - to_chat(user,"A mysterious force is keeping you from reading that.") - return - choice.attack_ghost(user) /obj/structure/bookcase/deconstruct(disassembled = TRUE) - new /obj/item/stack/sheet/mineral/wood(loc, 4) - for(var/obj/item/book/B in contents) - B.forceMove(get_turf(src)) - qdel(src) + var/atom/Tsec = drop_location() + new /obj/item/stack/sheet/mineral/wood(Tsec, 4) + for(var/obj/item/I in contents) + if(!isbook(I)) + continue + I.forceMove(Tsec) + return ..() /obj/structure/bookcase/update_icon_state() - icon_state = "book-[min(length(contents), 5)]" - - -/obj/structure/bookcase/manuals/medical - name = "medical manuals bookcase" - -/obj/structure/bookcase/manuals/medical/Initialize() - . = ..() - new /obj/item/book/manual/wiki/medical_cloning(src) - update_icon() + if(state == BOOKCASE_UNANCHORED || state == BOOKCASE_ANCHORED) + icon_state = "bookempty" + return + var/amount = contents.len + if(load_random_books) + amount += books_to_load + icon_state = "book-[clamp(amount, 0, 5)]" /obj/structure/bookcase/manuals/engineering @@ -198,34 +210,27 @@ var/dat //Actual page content var/due_date = 0 //Game time in 1/10th seconds var/author //Who wrote the thing, can be changed by pen or PC. It is not automatically assigned - var/unique = 0 //0 - Normal book, 1 - Should not be treated as normal book, unable to be copied, unable to be modified + var/unique = FALSE //false - Normal book, true - Should not be treated as normal book, unable to be copied, unable to be modified var/title //The real name of the book. var/window_size = null // Specific window size for the book, i.e: "1920x1080", Size x Width + /obj/item/book/attack_self(mob/user) - if(is_blind(user)) - to_chat(user, "As you are trying to read, you suddenly feel very stupid!") - return - if(ismonkey(user)) - to_chat(user, "You skim through the book but can't comprehend any of it.") + if(!user.can_read(src)) return if(dat) - show_to(user) - user.visible_message("[user] opens a book titled \"[title]\" and begins reading intently.") + user << browse("Penned by [author].
" + "[dat]", "window=book[window_size != null ? ";size=[window_size]" : ""]") + user.visible_message("[user] opens a book titled \"[title]\" and begins reading intently.") + // SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "book_nerd", /datum/mood_event/book_nerd) + onclose(user, "book") else to_chat(user, "This book is completely blank!") -/obj/item/book/attack_ghost(mob/dead/observer/O) - . = ..() - show_to(O) - -/obj/item/book/proc/show_to(mob/user) - user << browse("Penned by [author].
" + "[dat]", "window=book[window_size != null ? ";size=[window_size]" : ""]") /obj/item/book/attackby(obj/item/I, mob/user, params) if(istype(I, /obj/item/pen)) - if(is_blind(user)) - to_chat(user, " As you are trying to write on the book, you suddenly feel very stupid!") + if(user.is_blind()) + to_chat(user, "As you are trying to write on the book, you suddenly feel very stupid!") return if(unique) to_chat(user, "These pages don't seem to take the ink well! Looks like you can't modify it.") @@ -243,10 +248,10 @@ if(!user.canUseTopic(src, BE_CLOSE, literate)) return if (length(newtitle) > 20) - to_chat(user, "That title won't fit on the cover!") + to_chat(user, "That title won't fit on the cover!") return if(!newtitle) - to_chat(user, "That title is invalid.") + to_chat(user, "That title is invalid.") return else name = newtitle @@ -256,7 +261,7 @@ if(!user.canUseTopic(src, BE_CLOSE, literate)) return if(!content) - to_chat(user, "The content is invalid.") + to_chat(user, "The content is invalid.") return else dat += content @@ -265,7 +270,7 @@ if(!user.canUseTopic(src, BE_CLOSE, literate)) return if(!newauthor) - to_chat(user, "The name is invalid.") + to_chat(user, "The name is invalid.") return else author = newauthor @@ -275,34 +280,34 @@ else if(istype(I, /obj/item/barcodescanner)) var/obj/item/barcodescanner/scanner = I if(!scanner.computer) - to_chat(user, "[I]'s screen flashes: 'No associated computer found!'") + to_chat(user, "[I]'s screen flashes: 'No associated computer found!'") else switch(scanner.mode) if(0) scanner.book = src - to_chat(user, "[I]'s screen flashes: 'Book stored in buffer.'") + to_chat(user, "[I]'s screen flashes: 'Book stored in buffer.'") if(1) scanner.book = src scanner.computer.buffer_book = name - to_chat(user, "[I]'s screen flashes: 'Book stored in buffer. Book title stored in associated computer buffer.'") + to_chat(user, "[I]'s screen flashes: 'Book stored in buffer. Book title stored in associated computer buffer.'") if(2) scanner.book = src for(var/datum/borrowbook/b in scanner.computer.checkouts) if(b.bookname == name) scanner.computer.checkouts.Remove(b) - to_chat(user, "[I]'s screen flashes: 'Book stored in buffer. Book has been checked in.'") + to_chat(user, "[I]'s screen flashes: 'Book stored in buffer. Book has been checked in.'") return - to_chat(user, "[I]'s screen flashes: 'Book stored in buffer. No active check-out record found for current title.'") + to_chat(user, "[I]'s screen flashes: 'Book stored in buffer. No active check-out record found for current title.'") if(3) scanner.book = src for(var/obj/item/book in scanner.computer.inventory) if(book == src) - to_chat(user, "[I]'s screen flashes: 'Book stored in buffer. Title already present in inventory, aborting to avoid duplicate entry.'") + to_chat(user, "[I]'s screen flashes: 'Book stored in buffer. Title already present in inventory, aborting to avoid duplicate entry.'") return scanner.computer.inventory.Add(src) - to_chat(user, "[I]'s screen flashes: 'Book stored in buffer. Title added to general inventory.'") + to_chat(user, "[I]'s screen flashes: 'Book stored in buffer. Title added to general inventory.'") - else if(istype(I, /obj/item/kitchen/knife) || istype(I, /obj/item/wirecutters)) + else if(istype(I, /obj/item/kitchen/knife) || I.tool_behaviour == TOOL_WIRECUTTER) 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.") @@ -361,3 +366,8 @@ else to_chat(user, "No associated computer found. Only local scans will function properly.") to_chat(user, "\n") + + +#undef BOOKCASE_UNANCHORED +#undef BOOKCASE_ANCHORED +#undef BOOKCASE_FINISHED diff --git a/code/modules/library/lib_machines.dm b/code/modules/library/lib_machines.dm index e751861d6b..877be6788d 100644 --- a/code/modules/library/lib_machines.dm +++ b/code/modules/library/lib_machines.dm @@ -28,7 +28,7 @@ var/search_page = 0 COOLDOWN_DECLARE(library_visitor_topic_cooldown) clockwork = TRUE - + /obj/machinery/computer/libraryconsole/ui_interact(mob/user) . = ..() var/list/dat = list() // @@ -158,7 +158,8 @@ /* * Library Computer - * After 860 days, it's finally a buildable computer. + * After 860 days, it's finally a buildable computer.* + * * i cannot change maps because you are a buch of fucks who ignore map changes */ // 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. @@ -173,7 +174,7 @@ circuit = /obj/item/circuitboard/computer/libraryconsole - var/screenstate = 0 // 0 - Main Menu, 1 - Inventory, 2 - Checked Out, 3 - Check Out a Book + // var/screenstate = 0 // 0 - Main Menu, 1 - Inventory, 2 - Checked Out, 3 - Check Out a Book var/arcanecheckout = 0 var/buffer_book @@ -187,13 +188,13 @@ var/printer_cooldown = 0 COOLDOWN_DECLARE(library_console_topic_cooldown) -/obj/machinery/computer/bookmanagement/Initialize() +/obj/machinery/computer/libraryconsole/bookmanagement/Initialize() . = ..() if(circuit) circuit.name = "Book Inventory Management Console (Machine Board)" - circuit.build_path = /obj/machinery/computer/bookmanagement + circuit.build_path = /obj/machinery/computer/libraryconsole/bookmanagement -/obj/machinery/computer/bookmanagement/ui_interact(mob/user) +/obj/machinery/computer/libraryconsole/bookmanagement/ui_interact(mob/user) . = ..() var/dat = "" // switch(screenstate) @@ -324,17 +325,17 @@ popup.set_content(dat) popup.open() -/obj/machinery/computer/bookmanagement/proc/findscanner(viewrange) +/obj/machinery/computer/libraryconsole/bookmanagement/proc/findscanner(viewrange) for(var/obj/machinery/libraryscanner/S in range(viewrange, get_turf(src))) return S return null -/obj/machinery/computer/bookmanagement/proc/print_forbidden_lore(mob/user) +/obj/machinery/computer/libraryconsole/bookmanagement/proc/print_forbidden_lore(mob/user) new /obj/item/melee/cultblade/dagger(get_turf(src)) 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 sinister dagger sitting on the desk. You don't even remember where it came from...") user.visible_message("[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.", 2) -/obj/machinery/computer/bookmanagement/attackby(obj/item/W, mob/user, params) +/obj/machinery/computer/libraryconsole/bookmanagement/attackby(obj/item/W, mob/user, params) if(istype(W, /obj/item/barcodescanner)) var/obj/item/barcodescanner/scanner = W scanner.computer = src @@ -343,11 +344,11 @@ else return ..() -/obj/machinery/computer/bookmanagement/emag_act(mob/user) +/obj/machinery/computer/libraryconsole/bookmanagement/emag_act(mob/user) if(density && !(obj_flags & EMAGGED)) obj_flags |= EMAGGED -/obj/machinery/computer/bookmanagement/Topic(href, href_list) +/obj/machinery/computer/libraryconsole/bookmanagement/Topic(href, href_list) if(!COOLDOWN_FINISHED(src, library_console_topic_cooldown)) return COOLDOWN_START(src, library_console_topic_cooldown, 1 SECONDS) @@ -583,7 +584,7 @@ return ..() /obj/machinery/bookbinder/proc/bind_book(mob/user, obj/item/paper/P) - if(machine_stat) + if(stat) return if(busy) to_chat(user, "The book binder is busy. Please wait for completion of previous operation.") @@ -596,7 +597,7 @@ sleep(rand(200,400)) busy = FALSE if(P) - if(!machine_stat) + if(!stat) visible_message("[src] whirs as it prints and binds a new book.") var/obj/item/book/B = new(src.loc) B.dat = P.info diff --git a/code/modules/library/random_books.dm b/code/modules/library/random_books.dm index accd477387..d60609147a 100644 --- a/code/modules/library/random_books.dm +++ b/code/modules/library/random_books.dm @@ -10,78 +10,83 @@ /obj/item/book/random icon_state = "random_book" - var/amount = 1 - var/category = null + /// The category of books to pick from when creating this book. + var/random_category = null + /// If this book has already been 'generated' yet. + var/random_loaded = FALSE -/obj/item/book/random/Initialize() - ..() - create_random_books(amount, src.loc, TRUE, category) - return INITIALIZE_HINT_QDEL +/obj/item/book/random/Initialize(mapload) + . = ..() + icon_state = "book[rand(1,8)]" -/obj/item/book/random/triple - amount = 3 +/obj/item/book/random/attack_self() + if(!random_loaded) + create_random_books(1, loc, TRUE, random_category, src) + random_loaded = TRUE + return ..() /obj/structure/bookcase/random - var/category = null - var/book_count = 2 + load_random_books = TRUE + books_to_load = 2 icon_state = "random_bookcase" - anchored = TRUE - state = 2 /obj/structure/bookcase/random/Initialize(mapload) . = ..() - if(!book_count || !isnum(book_count)) - update_icon() - return - book_count += pick(-1,-1,0,1,1) - create_random_books(book_count, src, FALSE, category) + if(books_to_load && isnum(books_to_load)) + books_to_load += pick(-1,-1,0,1,1) update_icon() -/proc/create_random_books(amount = 2, location, fail_loud = FALSE, category = null) +/proc/create_random_books(amount, location, fail_loud = FALSE, category = null, obj/item/book/existing_book) . = list() if(!isnum(amount) || amount<1) return if (!SSdbcore.Connect()) - if(fail_loud || prob(5)) - var/obj/item/paper/P = new(location) - P.info = "There once was a book from Nantucket
But the database failed us, so f*$! it.
I tried to be good to you
Now this is an I.O.U
If you're feeling entitled, well, stuff it!

~" - P.update_icon() + if(existing_book && (fail_loud || prob(5))) + existing_book.author = "???" + existing_book.title = "Strange book" + existing_book.name = "Strange book" + existing_book.dat = "There once was a book from Nantucket
But the database failed us, so f*$! it.
I tried to be good to you
Now this is an I.O.U
If you're feeling entitled, well, stuff it!

~" return if(prob(25)) category = null - var/c = category? " AND category='[sanitizeSQL(category)]'" :"" - var/datum/DBQuery/query_get_random_books = SSdbcore.NewQuery("SELECT * FROM [format_table_name("library")] WHERE isnull(deleted)[c] GROUP BY title ORDER BY rand() LIMIT [amount];") // isdeleted copyright (c) not me + var/datum/db_query/query_get_random_books = SSdbcore.NewQuery({" + SELECT author, title, content + FROM [format_table_name("library")] + WHERE isnull(deleted) AND (:category IS NULL OR category = :category) + ORDER BY rand() LIMIT :limit + "}, list("category" = category, "limit" = amount)) if(query_get_random_books.Execute()) while(query_get_random_books.NextRow()) - var/obj/item/book/B = new(location) - . += B - B.author = query_get_random_books.item[2] - B.title = query_get_random_books.item[3] - B.dat = query_get_random_books.item[4] + var/obj/item/book/B + B = existing_book ? existing_book : new(location) + B.author = query_get_random_books.item[1] + B.title = query_get_random_books.item[2] + B.dat = query_get_random_books.item[3] B.name = "Book: [B.title]" - B.icon_state= "book[rand(1,8)]" + if(!existing_book) + B.icon_state= "book[rand(1,8)]" qdel(query_get_random_books) /obj/structure/bookcase/random/fiction name = "bookcase (Fiction)" - category = "Fiction" + random_category = "Fiction" /obj/structure/bookcase/random/nonfiction name = "bookcase (Non-Fiction)" - category = "Non-fiction" + random_category = "Non-fiction" /obj/structure/bookcase/random/religion name = "bookcase (Religion)" - category = "Religion" + random_category = "Religion" /obj/structure/bookcase/random/adult name = "bookcase (Adult)" - category = "Adult" + random_category = "Adult" /obj/structure/bookcase/random/reference name = "bookcase (Reference)" - category = "Reference" + random_category = "Reference" var/ref_book_prob = 20 /obj/structure/bookcase/random/reference/Initialize(mapload) . = ..() - while(book_count > 0 && prob(ref_book_prob)) - book_count-- + while(books_to_load > 0 && prob(ref_book_prob)) + books_to_load-- new /obj/item/book/manual/random(src)