Files
Bubberstation/code/modules/library/book.dm
SkyratBot eb96261bf7 [MIRROR] New illiterate quirk [MDB IGNORE] (#13846)
* New illiterate quirk

* Update health_analyzer.dm

* Update health_analyzer.dm

Co-authored-by: Tim <timothymtorres@gmail.com>
Co-authored-by: Zonespace <41448081+Zonespace27@users.noreply.github.com>
Co-authored-by: Gandalf <9026500+Gandalf2k15@users.noreply.github.com>
2022-05-24 16:33:47 +01:00

229 lines
8.7 KiB
Plaintext

//Some information about how html sanitization is handled
//All book info datums should store sanitized data. This cannot be worked around
//All inputs and outputs from the round (DB calls) need to use sanitized data
//All tgui menus should get unsanitized data, since jsx handles that on its own
//Everything else should use sanitized data. Yes including names, it's an xss vuln because of how chat works
///A datum which contains all the metadata of a book
/datum/book_info
///The title of the book
var/title
///The "author" of the book
var/author
///The info inside the book
var/content
/datum/book_info/New(_title, _author, _content)
title = _title
author = _author
content = _content
/datum/book_info/proc/set_title(_title, trusted = FALSE) //Trusted should only be used for books read from the db, or in cases that we can be sure the info has already been sanitized
if(trusted)
title = _title
return
title = reject_bad_text(trim(html_encode(_title), 30))
/datum/book_info/proc/get_title(default="N/A") //Loads in an html decoded version of the title. Only use this for tgui menus, absolutely nothing else.
return html_decode(title) || "N/A"
/datum/book_info/proc/set_author(_author, trusted = FALSE)
if(trusted)
author = _author
return
author = trim(html_encode(_author), MAX_NAME_LEN)
/datum/book_info/proc/get_author(default="N/A")
return html_decode(author) || "N/A"
/datum/book_info/proc/set_content(_content, trusted = FALSE)
if(trusted)
content = _content
return
content = trim(html_encode(_content), MAX_PAPER_LENGTH)
/datum/book_info/proc/get_content(default="N/A")
return html_decode(content) || "N/A"
///Returns a copy of the book_info datum
/datum/book_info/proc/return_copy()
var/datum/book_info/copycat = new(title, author, content)
return copycat
///Modify an existing book_info datum to match your data
/datum/book_info/proc/copy_into(datum/book_info/copycat)
copycat.set_title(title, trusted = TRUE)
copycat.set_author(author, trusted = TRUE)
copycat.set_content(content, trusted = TRUE)
return copycat
/datum/book_info/proc/compare(datum/book_info/cmp_with)
if(author != cmp_with.author)
return FALSE
if(title != cmp_with.title)
return FALSE
if(content != cmp_with.content)
return FALSE
return TRUE
/obj/item/book
name = "book"
icon = 'icons/obj/library.dmi'
icon_state ="book"
worn_icon_state = "book"
desc = "Crack it open, inhale the musk of its pages, and learn something new."
throw_speed = 1
throw_range = 5
w_class = WEIGHT_CLASS_NORMAL //upped to three because books are, y'know, pretty big. (and you could hide them inside eachother recursively forever)
attack_verb_continuous = list("bashes", "whacks", "educates")
attack_verb_simple = list("bash", "whack", "educate")
resistance_flags = FLAMMABLE
drop_sound = 'sound/items/handling/book_drop.ogg'
pickup_sound = 'sound/items/handling/book_pickup.ogg'
///Game time in 1/10th seconds
var/due_date = 0
///false - Normal book, true - Should not be treated as normal book, unable to be copied, unable to be modified
var/unique = FALSE
/// Specific window size for the book, i.e: "1920x1080", Size x Width
var/window_size = null
///The initial title, for use in var editing and such
var/starting_title
///The initial author, for use in var editing and such
var/starting_author
///The initial bit of content, for use in var editing and such
var/starting_content
///The packet of information that describes this book
var/datum/book_info/book_data
///Maximum icon state number
var/maximum_book_state = 8
/obj/item/book/Initialize()
. = ..()
book_data = new(starting_title, starting_author, starting_content)
/obj/item/book/proc/on_read(mob/user)
if(book_data?.content)
user << browse("<meta charset=UTF-8><TT><I>Penned by [book_data.author].</I></TT> <BR>" + "[book_data.content]", "window=book[window_size != null ? ";size=[window_size]" : ""]")
LAZYINITLIST(user.mind?.book_titles_read)
var/has_not_read_book = isnull(user.mind?.book_titles_read[starting_title])
if(has_not_read_book) // any new books give bonus mood
SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "book_nerd", /datum/mood_event/book_nerd)
user.mind?.book_titles_read[starting_title] = TRUE
onclose(user, "book")
else
to_chat(user, span_notice("This book is completely blank!"))
/// Generates a random icon state for the book
/obj/item/book/proc/gen_random_icon_state()
icon_state = "book[rand(1, maximum_book_state)]"
/obj/item/book/attack_self(mob/user)
if(user.is_blind())
to_chat(user, span_warning("You are blind and can't read anything!"))
return
if(!user.can_read(src))
return
user.visible_message(span_notice("[user] opens a book titled \"[book_data.title]\" and begins reading intently."))
on_read(user)
/obj/item/book/attackby(obj/item/I, mob/user, params)
if(istype(I, /obj/item/pen))
if(!user.canUseTopic(src, BE_CLOSE) || !user.can_write(I))
return
if(user.is_blind())
to_chat(user, span_warning("As you are trying to write on the book, you suddenly feel very stupid!"))
return
if(unique)
to_chat(user, span_warning("These pages don't seem to take the ink well! Looks like you can't modify it."))
return
var/choice = tgui_input_list(usr, "What would you like to change?", "Book Alteration", list("Title", "Contents", "Author", "Cancel"))
if(isnull(choice))
return
if(!user.canUseTopic(src, BE_CLOSE) || !user.can_write(I))
return
switch(choice)
if("Title")
var/newtitle = reject_bad_text(tgui_input_text(user, "Write a new title", "Book Title", max_length = 30))
if(!user.canUseTopic(src, BE_CLOSE) || !user.can_write(I))
return
if (length_char(newtitle) > 30)
to_chat(user, span_warning("That title won't fit on the cover!"))
return
if(!newtitle)
to_chat(user, span_warning("That title is invalid."))
return
name = newtitle
book_data.set_title(html_decode(newtitle)) //Don't want to double encode here
if("Contents")
var/content = tgui_input_text(user, "Write your book's contents (HTML NOT allowed)", "Book Contents", multiline = TRUE)
if(!user.canUseTopic(src, BE_CLOSE) || !user.can_write(I))
return
if(!content)
to_chat(user, span_warning("The content is invalid."))
return
book_data.set_content(html_decode(content))
if("Author")
var/author = tgui_input_text(user, "Write the author's name", "Author Name")
if(!user.canUseTopic(src, BE_CLOSE) || !user.can_write(I))
return
if(!author)
to_chat(user, span_warning("The name is invalid."))
return
book_data.set_author(html_decode(author)) //Setting this encodes, don't want to double up
else
return
else if(istype(I, /obj/item/barcodescanner))
var/obj/item/barcodescanner/scanner = I
var/obj/machinery/computer/libraryconsole/bookmanagement/computer = scanner.computer_ref?.resolve()
if(!computer)
to_chat(user, span_alert("[scanner]'s screen flashes: 'No associated computer found!'"))
return ..()
scanner.book_data = book_data.return_copy()
switch(scanner.mode)
if(0)
to_chat(user, span_notice("[scanner]'s screen flashes: 'Book stored in buffer.'"))
if(1)
computer.buffer_book = book_data.return_copy()
to_chat(user, span_notice("[scanner]'s screen flashes: 'Book stored in buffer. Book title stored in associated computer buffer.'"))
if(2)
var/list/checkouts = computer.checkouts
for(var/checkout_ref in checkouts)
var/datum/borrowbook/maybe_ours = checkouts[checkout_ref]
if(!book_data.compare(maybe_ours.book_data))
continue
checkouts -= checkout_ref
computer.checkout_update()
to_chat(user, span_notice("[scanner]'s screen flashes: 'Book stored in buffer. Book has been checked in.'"))
return
to_chat(user, span_notice("[scanner]'s screen flashes: 'Book stored in buffer. No active check-out record found for current title.'"))
if(3)
var/datum/book_info/our_copy = book_data.return_copy()
computer.inventory[ref(our_copy)] = our_copy
computer.inventory_update()
to_chat(user, span_notice("[scanner]'s screen flashes: 'Book stored in buffer. Title added to general inventory.'"))
else if((istype(I, /obj/item/knife) || I.tool_behaviour == TOOL_WIRECUTTER) && !(flags_1 & HOLOGRAM_1))
to_chat(user, span_notice("You begin to carve out [book_data.title]..."))
if(do_after(user, 30, target = src))
to_chat(user, span_notice("You carve out the pages from [book_data.title]! You didn't want to read it anyway."))
var/obj/item/storage/book/carved_out = new
carved_out.name = src.name
carved_out.title = book_data.title
carved_out.icon_state = src.icon_state
if(user.is_holding(src))
qdel(src)
user.put_in_hands(carved_out)
return
else
carved_out.forceMove(drop_location())
qdel(src)
return
return
else
..()