diff --git a/code/game/machinery/doors/airlock.dm b/code/game/machinery/doors/airlock.dm
index d5144e09d0..8d0bb0400c 100644
--- a/code/game/machinery/doors/airlock.dm
+++ b/code/game/machinery/doors/airlock.dm
@@ -819,7 +819,7 @@ About the new airlock wires panel:
usr.machine = src
if (href_list["wires"])
var/t1 = text2num(href_list["wires"])
- if (!istype(usr.equipped(), /obj/item/weapon/wirecutters) || !istype(usr.equipped(),/obj/item/weapon/shard))
+ if (!(istype(usr.equipped(), /obj/item/weapon/wirecutters) || istype(usr.equipped(),/obj/item/weapon/shard)))
usr << "You need wirecutters!"
return
if (src.isWireColorCut(t1) && istype(usr.equipped(), /obj/item/weapon/wirecutters))
diff --git a/code/game/magic/archived_book.dm b/code/game/magic/archived_book.dm
index c5a589816e..8443444aa3 100644
--- a/code/game/magic/archived_book.dm
+++ b/code/game/magic/archived_book.dm
@@ -1,5 +1,5 @@
#define BOOK_VERSION_MIN 1
-#define BOOK_VERSION_MAX 1
+#define BOOK_VERSION_MAX 2
#define BOOK_PATH "data/books/"
var/global/datum/book_manager/book_mgr = new()
@@ -67,6 +67,10 @@ datum/archived_book
id // the id of the book (like an isbn number)
dat // Actual page content
+ author_real // author's real_name
+ author_key // author's byond key
+ list/icon/photos // in-game photos used
+
// loads the book corresponding by the specified id
datum/archived_book/New(var/path)
if(isnull(path))
@@ -88,6 +92,12 @@ datum/archived_book/New(var/path)
F["id"] >> id
F["dat"] >> dat
+ F["author_real"] >> author_real
+ F["author_key"] >> author_key
+ F["photos"] >> photos
+ if(!photos)
+ photos = new()
+
// let's sanitize it here too!
for(var/tag in paper_blacklist)
if(findtext(dat,"<"+tag))
@@ -105,6 +115,10 @@ datum/archived_book/proc/save()
F["id"] << id
F["dat"] << dat
+ F["author_real"] << author_real
+ F["author_key"] << author_key
+ F["photos"] << photos
+
#undef BOOK_VERSION_MIN
#undef BOOK_VERSION_MAX
#undef BOOK_PATH
diff --git a/code/game/magic/library.dm b/code/game/magic/library.dm
index aced80ed11..10ac4a1d19 100644
--- a/code/game/magic/library.dm
+++ b/code/game/magic/library.dm
@@ -73,33 +73,55 @@
var/category
New()
- var/list/books = book_mgr.getall()
- var/list/catbooks = new()
- // get books in category
- for(var/datum/archived_book/B in books)
- if(!category || (category != B.category))
- continue;
- catbooks += B
- world << "cat [category]: [B.title]"
- if(catbooks.len <= 3)
- // if 3 or less books, fill shelf with that
- for(var/datum/archived_book/AB in catbooks)
- var/obj/item/weapon/book/B = new(src)
- B.name = "Book: [AB.title]"
- B.title = AB.title
- B.author = AB.author
- B.dat = AB.dat
- B.icon_state = "book[rand(1,7)]"
- else
- // otherwise, pick 3 random books
- for(var/i = 1 to 3)
- var/datum/archived_book/AB = pick(catbooks)
- var/obj/item/weapon/book/B = new(src)
- B.name = "Book: [AB.title]"
- B.title = AB.title
- B.author = AB.author
- B.dat = AB.dat
- B.icon_state = "book[rand(1,7)]"
+ spawn(2) // allow library comp to exist
+ var/list/books = book_mgr.getall()
+ var/list/catbooks = new()
+ // see if we have a library computer
+ var/obj/machinery/librarycomp/comp
+ if(istype(loc.loc, /area))
+ comp = locate() in loc.loc
+ // get books in category
+ for(var/datum/archived_book/B in books)
+ if(!category || (category != B.category))
+ continue;
+ catbooks += B
+ if(catbooks.len <= 3)
+ // if 3 or less books, fill shelf with that
+ for(var/datum/archived_book/AB in catbooks)
+ var/obj/item/weapon/book/B = new(src)
+ B.name = "Book: [AB.title]"
+ B.title = AB.title
+ B.author = AB.author
+ B.dat = AB.dat
+ B.gen_pages()
+ B.icon_state = "book[rand(1,7)]"
+ B.ssbn = AB.id
+ B.author_real = AB.author_real
+ B.author_key = AB.author_key
+ B.photos = AB.photos
+
+ // add to inventory
+ if(comp)
+ comp.inventory += B
+ else
+ // otherwise, pick 3 random books
+ for(var/i = 1 to 3)
+ var/datum/archived_book/AB = pick(catbooks)
+ var/obj/item/weapon/book/B = new(src)
+ B.name = "Book: [AB.title]"
+ B.title = AB.title
+ B.author = AB.author
+ B.dat = AB.dat
+ B.gen_pages()
+ B.icon_state = "book[rand(1,7)]"
+ B.ssbn = AB.id
+ B.author_real = AB.author_real
+ B.author_key = AB.author_key
+ B.photos = AB.photos
+
+ // add to inventory
+ if(comp)
+ comp.inventory += B
attackby(obj/O as obj, mob/user as mob)
if(istype(O, /obj/item/weapon/book))
@@ -201,13 +223,75 @@
unique = 0 // 0 - Normal book, 1 - Should not be treated as normal book, unable to be copied, unable to be modified
title // The real name of the book.
- attack_self(var/mob/user as mob)
+ author_real // author's real_name
+ author_key // author's byond key
+ ssbn // the ssbn, if a downloaded book
+
+ list/pages = new() // individual pages as a list of text
+ cur_page = 1 // current page being read
+ list/icon/photos // in-game photos used
+
+ proc/navbar()
+ return "
" \
+ + "
"+(cur_page > 1 \
+ ? "
<" \
+ + "
<" \
+ : "") \
+ + "
"+(cur_page < pages.len \
+ ? "
>" \
+ + "
>" \
+ : "") \
+ + "
[cur_page]/[pages.len]
"
+
+ // should be called if dat is changed
+ proc/gen_pages()
+ // split into pages
+ cur_page = 1
+ pages = dd_text2list(dat, "")
+ var/PN = 1
+ for(var/page in pages)
+ // look for photos and process
+ var/i = 1
+ while(i <= lentext(page))
+ i = findtext(page, "", i+7)
+ if(e_c == 0) e_c = INFINITY
+ var/e = min(e_s, e_c) // find the closest of the two
+ if(e == INFINITY) break
+ var/i_num = text2num(copytext(page, i+7, e))
+ page = copytext(page, 1, i) + "
Penned by [author].
" + "[dat]", "window=book")
- if(title)
- user.visible_message("[user] opens a book titled \"[src.title]\" and begins reading intently.")
- else
- user.visible_message("[user] opens a book titled \"[src.name]\" and begins reading intently.")
+ cache_imgs(user)
+ user << browse("[pages[cur_page]]
[pages.len > 1 ? navbar() : ""]", "window=book;size=600x500")
+
+ if(opening)
+ if(title)
+ user.visible_message("[user] opens a book titled \"[src.title]\" and begins reading intently.")
+ else
+ user.visible_message("[user] opens a book titled \"[src.name]\" and begins reading intently.")
+
onclose(user, "book")
else
user << "This book is completely blank!"
@@ -220,11 +304,12 @@
var/choice = input("What would you like to change?") in list("Title", "Contents", "Author", "Cancel")
switch(choice)
if("Title")
- var/title = input("Write a new title:") as text|null
- if(!title)
+ var/ntitle = input("Write a new title:") as text|null
+ if(!ntitle)
return
else
- src.name = sanitize(title)
+ title = sanitize(ntitle)
+ name = "Book: [title]"
if("Contents")
var/t = "[src.dat]"
do
@@ -248,6 +333,7 @@
return
src.dat = t
+ gen_pages()
if("Author")
var/nauthor = input("Write the author's name:") as text|null
if(!nauthor)
@@ -609,9 +695,11 @@ datum/borrowbook // Datum used to keep track of who has borrowed what when and f
dat += "No scanner found within wireless network range.
"
else if(!scanner.cache)
dat += "No data found in scanner memory.
"
+ else if(scanner.cache.ssbn && (scanner.cache.author_real != user.real_name && scanner.cache.author_key != user.client.ckey))
+ dat += "This book contains copy protection preventing you from re-uploading this to the database.
"
else
- dat += "Data marked for upload...
"
- dat += "Title: [scanner.cache.name]
"
+ dat += "Data marked for [scanner.cache.ssbn ? "re-" : ""]upload...
"
+ dat += "Title: [scanner.cache.title]
"
if(!scanner.cache.author)
scanner.cache.author = "Anonymous"
dat += "Author: [scanner.cache.author]
"
@@ -704,8 +792,13 @@ datum/borrowbook // Datum used to keep track of who has borrowed what when and f
if(href_list["delbook"])
var/obj/item/weapon/book/b = locate(href_list["delbook"])
inventory.Remove(b)
+ if(href_list["settitle"])
+ var/newtitle = input("Enter the book name: ", "Book Upload", scanner.cache.title) as text|null
+ if(newtitle)
+ scanner.cache.title = sanitize(newtitle)
+ scanner.cache.name = "Book: [scanner.cache.title]"
if(href_list["setauthor"])
- var/newauthor = input("Enter the author's name: ") as text|null
+ var/newauthor = input("Enter the author's name: ", "Book Upload", scanner.cache.author) as text|null
if(newauthor)
scanner.cache.author = sanitize(newauthor)
if(href_list["setcategory"])
@@ -743,11 +836,22 @@ datum/borrowbook // Datum used to keep track of who has borrowed what when and f
dbcon.Disconnect()
else
var/datum/archived_book/B = new()
- B.title = scanner.cache.name
+ if(scanner.cache.title)
+ B.title = scanner.cache.title
+ else
+ B.title = scanner.cache.name
B.author = scanner.cache.author
B.dat = scanner.cache.dat
B.category = upload_category
- B.id = book_mgr.freeid()
+ if(scanner.cache.ssbn)
+ B.id = scanner.cache.ssbn
+ else
+ B.id = book_mgr.freeid()
+ B.author_real = scanner.cache.author_real
+ B.author_key = scanner.cache.author_key
+ if(scanner.cache.photos.len >= 8)
+ scanner.cache.photos.len = 8
+ B.photos = scanner.cache.photos
B.save()
log_game("[usr.name]/[usr.key] has uploaded the book titled [scanner.cache.name], [length(scanner.cache.dat)] signs")
@@ -775,6 +879,7 @@ datum/borrowbook // Datum used to keep track of who has borrowed what when and f
B.title = title
B.author = author
B.dat = content
+ B.gen_pages()
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
@@ -789,7 +894,11 @@ datum/borrowbook // Datum used to keep track of who has borrowed what when and f
B.title = AB.title
B.author = AB.author
B.dat = AB.dat
+ B.gen_pages()
B.icon_state = "book[rand(1,7)]"
+ B.author_real = AB.author_real
+ B.author_key = AB.author_key
+ B.photos = AB.photos
src.visible_message("[src]'s printer hums as it produces a completely bound book. How did it do that?")
if(href_list["orderbyid"])
@@ -873,18 +982,236 @@ datum/borrowbook // Datum used to keep track of who has borrowed what when and f
anchored = 1
density = 1
+ var/obj/item/weapon/book/template
+ var/list/icon_names
+ var/i_preview = 1
+ var/printing = 0
+
+ proc/newGenericBook()
+ template = new(src)
+ template.title = "Print Job #" + "[rand(100, 999)]"
+ template.author = "Anonymous"
+ template.name = "Book: [template.title]"
+ template.icon_state = "book[rand(1,7)]"
+ template.dat = ""
+ template.gen_pages()
+ template.photos = new()
+ icon_names = new()
+ i_preview = 1
+
attackby(var/obj/O as obj, var/mob/user as mob)
if(istype(O, /obj/item/weapon/paper))
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)]"
- b.icon_state = "book[rand(1,7)]"
+
+ if(template)
+ template.dat += "[O:info]"
+ template.gen_pages()
+ else
+ template = new(src)
+ template.title = O.name == initial(O.name) ? "Print Job #" + "[rand(100, 999)]" : O.name
+ template.name = "Book: [template.title]"
+ template.icon_state = "book[rand(1,7)]"
+ template.dat = O:info
+ template.gen_pages()
+ template.photos = new()
+ icon_names = new()
+ i_preview = 1
del(O)
+ updateUsrDialog()
+ else if(istype(O, /obj/item/weapon/photo))
+ user.drop_item()
+ O.loc = src
+ user.visible_message("[user] loads a photo into [src].", "You load a photo into [src].")
+
+ if(!template)
+ newGenericBook()
+ if(template.photos.len >= 8)
+ user << "\red The photo tray is already full!"
+ O.loc = src.loc
+ return
+
+ var/icon/imported = new(O.icon)
+ imported.Crop(6,8,27,27)
+ imported.Scale(32,32)
+ template.photos += imported
+ icon_names += O.name
+ del(O)
+ updateUsrDialog()
+ else if(istype(O, /obj/item/weapon/book))
+ if(template)
+ user << "\red The binder already has a book in the buffer!"
+ return
+
+ user.drop_item()
+ O.loc = src
+ user.visible_message("[user] loads a book into [src] for editing.", "You load a book into [src] for editing.")
+
+ var/obj/machinery/librarycomp/C = locate() in loc.loc
+ if(C)
+ if(C.inventory.Find(O))
+ C.inventory -= O
+ template = O
+ icon_names = new()
+ for(var/i = 1 to template.photos.len)
+ icon_names += "Photo [i]"
+ updateUsrDialog()
else
- ..()
\ No newline at end of file
+ ..()
+
+/obj/machinery/bookbinder/attack_paw(user as mob)
+ return src.attack_hand(user)
+
+/obj/machinery/bookbinder/attack_ai(user as mob)
+ return src.attack_hand(user)
+
+/obj/machinery/bookbinder/attack_hand(user as mob)
+ if(..())
+ return
+
+ if(src.stat)
+ user << "[name] does not seem to be responding to your button mashing."
+ return
+
+ var/dat = "Book BinderNT Publishing House
"
+
+ if(printing)
+ dat += "[src] is currently printing."
+ else
+ // general settings
+ dat += "New Book"
+
+ if(template)
+ template.cache_imgs(user)
+
+ dat += " Delete Book
"
+
+ dat += "Title: [template.title]
"
+ dat += "Author: [template.author]
"
+
+ // articles
+ dat += "Pages:"
+ var/ID = 1
+ for(var/P in template.pages)
+ dat += "- Page [ID]"
+ dat += " Preview"
+ dat += " Delete
"
+ ID++
+
+ dat += "
New Page
"
+
+ // images
+ dat += "Images:"
+ ID = 1
+ for(var/icon/I in template.photos)
+ dat += "- [ID]: [icon_names[ID]]"
+ dat += " Delete"
+ ID++
+ dat += "
"
+ if(i_preview >= 1 && i_preview <= template.photos.len)
+ var/iconname = "book_preview.png"
+ user << browse_rsc(template.photos[i_preview],iconname)
+ dat += "[i_preview] ([icon_names[i_preview]]):
"
+ dat += "
Print"
+
+ dat += ""
+ user << browse(dat, "window=bookbinder")
+ onclose(user, "bookbinder")
+
+
+/obj/machinery/bookbinder/Topic(href, href_list)
+ if(..())
+ return
+ usr.machine = src
+ src.add_fingerprint(usr)
+
+ if(href_list["new"])
+ if(template)
+ var/R = alert("There is already a book in the buffer. Do you want to delete it and start over?", "Book Binder", "Delete", "Cancel")
+ if(R == "Cancel")
+ return
+ del(template)
+ newGenericBook()
+ updateUsrDialog()
+ if(href_list["delete"])
+ if(template)
+ del(template)
+ updateUsrDialog()
+ if(href_list["print"])
+ printing = 1
+ updateUsrDialog()
+ src.visible_message("[src] begins to hum as it warms up its printing drums.")
+ sleep(rand(50,100))
+ src.visible_message("[src] whirs as it prints and binds a new book.")
+
+ if(!template.ssbn)
+ template.author_real = usr.real_name
+ if(usr.client)
+ template.author_key = usr.client.ckey
+ template.loc = src.loc
+ template = null
+ printing = 0
+ updateUsrDialog()
+ if(href_list["p_new"])
+ template.dat += ""
+ template.gen_pages()
+ updateUsrDialog()
+
+ if(href_list["title"])
+ var/n_name = input(usr, "What would you like to title your book?", "Book Binder", template.title) as text|null
+ if(n_name)
+ template.title = sanitize(n_name)
+ template.name = "Book: [template.title]"
+ updateUsrDialog()
+ if(href_list["author"])
+ var/n_name = input(usr, "Who would you like your pen name to be?", "Book Binder", template.author) as text|null
+ if(n_name)
+ template.author = sanitize(n_name)
+ updateUsrDialog()
+
+ if(href_list["p_edit"])
+ var/PN = text2num(href_list["p_edit"])
+ var/list/pages = dd_text2list(template.dat, "")
+ var/t = pages[PN]
+ do
+ t = input(usr, "What text do you wish to add?", "Book Binder P.[PN]", t) as message
+
+ if(lentext(t) >= MAX_BOOK_MESSAGE_LEN)
+ var/cont = input(usr, "Your message is too long! Would you like to continue editing it?", "", "yes") in list("yes", "no")
+ if(cont == "no")
+ break
+ while(lentext(t) > MAX_BOOK_MESSAGE_LEN)
+
+ // check for exploits
+ for(var/tag in paper_blacklist)
+ if(findtext(t,"<"+tag))
+ usr << "\blue You think to yourself, \"Hm.. this is only paper...\""
+ return
+
+ // the actual pages list is formatted and shouldn't be directly editted
+ pages[PN] = t
+ template.dat = dd_list2text(pages, "")
+ template.gen_pages()
+ updateUsrDialog()
+ if(href_list["p_delete"])
+ var/i = text2num(href_list["p_delete"])
+ var/list/pages = dd_text2list(template.dat, "")
+ pages.Cut(i, i+1)
+ template.dat = dd_list2text(pages, "")
+ template.gen_pages()
+ updateUsrDialog()
+ if(href_list["p_preview"])
+ var/i = text2num(href_list["p_preview"])
+ var/dat = template.pages[i]
+ usr << browse(dat, "window=bookbinder_preview;size=600x500")
+ onclose(usr, "bookbinder_preview")
+
+ if(href_list["i_view"])
+ i_preview = text2num(href_list["i_view"])
+ updateUsrDialog()
+ if(href_list["i_delete"])
+ var/i = text2num(href_list["i_delete"])
+ template.photos.Cut(i, i+1)
+ icon_names.Cut(i, i+1)
+ updateUsrDialog()
diff --git a/code/game/objects/items/weapons/RCD.dm b/code/game/objects/items/weapons/RCD.dm
index ecb0b24d9a..9df99a5817 100644
--- a/code/game/objects/items/weapons/RCD.dm
+++ b/code/game/objects/items/weapons/RCD.dm
@@ -119,7 +119,7 @@ RCD
desc = "A RCD. It currently holds [matter]/[max_matter] matter-units."
return
else if(mode == 3 && (istype(A, /turf) || istype(A, /obj/machinery/door/airlock) ) )
- if(istype(A, /turf/simulated/wall) && matter >= 4)
+ if(istype(A, /turf/simulated/wall) && !istype(A, /turf/simulated/wall/r_wall) && matter >= 4)
user << "Deconstructing Wall (4)..."
playsound(src.loc, 'click.ogg', 50, 1)
if(do_after(user, 40))
diff --git a/code/game/vote.dm b/code/game/vote.dm
index 7741f2c182..cc75bde849 100644
--- a/code/game/vote.dm
+++ b/code/game/vote.dm
@@ -362,6 +362,7 @@
return
//world << "You're voting for [N] options!"
var/i
+ vote.choices = list()
for(i=1; i<=N; i++)
var/addvote = input(usr, "What is option #[i]?", "Enter Option #[i]") as text
vote.choices += addvote
diff --git a/code/modules/admin/admin.dm b/code/modules/admin/admin.dm
index 8fa08fea4e..fac90da513 100644
--- a/code/modules/admin/admin.dm
+++ b/code/modules/admin/admin.dm
@@ -2240,6 +2240,7 @@
return
var/i
+ vote.choices = list()
for(i=1; i<=N; i++)
var/addvote = input(usr, "What is option #[i]?", "Enter Option #[i]") as text
vote.choices += addvote
diff --git a/code/modules/chemical/Chemistry-Reagents.dm b/code/modules/chemical/Chemistry-Reagents.dm
index 52062de5e8..8bafb59cc9 100644
--- a/code/modules/chemical/Chemistry-Reagents.dm
+++ b/code/modules/chemical/Chemistry-Reagents.dm
@@ -1822,7 +1822,7 @@ datum
coco
name = "Coco Powder"
- id = "Coco Powder"
+ id = "coco"
description = "A fatty, bitter paste made from coco beans."
reagent_state = SOLID
nutriment_factor = 5 * REAGENTS_METABOLISM
diff --git a/code/modules/power/apc.dm b/code/modules/power/apc.dm
index d05272e903..11bcdc5bb1 100644
--- a/code/modules/power/apc.dm
+++ b/code/modules/power/apc.dm
@@ -873,8 +873,8 @@
/obj/machinery/power/apc/proc/malfoccupy(var/mob/living/silicon/ai/malf)
if(!istype(malf))
return
- if(src.z != 1)
- return
+ if(src.z != 1)
+ return
src.occupant = new /mob/living/silicon/ai(src,malf.laws,null,1)
src.occupant.adjustOxyLoss(malf.getOxyLoss())