Repository: runelite/runelite (java)

Issue:
Kourend Library Plugin broken
### Type

Incorrect behavior

### Operating System

Windows

### OS Version

_No response_

### Bug description

Recent Kourend changes broke the Kourend Library plugin and it gives incorrect information about basically every book. This was presumably caused by the removal of Dark Manuscripts.

### Screenshots or videos

_No response_

### RuneLite version

Most recent

### Logs

_No response_

Pull request description:
kourendlibrary: remove dark manuscripts
Fixes #17358

Patch:
diff --git a/runelite-api/src/main/java/net/runelite/api/AnimationID.java b/runelite-api/src/main/java/net/runelite/api/AnimationID.java
index 57f12ce77..4dcba5687 100644
--- a/runelite-api/src/main/java/net/runelite/api/AnimationID.java
+++ b/runelite-api/src/main/java/net/runelite/api/AnimationID.java
@@ -261,6 +261,8 @@ public final class AnimationID
 	public static final int CHURN_MILK_SHORT = 2793;
 	public static final int CHURN_MILK_MEDIUM = 2794;
 	public static final int CHURN_MILK_LONG = 2795;
+	public static final int CLEANING_SPECIMENS_1 = 6217;
+	public static final int CLEANING_SPECIMENS_2 = 6459;
 
 	// Ectofuntus animations
 	public static final int ECTOFUNTUS_FILL_SLIME_BUCKET = 4471;
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/EmoteClue.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/EmoteClue.java
index 977bd9f03..6b760d29f 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/EmoteClue.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/EmoteClue.java
@@ -95,6 +95,14 @@ public class EmoteClue extends ClueScroll implements LocationClueScroll
 		ItemVariationMapping.getVariations(PHARAOHS_SCEPTRE).stream()
 			.map(ItemRequirements::item)
 			.toArray(SingleItemRequirement[]::new));
