From f3fbda739ffcacb1071358c3f0f73299788085ba Mon Sep 17 00:00:00 2001
From: MistakeNot4892 <AGMogett@gmail.com>
Date: Sun, 8 May 2022 16:37:58 +1000
Subject: [PATCH] Refactoring skill book use to allow for adjacent reading.

---
 code/game/objects/items/books/skill_book.dm | 154 ++++++++++++++------
 1 file changed, 109 insertions(+), 45 deletions(-)

diff --git a/code/game/objects/items/books/skill_book.dm b/code/game/objects/items/books/skill_book.dm
index 3908408902..4923de1152 100644
--- a/code/game/objects/items/books/skill_book.dm
+++ b/code/game/objects/items/books/skill_book.dm
@@ -38,16 +38,16 @@ Skill books that increase your skills while you activate and hold them
 	author = "The Oracle of Bakersroof"
 	icon_state = "book2"
 	force = 4
-	w_class = ITEM_SIZE_LARGE //Skill books are THICC with knowledge. Up one level from regular books to prevent library-in-a-bag silliness.
+	w_class = ITEM_SIZE_LARGE            // Skill books are THICC with knowledge. Up one level from regular books to prevent library-in-a-bag silliness.
 	unique = TRUE
 	material = /decl/material/solid/plastic
 	matter = list(/decl/material/solid/wood = MATTER_AMOUNT_REINFORCEMENT)
 
-	var/decl/hierarchy/skill/skill // e.g. SKILL_LITERACY
-	var/skill_req = SKILL_NONE //The level the user needs in the skill to benefit from the book, e.g. SKILL_PROF
-	var/reading = FALSE //Tto check if the book is actively being used
-	var/custom = FALSE //To bypass init stuff, for player made textbooks and weird books. If true must have details manually set
-	var/ez_read = FALSE //Set to TRUE if you can read it without basic literacy skills
+	var/decl/hierarchy/skill/skill       // e.g. SKILL_LITERACY
+	var/skill_req = SKILL_NONE           // The level the user needs in the skill to benefit from the book, e.g. SKILL_PROF
+	var/weakref/reading                  // To check if the book is actively being used
+	var/custom = FALSE                   // To bypass init stuff, for player made textbooks and weird books. If true must have details manually set
+	var/ez_read = FALSE                  // Set to TRUE if you can read it without basic literacy skills
 
 	var/skill_name = "missing skill name"
 	var/progress = SKILLBOOK_PROG_FINISH // used to track the progress of making a custom book. defaults as finished so, you know, you can read the damn thing
@@ -56,6 +56,8 @@ Skill books that increase your skills while you activate and hold them
 
 	. = ..()
 
+	global.events_repository.register(/decl/observ/moved, src, src, .proc/check_buff)
+
 	if(!custom && skill && skill_req)// custom books should already have all they need
 		skill_name = initial(skill.name)
 		title = RANDOM_BOOK_TITLE(capitalize(skill_name))
@@ -82,44 +84,95 @@ Skill books that increase your skills while you activate and hold them
 /datum/skill_buff/skill_book
 	limit = 1 // you can only read one book at a time nerd, therefore you can only get one buff at a time
 
-/obj/item/book/skill/attack_self(mob/user)
+/obj/item/book/skill/get_single_monetary_worth()
+	. = max(..(), 200) + (100 * skill_req)
+
+/obj/item/book/skill/proc/check_can_read(mob/user)
+	if(QDELETED(user))
+		return FALSE
+	var/effective_title = length(title) ? title : "the textbook"
+	if(!CanPhysicallyInteract(user))
+		to_chat(user, SPAN_WARNING("You can't reach [effective_title]!"))
+		return FALSE
 	if(!skill || (custom && progress == SKILLBOOK_PROG_NONE))
-		to_chat(user, SPAN_WARNING("The textbook is blank!"))
-		return
+		to_chat(user, SPAN_WARNING("[capitalize(effective_title)] is blank!"))
+		return FALSE
 	if(custom && progress < SKILLBOOK_PROG_FINISH)
-		to_chat(user, SPAN_WARNING("The textbook is unfinished! You can't learn from it in this state!"))
-		return
+		to_chat(user, SPAN_WARNING("[capitalize(effective_title)] is unfinished! You can't learn from it in this state!"))
+		return FALSE
 	if(!ez_read &&!user.skill_check(SKILL_LITERACY, SKILL_BASIC))
-		to_chat(user, SPAN_WARNING(pick("Haha, you know you can't read. Good joke. Put [title] back.","You open up [title], but there aren't any pictures, so you close it again.","You don't know how to read! What good is this [name] to you?!")))
-		return
+		to_chat(user, SPAN_WARNING(pick(list(
+			"Haha, you know you can't read. Good joke. Put [effective_title] back.",
+			"You open up [effective_title], but there aren't any pictures, so you close it again.",
+			"You don't know how to read! What good is [effective_title] to you?!"
+		))))
+		return FALSE
 