+	private static final AnyRequirementCollection ANY_TEAM_CAPE = any("Any team cape",
+		Stream.of(
+			ItemVariationMapping.getVariations(TEAM1_CAPE).stream(),
+			Stream.of(TEAM_CAPE_I, TEAM_CAPE_X, TEAM_CAPE_ZERO))
+			.reduce(Stream::concat)
+			.orElseGet(Stream::empty)
+			.map(ItemRequirements::item)
+			.toArray(SingleItemRequirement[]::new));
 	static final AnyRequirementCollection ACTIVE_CRYSTAL_BOW_OR_BOW_OF_FAERDHINEN = any("Crystal Bow or Bow of Faerdhinen",
 		Stream.of(
 			ItemVariationMapping.getVariations(BOW_OF_FAERDHINEN_INACTIVE).stream(),
@@ -107,7 +115,7 @@ public class EmoteClue extends ClueScroll implements LocationClueScroll
 
 	static final List<EmoteClue> CLUES = ImmutableList.of(
 		new EmoteClue("Beckon on the east coast of the Kharazi Jungle. Beware of double agents! Equip any vestment stole and a heraldic rune shield.", "Kharazi Jungle", NORTHEAST_CORNER_OF_THE_KHARAZI_JUNGLE, new WorldPoint(2954, 2933, 0), DOUBLE_AGENT_108, BECKON, any("Any stole", item(GUTHIX_STOLE), item(SARADOMIN_STOLE), item(ZAMORAK_STOLE), item(ARMADYL_STOLE), item(BANDOS_STOLE), item(ANCIENT_STOLE)), any("Any heraldic rune shield", item(RUNE_SHIELD_H1), item(RUNE_SHIELD_H2), item(RUNE_SHIELD_H3), item(RUNE_SHIELD_H4), item(RUNE_SHIELD_H5))),
-		new EmoteClue("Cheer in the Barbarian Agility Arena. Headbang before you talk to me. Equip a steel platebody, maple shortbow and a Wilderness cape.", "Barbarian Outpost", BARBARIAN_OUTPOST_OBSTACLE_COURSE, new WorldPoint(2552, 3556, 0), CHEER, HEADBANG, item(STEEL_PLATEBODY), item(MAPLE_SHORTBOW), range("Any team cape", TEAM1_CAPE, TEAM50_CAPE)),
+		new EmoteClue("Cheer in the Barbarian Agility Arena. Headbang before you talk to me. Equip a steel platebody, maple shortbow and a Wilderness cape.", "Barbarian Outpost", BARBARIAN_OUTPOST_OBSTACLE_COURSE, new WorldPoint(2552, 3556, 0), CHEER, HEADBANG, item(STEEL_PLATEBODY), item(MAPLE_SHORTBOW), ANY_TEAM_CAPE),
 		new EmoteClue("Bow upstairs in the Edgeville Monastery. Equip a completed prayer book.", "Edgeville Monastery", SOUTHEAST_CORNER_OF_THE_MONASTERY, new WorldPoint(3056, 3484, 1), BOW, any("Any god book", item(HOLY_BOOK), item(BOOK_OF_BALANCE), item(UNHOLY_BOOK), item(BOOK_OF_LAW), item(BOOK_OF_WAR), item(BOOK_OF_DARKNESS), item(HOLY_BOOK_OR), item(BOOK_OF_BALANCE_OR), item(UNHOLY_BOOK_OR), item(BOOK_OF_LAW_OR), item(BOOK_OF_WAR_OR), item(BOOK_OF_DARKNESS_OR))),
 		new EmoteClue("Cheer in the Shadow dungeon. Equip a rune crossbow, climbing boots and any mitre.", "Shadow dungeon", ENTRANCE_OF_THE_CAVE_OF_DAMIS, new WorldPoint(2629, 5071, 0), CHEER, any("Any mitre", item(GUTHIX_MITRE), item(SARADOMIN_MITRE), item(ZAMORAK_MITRE), item(ANCIENT_MITRE), item(BANDOS_MITRE), item(ARMADYL_MITRE)), any("Rune crossbow", item(RUNE_CROSSBOW), item(RUNE_CROSSBOW_OR)), any("Climbing boots", item(CLIMBING_BOOTS), item(CLIMBING_BOOTS_G)), any("Ring of visibility or ring of shadows", item(RING_OF_VISIBILITY), item(RING_OF_SHADOWS), item(RING_OF_SHADOWS_UNCHARGED))),
 		new EmoteClue("Cheer at the top of the agility pyramid. Beware of double agents! Equip a blue mystic robe top, and any rune heraldic shield.", "Agility Pyramid", AGILITY_PYRAMID, new WorldPoint(3043, 4697, 3), DOUBLE_AGENT_108, CHEER, item(MYSTIC_ROBE_TOP), any("Any rune heraldic shield", item(RUNE_SHIELD_H1), item(RUNE_SHIELD_H2), item(RUNE_SHIELD_H3), item(RUNE_SHIELD_H4), item(RUNE_SHIELD_H5))),
@@ -211,7 +219,7 @@ public class EmoteClue extends ClueScroll implements LocationClueScroll
 		new EmoteClue("Yawn in the 7th room of Pyramid Plunder. Beware of double agents! Equip a pharaoh sceptre and a full set of menaphite robes.", "Pyramid Plunder", _7TH_CHAMBER_OF_JALSAVRAH, new WorldPoint(1944, 4427, 0), DOUBLE_AGENT_141, YAWN, ANY_PHARAOHS_SCEPTRE, any("Full set of menaphite robes", all(item(MENAPHITE_PURPLE_HAT), item(MENAPHITE_PURPLE_TOP), range(MENAPHITE_PURPLE_ROBE, MENAPHITE_PURPLE_KILT)), all(item(MENAPHITE_RED_HAT), item(MENAPHITE_RED_TOP), range(MENAPHITE_RED_ROBE, MENAPHITE_RED_KILT)))),
 		new EmoteClue("Yawn in the Varrock library. Equip a green gnome robe top, HAM robe bottom and an iron warhammer.", "Varrock Castle", VARROCK_PALACE_LIBRARY, new WorldPoint(3209, 3492, 0), YAWN, item(GREEN_ROBE_TOP), item(HAM_ROBE), item(IRON_WARHAMMER)),
 		new EmoteClue("Yawn in Draynor marketplace. Equip studded leather chaps, an iron kiteshield and a steel longsword.", "Draynor", DRAYNOR_VILLAGE_MARKET, new WorldPoint(3083, 3253, 0), YAWN, item(STUDDED_CHAPS), item(IRON_KITESHIELD), item(STEEL_LONGSWORD)),
-		new EmoteClue("Yawn in the Castle Wars lobby. Shrug before you talk to me. Equip a ruby amulet, a mithril scimitar and a Wilderness cape.", "Castle Wars", CASTLE_WARS_BANK, new WorldPoint(2440, 3092, 0), YAWN, SHRUG, item(RUBY_AMULET), item(MITHRIL_SCIMITAR), range("Any team cape", TEAM1_CAPE, TEAM50_CAPE)),
+		new EmoteClue("Yawn in the Castle Wars lobby. Shrug before you talk to me. Equip a ruby amulet, a mithril scimitar and a Wilderness cape.", "Castle Wars", CASTLE_WARS_BANK, new WorldPoint(2440, 3092, 0), YAWN, SHRUG, item(RUBY_AMULET), item(MITHRIL_SCIMITAR), ANY_TEAM_CAPE),
 		new EmoteClue("Yawn in the rogues' general store. Beware of double agents! Equip an adamant square shield, blue dragon vambraces and a rune pickaxe.", "Rogues general store", NOTERAZZOS_SHOP_IN_THE_WILDERNESS, new WorldPoint(3026, 3701, 0), DOUBLE_AGENT_65, YAWN, item(ADAMANT_SQ_SHIELD), item(BLUE_DHIDE_VAMBRACES), item(RUNE_PICKAXE)),
 		new EmoteClue("Yawn at the top of Trollheim. Equip a lava battlestaff, black dragonhide vambraces and a mind shield.", "Trollheim Mountain", ON_TOP_OF_TROLLHEIM_MOUNTAIN, new WorldPoint(2887, 3676, 0), YAWN, any("Lava battlestaff", item(LAVA_BATTLESTAFF), item(LAVA_BATTLESTAFF_21198)), item(BLACK_DHIDE_VAMBRACES), item(MIND_SHIELD)),
 		new EmoteClue("Yawn in the centre of the Arceuus Library. Nod your head before you talk to me. Equip blue dragonhide vambraces, adamant boots and an adamant dagger.", "Arceuus library", ENTRANCE_OF_THE_ARCEUUS_LIBRARY, new WorldPoint(1632, 3807, 0), YAWN, YES, item(BLUE_DHIDE_VAMBRACES), item(ADAMANT_BOOTS), item(ADAMANT_DAGGER)),
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/idlenotifier/IdleNotifierPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/idlenotifier/IdleNotifierPlugin.java
index 790a6380e..fa9430c7a 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/idlenotifier/IdleNotifierPlugin.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/idlenotifier/IdleNotifierPlugin.java
@@ -337,6 +337,8 @@ public class IdleNotifierPlugin extends Plugin
 			case CHURN_MILK_SHORT:
 			case CHURN_MILK_MEDIUM:
 			case CHURN_MILK_LONG:
+			case CLEANING_SPECIMENS_1:
+			case CLEANING_SPECIMENS_2:
 			case LOOKING_INTO:
 				resetTimers();
 				lastAnimation = animation;
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/itemidentification/ItemIdentification.java b/runelite-client/src/main/java/net/runelite/client/plugins/itemidentification/ItemIdentification.java
index a28781dd0..605e39a78 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/itemidentification/ItemIdentification.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/itemidentification/ItemIdentification.java
@@ -294,10 +294,12 @@ enum ItemIdentification
 	STAMINA(Type.POTION, "Stamina", "St", ItemID.STAMINA_POTION4, ItemID.STAMINA_POTION3, ItemID.STAMINA_POTION2, ItemID.STAMINA_POTION1),
 	OVERLOAD(Type.POTION, "Overloa", "OL", ItemID.OVERLOAD_4, ItemID.OVERLOAD_3, ItemID.OVERLOAD_2, ItemID.OVERLOAD_1),
 	ABSORPTION(Type.POTION, "Absorb", "Ab", ItemID.ABSORPTION_4, ItemID.ABSORPTION_3, ItemID.ABSORPTION_2, ItemID.ABSORPTION_1),
+	BLIGHTED_SUPER_RESTORE(Type.POTION, "BS.Rest", "BS.Re", ItemID.BLIGHTED_SUPER_RESTORE4, ItemID.BLIGHTED_SUPER_RESTORE3, ItemID.BLIGHTED_SUPER_RESTORE2, ItemID.BLIGHTED_SUPER_RESTORE1),
 
 	ZAMORAK_BREW(Type.POTION, "ZammyBr", "Za", ItemID.ZAMORAK_BREW4, ItemID.ZAMORAK_BREW3, ItemID.ZAMORAK_BREW2, ItemID.ZAMORAK_BREW1),
 	SARADOMIN_BREW(Type.POTION, "SaraBr", "Sa", ItemID.SARADOMIN_BREW4, ItemID.SARADOMIN_BREW3, ItemID.SARADOMIN_BREW2, ItemID.SARADOMIN_BREW1),
 	ANCIENT_BREW(Type.POTION, "AncBr", "A.Br", ItemID.ANCIENT_BREW4, ItemID.ANCIENT_BREW3, ItemID.ANCIENT_BREW2, ItemID.ANCIENT_BREW1),
+	FORGOTTEN_BREW(Type.POTION, "ForgBr", "F.Br", ItemID.FORGOTTEN_BREW4, ItemID.FORGOTTEN_BREW3, ItemID.FORGOTTEN_BREW2, ItemID.FORGOTTEN_BREW1),
 
 	ANTIPOISON(Type.POTION, "AntiP", "AP", ItemID.ANTIPOISON4, ItemID.ANTIPOISON3, ItemID.ANTIPOISON2, ItemID.ANTIPOISON1),
 	SUPERANTIPOISON(Type.POTION, "S.AntiP", "S.AP", ItemID.SUPERANTIPOISON4, ItemID.SUPERANTIPOISON3, ItemID.SUPERANTIPOISON2, ItemID.SUPERANTIPOISON1),
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/kourendlibrary/Book.java b/runelite-client/src/main/java/net/runelite/client/plugins/kourendlibrary/Book.java
index 661ac88db..bcfc4ee31 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/kourendlibrary/Book.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/kourendlibrary/Book.java
@@ -34,17 +34,6 @@ import net.runelite.client.game.ItemManager;
 
 enum Book
 {
-	DARK_MANUSCRIPT_13514(ItemID.DARK_MANUSCRIPT),
-	DARK_MANUSCRIPT_13515(ItemID.DARK_MANUSCRIPT_13515),
-	DARK_MANUSCRIPT_13516(ItemID.DARK_MANUSCRIPT_13516),
-	DARK_MANUSCRIPT_13517(ItemID.DARK_MANUSCRIPT_13517),
-	DARK_MANUSCRIPT_13518(ItemID.DARK_MANUSCRIPT_13518),
-	DARK_MANUSCRIPT_13519(ItemID.DARK_MANUSCRIPT_13519),
-	DARK_MANUSCRIPT_13520(ItemID.DARK_MANUSCRIPT_13520),
-	DARK_MANUSCRIPT_13521(ItemID.DARK_MANUSCRIPT_13521),
-	DARK_MANUSCRIPT_13522(ItemID.DARK_MANUSCRIPT_13522),
-	DARK_MANUSCRIPT_13523(ItemID.DARK_MANUSCRIPT_13523),
-
 	RADAS_CENSUS(ItemID.RADAS_CENSUS, "Rada's Census", "Census of King Rada III, by Matthias Vorseth."),
 	RICKTORS_DIARY_7(ItemID.RICKTORS_DIARY_7, "Ricktor's Diary 7", "Diary of Steklan Ricktor, volume 7."),
 	EATHRAM_RADA_EXTRACT(ItemID.EATHRAM__RADA_EXTRACT, "Eathram & Rada extract", "An extract from Eathram & Rada, by Anonymous."),
@@ -81,10 +70,7 @@ enum Book
 		HashMap<String, Book> byName = new HashMap<>();
 		for (Book b : Book.values())
 		{
-			if (!b.isDarkManuscript)
-			{
-				byName.put(b.name, b);
-			}
+			byName.put(b.name, b);
 		}
 		return Collections.unmodifiableMap(byName);
 	}
@@ -111,25 +97,14 @@ enum Book
 	@Getter
 	private AsyncBufferedImage icon;
 
-	@Getter
-	private final boolean isDarkManuscript;
 
 	Book(int id, String shortName, String name)
 	{
 		this.item = id;
-		this.isDarkManuscript = false;
 		this.shortName = shortName;
 		this.name = name;
 	}
 
-	Book(int id)
-	{
-		this.item = id;
-		this.isDarkManuscript = true;
-		this.name = "Dark Manuscript";
-		this.shortName = "Dark Manuscript";
-	}
-
 	static void fillImages(ItemManager itemManager)
 	{
 		for (Book b : values())
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/kourendlibrary/KourendLibraryConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/kourendlibrary/KourendLibraryConfig.java
index c143ac28b..45bc004c4 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/kourendlibrary/KourendLibraryConfig.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/kourendlibrary/KourendLibraryConfig.java
@@ -63,16 +63,6 @@ public interface KourendLibraryConfig extends Config
 		return false;
 	}
 
-	@ConfigItem(
-		keyName = "hideDarkManuscript",
-		name = "Hide Dark Manuscript",
-		description = "Whether to hide Dark Manuscripts"
-	)
-	default boolean hideDarkManuscript()
-	{
-		return false;
-	}
-
 	@ConfigItem(
 		keyName = "showTutorialOverlay",
 		name = "Show tutorial overlay",
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/kourendlibrary/KourendLibraryOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/kourendlibrary/KourendLibraryOverlay.java
index 58c54dc7d..0c78afa7e 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/kourendlibrary/KourendLibraryOverlay.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/kourendlibrary/KourendLibraryOverlay.java
@@ -109,17 +109,6 @@ class KourendLibraryOverlay extends Overlay
 				boolean bookIsKnown = bookcase.isBookSet();
 				Book book = bookcase.getBook();
 				Set<Book> possible = bookcase.getPossibleBooks();
-				if (bookIsKnown && book == null)
-				{
-					for (Book b : possible)
-					{
-						if (b != null && b.isDarkManuscript())
-						{
-							book = b;
-							break;
-						}
-					}
-				}
 
 				if (!bookIsKnown && possible.size() == 1)
 				{
@@ -127,8 +116,7 @@ class KourendLibraryOverlay extends Overlay
 					bookIsKnown = true;
 				}
 
-				if ((book == Book.VARLAMORE_ENVOY && !plugin.showVarlamoreEnvoy())
-					|| (book != null && book.isDarkManuscript() && config.hideDarkManuscript()))
+				if (book == Book.VARLAMORE_ENVOY && !plugin.showVarlamoreEnvoy())
 				{
 					continue;
 				}
@@ -241,7 +229,6 @@ class KourendLibraryOverlay extends Overlay
 	{
 		return config.hideDuplicateBook()
 			&& book != null
-			&& !book.isDarkManuscript()
 			&& plugin.doesPlayerContainBook(book);
 	}
 }
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/kourendlibrary/KourendLibraryPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/kourendlibrary/KourendLibraryPanel.java
index 513127a24..f8df4403a 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/kourendlibrary/KourendLibraryPanel.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/kourendlibrary/KourendLibraryPanel.java
@@ -86,7 +86,6 @@ class KourendLibraryPanel extends PluginPanel
 		c.gridx = 0;
 		c.gridy = 0;
 		Stream.of(Book.values())
-			.filter(b -> !b.isDarkManuscript())
 			.filter(b -> b != Book.VARLAMORE_ENVOY || plugin.showVarlamoreEnvoy())
 			.sorted(Comparator.comparing(Book::getShortName))
 			.forEach(b ->
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/kourendlibrary/Library.java b/runelite-client/src/main/java/net/runelite/client/plugins/kourendlibrary/Library.java
index 282c02ab6..c97c7918a 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/kourendlibrary/Library.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/kourendlibrary/Library.java
@@ -24,6 +24,7 @@
  */
 package net.runelite.client.plugins.kourendlibrary;
 
+import com.google.common.annotations.VisibleForTesting;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -51,9 +52,7 @@ import static net.runelite.client.plugins.kourendlibrary.Book.*;
  * Each of the 352 bookcase slots "Bookcase"s has an index which is used to
  * place the book inside of them. The game chooses one of the 5 sequences and a
  * bookcase starting index, then places a book from the sequence into every 13th
- * bookcase, by index. Each sequence contains 26 Books, consisting of 16 books
- * and 10 dark manuscripts. You can only get one dark manuscript at a time, though
- * they are all placed into shelves.
+ * bookcase, by index. Each sequence contains 16 Books.
  */
 @Singleton
 @Slf4j
@@ -65,7 +64,8 @@ class Library
 
 	private final List<List<Book>> sequences = populateSequences();
 
-	private final int step;
+	@VisibleForTesting
+	final int step;
 
 	@Getter
 	private SolvedState state;
@@ -110,7 +110,7 @@ class Library
 		log.info("Library is now reset");
 	}
 
-	synchronized void mark(WorldPoint loc, Book book)
+	void mark(WorldPoint loc, Book book)
 	{
 		Bookcase bookcase = byPoint.get(loc);
 		if (bookcase == null)
@@ -119,12 +119,23 @@ class Library
 			return;
 		}
 
+		mark(bookcase, book);
+	}
+
+	@VisibleForTesting
+	void mark(int bookcaseIndex, Book book)
+	{
+		mark(byIndex.get(bookcaseIndex), book);
+	}
+
+	synchronized void mark(Bookcase bookcase, Book book)
+	{
 		if (bookcase.isBookSet())
 		{
 			// Bookcase is set from a previous mark
-			// Check for a mismatch, unless it is now null and had a dark manuscript or Varlamore Envoy
+			// Check for a mismatch, unless it is now null and has Varlamore Envoy
 			if (book != bookcase.getBook()
-				&& !(book == null && (bookcase.getBook().isDarkManuscript() || bookcase.getBook() == VARLAMORE_ENVOY)))
+				&& !(book == null && bookcase.getBook() == VARLAMORE_ENVOY))
 			{
 				reset();
 			}
@@ -141,11 +152,11 @@ class Library
 
 		if (state == SolvedState.COMPLETE)
 		{
-			// Reset if we found nothing when we expected something that wasn't a Dark Manuscript or Varlamore Envoy
+			// Reset if we found nothing when we expected something that wasn't Varlamore Envoy
 			// since the layout has changed
 			if (book == null
 				&& !bookcase.getPossibleBooks().isEmpty()
-				&& bookcase.getPossibleBooks().stream().noneMatch(b -> b.isDarkManuscript() || b == VARLAMORE_ENVOY))
+				&& bookcase.getPossibleBooks().stream().noneMatch(b -> b == VARLAMORE_ENVOY))
 			{
 				reset();
 			}
@@ -179,7 +190,6 @@ class Library
 
 			// Map each sequence to the number of bookcases that match the sequence
 			// return 0 if it is a mismatch.
-			// Keep in mind that Bookcases with dark manuscripts may be set to null.
 			int[] certainty = sequences.stream().mapToInt(sequence ->
 			{
 				int zero = getBookcaseZeroIndexForSequenceWithBook(sequence, bookcaseIndex, book);
@@ -195,8 +205,7 @@ class Library
 						if (iBookcase.isBookSet() && seqI < sequence.size())
 						{
 							Book seqBook = sequence.get(seqI);
-							boolean isSeqManuscript = seqBook == null || seqBook.isDarkManuscript();
-							if (!((isSeqManuscript && iBookcase.getBook() == null) || (iBookcase.getBook() == seqBook)))
+							if (iBookcase.getBook() != seqBook)
 							{
 								log.debug("Bailing @ i={} ai={} {}; {} != {}", i, ai, iBookcase.getIndex(), iBookcase.getBook(), seqBook);
 								found = 0;
@@ -285,113 +294,73 @@ class Library
 	{
 		List<List<Book>> books = Arrays.asList(
 			Arrays.asList(
-				DARK_MANUSCRIPT_13516,
 				KILLING_OF_A_KING,
-				DARK_MANUSCRIPT_13520,
 				IDEOLOGY_OF_DARKNESS,
 				RADAS_JOURNEY,
 				TRANSVERGENCE_THEORY,
 				TRISTESSAS_TRAGEDY,
-				DARK_MANUSCRIPT_13523,
-				DARK_MANUSCRIPT_13521,
 				RADAS_CENSUS,
 				TREACHERY_OF_ROYALTY,
 				HOSIDIUS_LETTER,
-				DARK_MANUSCRIPT_13519,
 				RICKTORS_DIARY_7,
-				DARK_MANUSCRIPT_13514,
 				EATHRAM_RADA_EXTRACT,
-				DARK_MANUSCRIPT_13522,
 				VARLAMORE_ENVOY,
 				WINTERTODT_PARABLE,
 				TWILL_ACCORD,
-				DARK_MANUSCRIPT_13515,
 				BYRNES_CORONATION_SPEECH,
-				DARK_MANUSCRIPT_13517,
 				SOUL_JOURNEY,
-				DARK_MANUSCRIPT_13518,
 				TRANSPORTATION_INCANTATIONS
 			),
 			Arrays.asList(
-				DARK_MANUSCRIPT_13516,
 				KILLING_OF_A_KING,
-				DARK_MANUSCRIPT_13520,
 				IDEOLOGY_OF_DARKNESS,
 				RADAS_JOURNEY,
 				TRANSVERGENCE_THEORY,
 				TRISTESSAS_TRAGEDY,
-				DARK_MANUSCRIPT_13523,
-				DARK_MANUSCRIPT_13521,
 				RADAS_CENSUS,
 				TREACHERY_OF_ROYALTY,
 				HOSIDIUS_LETTER,
 				VARLAMORE_ENVOY,
-				DARK_MANUSCRIPT_13519,
 				RICKTORS_DIARY_7,
-				DARK_MANUSCRIPT_13514,
 				EATHRAM_RADA_EXTRACT,
-				DARK_MANUSCRIPT_13522,
 				SOUL_JOURNEY,
 				WINTERTODT_PARABLE,
 				TWILL_ACCORD,
-				DARK_MANUSCRIPT_13515,
 				BYRNES_CORONATION_SPEECH,
-				DARK_MANUSCRIPT_13517,
-				DARK_MANUSCRIPT_13518,
 				TRANSPORTATION_INCANTATIONS
 			),
 			Arrays.asList(
 				RICKTORS_DIARY_7,
 				VARLAMORE_ENVOY,
-				DARK_MANUSCRIPT_13514,
 				EATHRAM_RADA_EXTRACT,
 				IDEOLOGY_OF_DARKNESS,
-				DARK_MANUSCRIPT_13516,
-				DARK_MANUSCRIPT_13521,
 				RADAS_CENSUS,
-				DARK_MANUSCRIPT_13515,
 				KILLING_OF_A_KING,
-				DARK_MANUSCRIPT_13520,
 				TREACHERY_OF_ROYALTY,
 				HOSIDIUS_LETTER,
-				DARK_MANUSCRIPT_13519,
 				BYRNES_CORONATION_SPEECH,
-				DARK_MANUSCRIPT_13517,
 				SOUL_JOURNEY,
-				DARK_MANUSCRIPT_13522,
 				WINTERTODT_PARABLE,
 				TWILL_ACCORD,
 				RADAS_JOURNEY,
 				TRANSVERGENCE_THEORY,
 				TRISTESSAS_TRAGEDY,
-				DARK_MANUSCRIPT_13523,
-				DARK_MANUSCRIPT_13518,
 				TRANSPORTATION_INCANTATIONS
 			),
 			Arrays.asList(
 				RADAS_CENSUS,
-				DARK_MANUSCRIPT_13522,
 				RICKTORS_DIARY_7,
-				DARK_MANUSCRIPT_13514,
 				EATHRAM_RADA_EXTRACT,
-				DARK_MANUSCRIPT_13516,
 				KILLING_OF_A_KING,
-				DARK_MANUSCRIPT_13520,
 				HOSIDIUS_LETTER,
-				DARK_MANUSCRIPT_13519,
-				DARK_MANUSCRIPT_13521,
 				WINTERTODT_PARABLE,
 				TWILL_ACCORD,
-				DARK_MANUSCRIPT_13515,
 				BYRNES_CORONATION_SPEECH,
-				DARK_MANUSCRIPT_13517,
 				IDEOLOGY_OF_DARKNESS,
 				RADAS_JOURNEY,
 				TRANSVERGENCE_THEORY,
 				TRISTESSAS_TRAGEDY,
-				DARK_MANUSCRIPT_13523,
 				TREACHERY_OF_ROYALTY,
-				DARK_MANUSCRIPT_13518,
 				TRANSPORTATION_INCANTATIONS,
 				SOUL_JOURNEY,
 				VARLAMORE_ENVOY
@@ -402,26 +371,16 @@ class Library
 				TREACHERY_OF_ROYALTY,
 				RADAS_JOURNEY,
 				KILLING_OF_A_KING,
-				DARK_MANUSCRIPT_13520,
 				VARLAMORE_ENVOY,
-				DARK_MANUSCRIPT_13522,
 				BYRNES_CORONATION_SPEECH,
-				DARK_MANUSCRIPT_13517,
 				HOSIDIUS_LETTER,
-				DARK_MANUSCRIPT_13516,
-				DARK_MANUSCRIPT_13519,
 				TRISTESSAS_TRAGEDY,
-				DARK_MANUSCRIPT_13523,
-				DARK_MANUSCRIPT_13521,
 				RICKTORS_DIARY_7,
-				DARK_MANUSCRIPT_13514,
 				IDEOLOGY_OF_DARKNESS,
 				WINTERTODT_PARABLE,
 				TWILL_ACCORD,
 				SOUL_JOURNEY,
-				DARK_MANUSCRIPT_13515,
 				EATHRAM_RADA_EXTRACT,
-				DARK_MANUSCRIPT_13518,
 				TRANSPORTATION_INCANTATIONS
 			)
 		);
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/npcunaggroarea/NpcAggroAreaPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/npcunaggroarea/NpcAggroAreaPlugin.java
index 1a71d0a77..f42c587cc 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/npcunaggroarea/NpcAggroAreaPlugin.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/npcunaggroarea/NpcAggroAreaPlugin.java
@@ -288,7 +288,7 @@ public class NpcAggroAreaPlugin extends Plugin
 
 		for (String pattern : npcNamePatterns)
 		{
-			if (WildcardMatcher.matches(pattern, npcName))
+			if (npc.getCombatLevel() != 0 && WildcardMatcher.matches(pattern, npcName))
 			{
 				return true;
 			}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/prayer/PrayerReorder.java b/runelite-client/src/main/java/net/runelite/client/plugins/prayer/PrayerReorder.java
index 87f3f36da..572b7af6c 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/prayer/PrayerReorder.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/prayer/PrayerReorder.java
@@ -56,6 +56,7 @@ import net.runelite.client.chat.ChatMessageManager;
 import net.runelite.client.chat.QueuedMessage;
 import net.runelite.client.config.ConfigManager;
 import net.runelite.client.eventbus.Subscribe;
+import net.runelite.client.events.ProfileChanged;
 import net.runelite.client.menus.MenuManager;
 import net.runelite.client.menus.WidgetMenuOption;
 import org.apache.commons.lang3.ArrayUtils;
@@ -495,4 +496,10 @@ class PrayerReorder
 			}
 		}
 	}
+
+	@Subscribe
+	public void onProfileChanged(ProfileChanged e)
+	{
+		clientThread.invokeLater(this::redrawPrayers);
+	}
 }
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/FairyRingLocation.java b/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/FairyRingLocation.java
index 03aa9eae0..83f8fce45 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/FairyRingLocation.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/FairyRingLocation.java
@@ -35,6 +35,7 @@ enum FairyRingLocation
 	AIQ("AIQ", new WorldPoint(2995, 3112, 0)),
 	AJR("AJR", new WorldPoint(2779, 3615, 0)),
 	AJS("AJS", new WorldPoint(2499, 3898, 0)),
+	AKR("AKR", new WorldPoint(1823, 3539, 0)),
 	AKP("AKP", new WorldPoint(3283, 2704, 0)),
 	AKQ("AKQ", new WorldPoint(2318, 3617, 0)),
 	AKS("AKS", new WorldPoint(2570, 2958, 0)),
diff --git a/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayManager.java b/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayManager.java
index 3060fe79b..3439b857a 100644
--- a/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayManager.java
+++ b/runelite-client/src/main/java/net/runelite/client/ui/overlay/OverlayManager.java
@@ -129,7 +129,11 @@ public class OverlayManager
 	{
 		synchronized (this)
 		{
-			overlays.forEach(this::loadOverlay);
+			overlays.forEach((o) ->
+			{
+				loadOverlay(o);
+				o.revalidate();
+			});
 		}
 		rebuildOverlayLayers();
 	}


Test patch:
diff --git a/runelite-client/src/test/java/net/runelite/client/plugins/kourendlibrary/LibraryTest.java b/runelite-client/src/test/java/net/runelite/client/plugins/kourendlibrary/LibraryTest.java
index 2f782bc0a..86214e918 100644
--- a/runelite-client/src/test/java/net/runelite/client/plugins/kourendlibrary/LibraryTest.java
+++ b/runelite-client/src/test/java/net/runelite/client/plugins/kourendlibrary/LibraryTest.java
@@ -27,7 +27,6 @@ package net.runelite.client.plugins.kourendlibrary;
 import com.google.inject.Guice;
 import com.google.inject.Inject;
 import com.google.inject.testing.fieldbinder.BoundFieldModule;
-import net.runelite.api.coords.WorldPoint;
 import static org.junit.Assert.assertEquals;
 import org.junit.Before;
 import org.junit.Test;
@@ -46,32 +45,20 @@ public class LibraryTest
 	@Test
 	public void testVarlamoreEnvoyFindingProcess()
 	{
-		library.mark(new WorldPoint(1610, 3799, 1), null);
-		library.mark(new WorldPoint(1608, 3799, 1), null);
-		library.mark(new WorldPoint(1615, 3799, 1), null);
-		library.mark(new WorldPoint(1616, 3799, 1), null);
-		library.mark(new WorldPoint(1621, 3799, 1), null);
-		library.mark(new WorldPoint(1624, 3796, 1), null);
-		library.mark(new WorldPoint(1624, 3792, 1), null);
-		library.mark(new WorldPoint(1624, 3791, 1), null);
-		library.mark(new WorldPoint(1623, 3789, 1), null);
-
 		assertEquals(SolvedState.NO_DATA, library.getState());
-
-		library.mark(new WorldPoint(1621, 3789, 1), Book.WINTERTODT_PARABLE);
-
+		library.mark(0, Book.RADAS_JOURNEY);
 		assertEquals(SolvedState.INCOMPLETE, library.getState());
-
-		library.mark(new WorldPoint(1618, 3799, 2), null);
-		library.mark(new WorldPoint(1613, 3792, 2), null);
-		library.mark(new WorldPoint(1618, 3790, 2), Book.TRANSPORTATION_INCANTATIONS);
-		library.mark(new WorldPoint(1609, 3816, 2), Book.RICKTORS_DIARY_7);
-
+		library.mark(library.step, Book.KILLING_OF_A_KING);
 		assertEquals(SolvedState.COMPLETE, library.getState());
 
 		// The Varlamore Envoy book can be found in this bookcase, but should not cause a state reset if not found
-		library.mark(new WorldPoint(1622, 3816, 2), null);
-
+		library.mark(library.step * 2, null);
+		assertEquals(SolvedState.COMPLETE, library.getState());
+		library.mark(library.step * 2, Book.VARLAMORE_ENVOY);
 		assertEquals(SolvedState.COMPLETE, library.getState());
+
+		// not valid, should reset
+		library.mark(library.step * 2, Book.TRANSPORTATION_INCANTATIONS);
+		assertEquals(SolvedState.INCOMPLETE, library.getState());
 	}
 }