-	if(reading) //Close book, get rid of buffs
-		src.unlearn(user)
-		to_chat(user, SPAN_NOTICE("You close the [name]. That's enough learning for now."))
-		reading = FALSE
-		return
+	if(reading)
+		if(reading.resolve() != user)
+			to_chat(user, SPAN_WARNING("\The [reading.resolve()] is already reading [effective_title]!"))
+		else
+			to_chat(user, SPAN_WARNING("You are already reading [effective_title]!"))
+		return FALSE
 
 	if(user.too_many_buffs(/datum/skill_buff/skill_book))
 		to_chat(user, SPAN_WARNING("You can't read two books at once!"))
-		return
+		return FALSE
 
 	if(!user.skill_check(skill, skill_req))
-		to_chat(user, SPAN_WARNING("[title] is too advanced for you! Try something easier, perhaps the \"For Idiots\" edition?"))
-		return
+		to_chat(user, SPAN_WARNING("[capitalize(title)] is too advanced for you! Try something easier, perhaps the \"For Idiots\" edition?"))
+		return FALSE
+
 	if(user.get_skill_value(skill) > skill_req)
-		to_chat(user, SPAN_WARNING("You already know everything [title] has to teach you!"))
-		return
+		to_chat(user, SPAN_WARNING("You already know everything [effective_title] has to teach you!"))
+		return FALSE
 
-	to_chat(user, SPAN_NOTICE("You open up the [name] and start reading..."))
-	if(user.do_skilled(4 SECONDS, SKILL_LITERACY, src, 0.75))
-		var/list/buff = list()
-		buff[skill] = 1
-		user.buff_skill(buff, buff_type = /datum/skill_buff/skill_book)
-		reading = TRUE
-		to_chat(user, SPAN_NOTICE("You find the information you need! Better keep the page open to reference it."))
-	else
-		to_chat(user, SPAN_DANGER("Your perusal of the [name] was interrupted!"))
-		return
+	return TRUE
+
+/obj/item/book/skill/attack_self(mob/user)
+	return try_to_read(user) || ..()
+
+/obj/item/book/skill/verb/read_book()
+	set name = "Read Book"
+	set category = "Object"
+	set src in view(1)
+	try_to_read(usr)
+
+/obj/item/book/skill/proc/try_to_read(mob/user)
+
+	if(istype(user, /mob/observer))
+		to_chat(user, SPAN_WARNING("Ghosts can't read! Go away!"))
+		return TRUE
+
+	if(isturf(loc))
+		user.face_atom(src)
+
+	if(user && user == reading?.resolve())
+		//Close book, get rid of buffs
+		unlearn(user)
+		to_chat(user, SPAN_NOTICE("You close [title]. That's enough learning for now."))
+		reading = null
+		STOP_PROCESSING(SSprocessing, src)
+		return TRUE
+
+	if(!check_can_read(user))
+		return FALSE
+
+	to_chat(user, SPAN_NOTICE("You open up [title] and start reading..."))
+	if(!user.do_skilled(4 SECONDS, SKILL_LITERACY, src, 0.75))
+		to_chat(user, SPAN_DANGER("Your perusal of [title] was interrupted!"))
+		return TRUE
+
+	if(!check_can_read(user))
+		return TRUE
+
+	var/list/buff = list()
+	buff[skill] = 1
+	user.buff_skill(buff, buff_type = /datum/skill_buff/skill_book)
+	reading = weakref(user)
+	to_chat(user, SPAN_NOTICE("You find the information you need! Better keep the page open to reference it."))
+	START_PROCESSING(SSprocessing, src)
+	return TRUE
 
 // buff removal
 /obj/item/book/skill/proc/unlearn(var/mob/user)
@@ -127,19 +180,30 @@ Skill books that increase your skills while you activate and hold them
 	for(var/datum/skill_buff/skill_book/S in F)
 		S.remove()
 
-// Remove buffs when book goes away
-/obj/item/book/skill/dropped(mob/user)
-	if(reading)
-		to_chat(user, SPAN_DANGER("You lose the page you were on! You can't cross-reference using the [name] like this!"))
-		var/mob/M = user
-		if(istype(M) && M.fetch_buffs_of_type(/datum/skill_buff/skill_book, 0))
-			src.unlearn(user)
-		reading = FALSE
-	. = ..()
+/obj/item/book/skill/Process()
+	if(!reading)
+		return PROCESS_KILL
+	check_buff()
+
+/obj/item/book/skill/proc/check_buff()
+	if(!reading)
+		return
+	var/mob/R = reading.resolve()
+	if(!istype(R) || !CanPhysicallyInteract(R))
+		remove_buff()
+
+/obj/item/book/skill/proc/remove_buff()
+	var/mob/R = reading?.resolve()
+	reading = null
+	if(istype(R))
+		to_chat(R, SPAN_DANGER("You lose the page you were on! You can't cross-reference using [title] like this!"))
+		if(R.fetch_buffs_of_type(/datum/skill_buff/skill_book, 0))
+			unlearn(R)
+	STOP_PROCESSING(SSprocessing, src)
+
 /obj/item/book/skill/Destroy()
-	var/mob/M = loc
-	if(istype(M) && M.fetch_buffs_of_type(/datum/skill_buff/skill_book, 0))
-		src.unlearn(M)
+	global.events_repository.unregister(/decl/observ/moved, src, src)
+	remove_buff()
 	. = ..()
 
 /obj/item/book/skill/get_codex_value()