Repository: runelite/runelite
Pull request #18199 (2024-08-29 00:40:56)

agility: fix wilderness course
~~this is probably correct but I'm not bonded so I didn't test it, if someone else wants to~~

![image](https://github.com/user-attachments/assets/1032580a-cd24-495d-b419-e93a823878dc)
![image](https://github.com/user-attachments/assets/54762414-22a5-44c2-9dd7-91a62c2ce3e2)
![image](https://github.com/user-attachments/assets/d1e02f64-bce4-4a94-8e61-dfc6ecce7e33)


tylerwgrass: Ran each ending and LGTM.

Patch:
diff --git a/cache/pom.xml b/cache/pom.xml
index b08a0522f..34f2e8dc4 100644
--- a/cache/pom.xml
+++ b/cache/pom.xml
@@ -29,7 +29,7 @@
 	<parent>
 		<groupId>net.runelite</groupId>
 		<artifactId>runelite-parent</artifactId>
-		<version>1.10.39-SNAPSHOT</version>
+		<version>1.10.41-SNAPSHOT</version>
 	</parent>
 
 	<artifactId>cache</artifactId>
diff --git a/pom.xml b/pom.xml
index 8daa0dee1..362a39015 100644
--- a/pom.xml
+++ b/pom.xml
@@ -28,7 +28,7 @@
 
 	<groupId>net.runelite</groupId>
 	<artifactId>runelite-parent</artifactId>
-	<version>1.10.39-SNAPSHOT</version>
+	<version>1.10.41-SNAPSHOT</version>
 	<packaging>pom</packaging>
 
 	<name>RuneLite</name>
diff --git a/runelite-api/pom.xml b/runelite-api/pom.xml
index 12db42e20..ef55a7ec1 100644
--- a/runelite-api/pom.xml
+++ b/runelite-api/pom.xml
@@ -29,7 +29,7 @@
 	<parent>
 		<groupId>net.runelite</groupId>
 		<artifactId>runelite-parent</artifactId>
-		<version>1.10.39-SNAPSHOT</version>
+		<version>1.10.41-SNAPSHOT</version>
 	</parent>
 
 	<artifactId>runelite-api</artifactId>
diff --git a/runelite-api/src/main/interfaces/interfaces.toml b/runelite-api/src/main/interfaces/interfaces.toml
index f54f78c88..2dfa7c09d 100644
--- a/runelite-api/src/main/interfaces/interfaces.toml
+++ b/runelite-api/src/main/interfaces/interfaces.toml
@@ -70,12 +70,12 @@ potion_store=48
 incinerator=49
 incinerator_confirm=50
 potionstore_content=52
-equipment_parent=74
-equipment_set_bonus=115
-settings_button=119
-equipment_button=120
-popup=122
-equipment_stat_bonus=127
+equipment_parent=77
+equipment_set_bonus=118
+settings_button=122
+equipment_button=123
+popup=125
+equipment_stat_bonus=131
 
 [bank_inventory]
 id=15
@@ -627,6 +627,7 @@ container=0
 
 [prayer]
 id=541
+parent=0
 
 [private_chat]
 id=163
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 153e82525..7eb514c70 100644
--- a/runelite-api/src/main/java/net/runelite/api/AnimationID.java
+++ b/runelite-api/src/main/java/net/runelite/api/AnimationID.java
@@ -236,6 +236,10 @@ public final class AnimationID
 	public static final int DENSE_ESSENCE_CHIPPING = 7201;
 	public static final int DENSE_ESSENCE_CHISELING = 7202;
 	public static final int HERBLORE_POTIONMAKING = 363; //used for both herb and secondary
+	public static final int HERBLORE_MIXOLOGY_CONCENTRATE = 11644;
+	public static final int HERBLORE_MIXOLOGY_CRYSTALIZE = 11639;
+	public static final int HERBLORE_MIXOLOGY_HOMOGENIZE = 11634;
+	public static final int HERBLORE_MIXOLOGY_REFINER = 11604;
 	public static final int MAGIC_CHARGING_ORBS = 726;
 	public static final int MAGIC_MAKE_TABLET = 4068;
 	public static final int MAGIC_ENCHANTING_JEWELRY = 931;
@@ -301,6 +305,7 @@ public final class AnimationID
 	// NPC animations
 	public static final int TZTOK_JAD_MAGIC_ATTACK = 2656;
 	public static final int TZTOK_JAD_RANGE_ATTACK = 2652;
+	public static final int CYCLOPS_ATTACK = 4652;
 	public static final int HELLHOUND_DEFENCE = 6566;
 
 	// Farming
diff --git a/runelite-api/src/main/java/net/runelite/api/NpcID.java b/runelite-api/src/main/java/net/runelite/api/NpcID.java
index a8c869287..ed2a35e21 100644
--- a/runelite-api/src/main/java/net/runelite/api/NpcID.java
+++ b/runelite-api/src/main/java/net/runelite/api/NpcID.java
@@ -11601,8 +11601,7 @@ public final class NpcID
 	public static final int ICE_BLOCK_13790 = 13790;
 	public static final int FROST_CRAB_13791 = 13791;
 	public static final int ICE_BLOCK_13792 = 13792;
-	public static final int BLUE_DRAGON_13793 = 13793;
-	public static final int BABY_BLUE_DRAGON_13794 = 13794;
+	public static final int BREWMA = 13793;
 	public static final int BRUTAL_BLUE_DRAGON_13795 = 13795;
 	public static final int ICE_GIANT_13796 = 13796;
 	public static final int ICE_GIANT_13797 = 13797;
@@ -11836,5 +11835,9 @@ public final class NpcID
 	public static final int HUBERTE_14045 = 14045;
 	public static final int MOXI_14046 = 14046;
 	public static final int GULL_14047 = 14047;
+	public static final int BLUE_DRAGON_14103 = 14103;
+	public static final int BLUE_DRAGON_14104 = 14104;
+	public static final int BABY_BLUE_DRAGON_14105 = 14105;
+	public static final int BABY_BLUE_DRAGON_14106 = 14106;
 	/* This file is automatically generated. Do not edit. */
 }
diff --git a/runelite-api/src/main/java/net/runelite/api/NullObjectID.java b/runelite-api/src/main/java/net/runelite/api/NullObjectID.java
index 33a6ad308..8df4af53e 100644
--- a/runelite-api/src/main/java/net/runelite/api/NullObjectID.java
+++ b/runelite-api/src/main/java/net/runelite/api/NullObjectID.java
@@ -28811,5 +28811,9 @@ public final class NullObjectID
 	public static final int NULL_55403 = 55403;
 	public static final int NULL_55404 = 55404;
 	public static final int NULL_55405 = 55405;
+	public static final int NULL_55420 = 55420;
+	public static final int NULL_55421 = 55421;
+	public static final int NULL_55422 = 55422;
+	public static final int NULL_55423 = 55423;
 	/* This file is automatically generated. Do not edit. */
 }
diff --git a/runelite-api/src/main/java/net/runelite/api/ObjectID.java b/runelite-api/src/main/java/net/runelite/api/ObjectID.java
index dd7905b6d..c00198faf 100644
--- a/runelite-api/src/main/java/net/runelite/api/ObjectID.java
+++ b/runelite-api/src/main/java/net/runelite/api/ObjectID.java
@@ -26582,5 +26582,18 @@ public final class ObjectID
 	public static final int SHELVES_55407 = 55407;
 	public static final int SHELVES_55408 = 55408;
 	public static final int SHELVES_55409 = 55409;
+	public static final int CAULDRON_55410 = 55410;
+	public static final int REWARD_CART = 55411;
+	public static final int REWARD_CART_55412 = 55412;
+	public static final int REWARD_CART_55413 = 55413;
+	public static final int REWARD_CART_55414 = 55414;
+	public static final int REWARD_CART_55415 = 55415;
+	public static final int REWARD_CART_55416 = 55416;
+	public static final int ROCKSLIDE_55417 = 55417;
+	public static final int ROCKSLIDE_55418 = 55418;
+	public static final int ROCKSLIDE_55419 = 55419;
+	public static final int GRAPE_STORAGE_55424 = 55424;
+	public static final int GRAPE_STORAGE_55425 = 55425;
+	public static final int GRAPE_STORAGE_55426 = 55426;
 	/* This file is automatically generated. Do not edit. */
 }
diff --git a/runelite-api/src/main/java/net/runelite/api/hooks/DrawCallbacks.java b/runelite-api/src/main/java/net/runelite/api/hooks/DrawCallbacks.java
index 4b5281912..26bfc57be 100644
--- a/runelite-api/src/main/java/net/runelite/api/hooks/DrawCallbacks.java
+++ b/runelite-api/src/main/java/net/runelite/api/hooks/DrawCallbacks.java
@@ -127,19 +127,19 @@ public interface DrawCallbacks
 	{
 	}
 
-	default void drawPass(Projection projection, Scene scene, int pass)
+	default void drawPass(Projection entityProjection, Scene scene, int pass)
 	{
 	}
 
-	default void drawZone(Projection projection, Scene scene, int pass, int zx, int zz)
+	default void drawZone(Projection entityProjection, Scene scene, int pass, int zx, int zz)
 	{
 	}
 
-	default void drawDynamic(Scene scene, TileObject tileObject, Renderable r, Model m, int orient, int x, int y, int z)
+	default void drawDynamic(Projection worldProjection, Scene scene, TileObject tileObject, Renderable r, Model m, int orient, int x, int y, int z)
 	{
 	}
 
-	default void drawTemp(Scene scene, GameObject gameObject, Model m)
+	default void drawTemp(Projection worldProjection, Scene scene, GameObject gameObject, Model m)
 	{
 	}
 
diff --git a/runelite-client/pom.xml b/runelite-client/pom.xml
index 3b2282bd5..98020cc38 100644
--- a/runelite-client/pom.xml
+++ b/runelite-client/pom.xml
@@ -29,7 +29,7 @@
 	<parent>
 		<groupId>net.runelite</groupId>
 		<artifactId>runelite-parent</artifactId>
-		<version>1.10.39-SNAPSHOT</version>
+		<version>1.10.41-SNAPSHOT</version>
 	</parent>
 
 	<artifactId>client</artifactId>
@@ -372,10 +372,21 @@
 							<shadedArtifactAttached>true</shadedArtifactAttached>
 							<shadedClassifierName>shaded</shadedClassifierName>
 							<createDependencyReducedPom>false</createDependencyReducedPom>
+							<filters>
+								<filter>
+									<artifact>*:*</artifact>
+									<excludes>
+										<exclude>META-INF/versions/**/module-info.class</exclude>
+									</excludes>
+								</filter>
+							</filters>
 
 							<transformers>
 								<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
-									<mainClass>net.runelite.client.RuneLite</mainClass>
+									<manifestEntries>
+										<Main-Class>net.runelite.client.RuneLite</Main-Class>
+										<Multi-Release>true</Multi-Release>
+									</manifestEntries>
 								</transformer>
 							</transformers>
 						</configuration>
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/agility/Courses.java b/runelite-client/src/main/java/net/runelite/client/plugins/agility/Courses.java
index 8dec2d62f..8a3200ef2 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/agility/Courses.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/agility/Courses.java
@@ -44,7 +44,7 @@ enum Courses
 	SHAYZIEN_ADVANCED(507.5, 5944, new WorldPoint(1522, 3625, 0)),
 	FALADOR(586, 12084, new WorldPoint(3029, 3332, 0), new WorldPoint(3029, 3333, 0), new WorldPoint(3029, 3334, 0), new WorldPoint(3029, 3335, 0)),
 	COLOSSAL_WYRM_BASIC(504),
-	WILDERNESS(571.4, 11837, new WorldPoint(2994, 3933, 0)),
+	WILDERNESS(571.4, 11837, new WorldPoint(2993, 3933, 0), new WorldPoint(2994, 3933, 0), new WorldPoint(2995, 3933, 0)),
 	WEREWOLF(730.0, 14234, new WorldPoint(3528, 9873, 0)),
 	SEERS(570.0, 10806, new WorldPoint(2704, 3464, 0)),
 	COLOSSAL_WYRM_ADVANCED(685),
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/animsmoothing/AnimationSmoothingPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/animsmoothing/AnimationSmoothingPlugin.java
index ede1be560..0616adaaa 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/animsmoothing/AnimationSmoothingPlugin.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/animsmoothing/AnimationSmoothingPlugin.java
@@ -69,6 +69,8 @@ public class AnimationSmoothingPlugin extends Plugin
 			case AnimationID.WYRM_ATTACK_MELEE:
 			case AnimationID.WYRM_ATTACK_MAGE:
 
+			case AnimationID.CYCLOPS_ATTACK:
+
 			case AnimationID.TREE_SPIRIT_IDLE:
 			case AnimationID.TREE_SPIRIT_WALK:
 
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/attackstyles/AttackStylesPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/attackstyles/AttackStylesPlugin.java
index 7f6e43959..5ddb2204c 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/attackstyles/AttackStylesPlugin.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/attackstyles/AttackStylesPlugin.java
@@ -58,6 +58,7 @@ import net.runelite.client.eventbus.Subscribe;
 import net.runelite.client.events.ConfigChanged;
 import net.runelite.client.plugins.Plugin;
 import net.runelite.client.plugins.PluginDescriptor;
+import static net.runelite.client.plugins.attackstyles.AttackStyle.CASTING;
 import static net.runelite.client.plugins.attackstyles.AttackStyle.DEFENSIVE;
 import static net.runelite.client.plugins.attackstyles.AttackStyle.DEFENSIVE_CASTING;
 import static net.runelite.client.plugins.attackstyles.AttackStyle.OTHER;
@@ -296,6 +297,14 @@ public class AttackStylesPlugin extends Plugin
 		int weaponStyleEnum = client.getEnum(EnumID.WEAPON_STYLES).getIntValue(weaponType);
 		if (weaponStyleEnum == -1)
 		{
+			// Blue moon spear
+			if (weaponType == 22)
+			{
+				return new AttackStyle[]{
+					AttackStyle.ACCURATE, AttackStyle.AGGRESSIVE, null, DEFENSIVE, CASTING, DEFENSIVE_CASTING
+				};
+			}
+
 			if (weaponType == 30)
 			{
 				// Partisan
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/bosstimer/Boss.java b/runelite-client/src/main/java/net/runelite/client/plugins/bosstimer/Boss.java
index 120f42c21..120b36ad7 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/bosstimer/Boss.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/bosstimer/Boss.java
@@ -75,6 +75,7 @@ enum Boss
 	// but we still want the respawn timer to show when it despawns.
 	ARAXXOR(NpcID.ARAXXOR_13669, 15, RSTimeUnit.GAME_TICKS, ItemID.NID, true),
 	AMOXLIATL(NpcID.AMOXLIATL, 28, RSTimeUnit.GAME_TICKS, ItemID.MOXI),
+	HUEYCOATL(NpcID.THE_HUEYCOATL_14012, 50, RSTimeUnit.GAME_TICKS, ItemID.HUBERTE),
 	;
 
 	private static final Map<Integer, Boss> bosses;
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/bosstimer/BossTimersPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/bosstimer/BossTimersPlugin.java
index 27a6a0c9e..2d4e2414e 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/bosstimer/BossTimersPlugin.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/bosstimer/BossTimersPlugin.java
@@ -28,6 +28,7 @@ package net.runelite.client.plugins.bosstimer;
 import javax.inject.Inject;
 import lombok.extern.slf4j.Slf4j;
 import net.runelite.api.NPC;
+import net.runelite.api.events.NpcChanged;
 import net.runelite.api.events.NpcDespawned;
 import net.runelite.client.eventbus.Subscribe;
 import net.runelite.client.game.ItemManager;
@@ -65,13 +66,26 @@ public class BossTimersPlugin extends Plugin
 		NPC npc = npcDespawned.getNpc();
 		Boss boss = Boss.find(npc.getId());
 
-		if (boss == null || (!boss.isIgnoreDead() && !npcUtil.isDying(npc)))
+		if (boss != null && (boss.isIgnoreDead() || npcUtil.isDying(npc)))
 		{
-			return;
+			createTimer(npc, boss);
 		}
+	}
 
-		// remove existing timer
-		infoBoxManager.removeIf(t -> t instanceof RespawnTimer && ((RespawnTimer) t).getBoss() == boss);
+	@Subscribe
+	public void onNpcChanged(NpcChanged npcChanged)
+	{
+		NPC npc = npcChanged.getNpc();
+		Boss boss = Boss.find(npc.getId());
+		if (boss == Boss.HUEYCOATL)
+		{
+			createTimer(npc, boss);
+		}
+	}
+
+	private void createTimer(NPC npc, Boss boss)
+	{
+		clearTimer(boss);
 
 		log.debug("Creating spawn timer for {} ({})", npc.getName(), boss.getSpawnTime());
 
@@ -79,4 +93,9 @@ public class BossTimersPlugin extends Plugin
 		timer.setTooltip(npc.getName());
 		infoBoxManager.addInfoBox(timer);
 	}
+
+	private void clearTimer(Boss boss)
+	{
+		infoBoxManager.removeIf(t -> t instanceof RespawnTimer && ((RespawnTimer) t).getBoss() == boss);
+	}
 }
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 13d30810f..3d5a1a269 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
@@ -118,7 +118,7 @@ public class EmoteClue extends ClueScroll implements LocationClueScroll
 		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))),
+		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))),
 		new EmoteClue("Dance in Iban's temple. Beware of double agents! Equip Iban's staff, a black mystic top and a black mystic bottom.", "Iban's temple", WELL_OF_VOYAGE, new WorldPoint(2011, 4712, 0), DOUBLE_AGENT_141, DANCE, any("Any iban's staff", item(IBANS_STAFF), item(IBANS_STAFF_U)), item(MYSTIC_ROBE_TOP_DARK), item(MYSTIC_ROBE_BOTTOM_DARK)),
 		new EmoteClue("Dance on the Fishing Platform. Equip barrows gloves, an amulet of glory and a dragon med helm.", "Fishing Platform", SOUTHEAST_CORNER_OF_THE_FISHING_PLATFORM, new WorldPoint(2782, 3273, 0), DANCE, any("Any amulet of glory", item(AMULET_OF_GLORY), item(AMULET_OF_GLORY1), item(AMULET_OF_GLORY2), item(AMULET_OF_GLORY3), item(AMULET_OF_GLORY4), item(AMULET_OF_GLORY5), item(AMULET_OF_GLORY6)), item(BARROWS_GLOVES), item(DRAGON_MED_HELM)),
 		new EmoteClue("Flap at the death altar. Beware of double agents! Equip a death tiara, a legend's cape and any ring of wealth.", "Death altar", DEATH_ALTAR, new WorldPoint(2205, 4838, 0), DOUBLE_AGENT_141, FLAP, ANY_RING_OF_WEALTH, item(DEATH_TIARA), item(CAPE_OF_LEGENDS)),
@@ -181,7 +181,7 @@ public class EmoteClue extends ClueScroll implements LocationClueScroll
 		new EmoteClue("Jump for joy in the Ancient Cavern. Equip a granite shield, splitbark body and any rune heraldic helm.", "Ancient cavern", ENTRANCE_OF_THE_CAVERN_UNDER_THE_WHIRLPOOL, new WorldPoint(1768, 5366, 1), JUMP_FOR_JOY, item(GRANITE_SHIELD), item(SPLITBARK_BODY), range("Any rune heraldic helm", RUNE_HELM_H1, RUNE_HELM_H5)),
 		new EmoteClue("Jump for joy at the Neitiznot rune rock. Equip Rune boots, a proselyte hauberk and a dragonstone ring.", "Fremennik Isles", NEAR_A_RUNITE_ROCK_IN_THE_FREMENNIK_ISLES, new WorldPoint(2375, 3850, 0), JUMP_FOR_JOY, item(RUNE_BOOTS), item(PROSELYTE_HAUBERK), item(DRAGONSTONE_RING)),
 		new EmoteClue("Jump for joy in the centre of Zul-Andra. Beware of double agents! Equip a dragon 2h sword, bandos boots and an obsidian cape.", "Zul-Andra", NEAR_THE_PIER_IN_ZULANDRA, new WorldPoint(2199, 3056, 0), DOUBLE_AGENT_141, JUMP_FOR_JOY, item(DRAGON_2H_SWORD), any("Bandos boots", item(BANDOS_BOOTS), item(GUARDIAN_BOOTS), item(ECHO_BOOTS)), item(OBSIDIAN_CAPE)),
-		new EmoteClue("Jump for Joy in the mine near the Twilight Temple. Bow before you talk to me. Equip a maple longbow, a ruby amulet and some steel platelegs.", "Twilight Temple", TWILIGHT_TEMPLE_MINE, new WorldPoint (1672, 3284, 0), JUMP_FOR_JOY, item(MAPLE_LONGBOW), item(RUBY_AMULET), item(STEEL_PLATELEGS)),
+		new EmoteClue("Jump for Joy in the mine near the Twilight Temple. Bow before you talk to me. Equip a maple longbow, a ruby amulet and some steel platelegs.", "Twilight Temple", TWILIGHT_TEMPLE_MINE, new WorldPoint (1672, 3284, 0), JUMP_FOR_JOY, BOW, item(MAPLE_LONGBOW), item(RUBY_AMULET), item(STEEL_PLATELEGS)),
 		new EmoteClue("Laugh by the fountain of heroes. Equip splitbark legs, dragon boots and a Rune longsword.", "Fountain of heroes", FOUNTAIN_OF_HEROES, new WorldPoint(2920, 9893, 0), LAUGH, item(SPLITBARK_LEGS), any("Dragon boots", item(DRAGON_BOOTS), item(DRAGON_BOOTS_G), item(PRIMORDIAL_BOOTS)), item(RUNE_LONGSWORD)),
 		new EmoteClue("Laugh in Jokul's tent in the Mountain Camp. Beware of double agents! Equip a rune full helmet, blue dragonhide chaps and a fire battlestaff.", "Mountain Camp", MOUNTAIN_CAMP_GOAT_ENCLOSURE, new WorldPoint(2812, 3681, 0), DOUBLE_AGENT_108, LAUGH, item(RUNE_FULL_HELM), item(BLUE_DHIDE_CHAPS), item(FIRE_BATTLESTAFF)),
 		new EmoteClue("Laugh at the crossroads south of the Sinclair Mansion. Equip a cowl, a blue wizard robe top and an iron scimitar.", "Sinclair Mansion", ROAD_JUNCTION_SOUTH_OF_SINCLAIR_MANSION, new WorldPoint(2741, 3536, 0), LAUGH, item(LEATHER_COWL), item(BLUE_WIZARD_ROBE), item(IRON_SCIMITAR)),
@@ -200,7 +200,7 @@ public class EmoteClue extends ClueScroll implements LocationClueScroll
 		new EmoteClue("Blow a raspberry in the Fishing Guild bank. Beware of double agents! Equip an elemental shield, blue dragonhide chaps and a rune warhammer.", "Fishing Guild", FISHING_GUILD_BANK, new WorldPoint(2588, 3419, 0), DOUBLE_AGENT_108, RASPBERRY, item(ELEMENTAL_SHIELD), item(BLUE_DHIDE_CHAPS), item(RUNE_WARHAMMER)),
 		new EmoteClue("Salute in the banana plantation. Beware of double agents! Equip a diamond ring, amulet of power, and nothing on your chest and legs.", "Karamja", WEST_SIDE_OF_THE_KARAMJA_BANANA_PLANTATION, new WorldPoint(2914, 3168, 0), DOUBLE_AGENT_108, SALUTE, item(DIAMOND_RING), item(AMULET_OF_POWER), emptySlot("Nothing on chest & legs", BODY, LEGS)),
 		new EmoteClue("Salute in the Warriors' Guild bank. Equip only a black salamander.", "Warriors' guild", WARRIORS_GUILD_BANK, new WorldPoint(2844, 3542, 0), SALUTE, item(BLACK_SALAMANDER), emptySlot("Nothing else", HEAD, CAPE, AMULET, BODY, SHIELD, LEGS, GLOVES, BOOTS, RING, AMMO)),
-		new EmoteClue("Salute in the centre of the mess hall. Beware of double agents! Equip a rune halberd rune platebody, and an amulet of strength.", "Hosidius mess hall", HOSIDIUS_MESS, new WorldPoint(1646, 3631, 0), DOUBLE_AGENT_108, SALUTE, item(RUNE_HALBERD), item(RUNE_PLATEBODY), item(AMULET_OF_STRENGTH)),
+		new EmoteClue("Salute in the centre of the mess hall. Beware of double agents! Equip a rune halberd rune platebody and an amulet of strength.", "Hosidius mess hall", HOSIDIUS_MESS, new WorldPoint(1646, 3631, 0), DOUBLE_AGENT_108, SALUTE, item(RUNE_HALBERD), item(RUNE_PLATEBODY), item(AMULET_OF_STRENGTH)),
 		new EmoteClue("Salute outside the gates of Cam Torum. Beware of double agents! Equip a full set of blue moon equipment.", "Cam Torum", CAM_TORUM_ENTRANCE, new WorldPoint(1436, 3115, 0), DOUBLE_AGENT_141, SALUTE, any("Blue moon helm", item(BLUE_MOON_HELM), item(BLUE_MOON_HELM_29041)), any("Blue moon chestplate", item(BLUE_MOON_CHESTPLATE), item(BLUE_MOON_CHESTPLATE_29037)), any("Blue moon tassets", item(BLUE_MOON_TASSETS), item(BLUE_MOON_TASSETS_29039)), item(BLUE_MOON_SPEAR)),
 		new EmoteClue("Shrug in the mine near Rimmington. Equip a gold necklace, a gold ring and a bronze spear.", "Rimmington mine", RIMMINGTON_MINE, new WorldPoint(2976, 3238, 0), SHRUG, item(GOLD_NECKLACE), item(GOLD_RING), item(BRONZE_SPEAR)),
 		new EmoteClue("Shrug in Catherby bank. Yawn before you talk to me. Equip a maple longbow, green d'hide chaps and an iron med helm.", "Catherby", OUTSIDE_CATHERBY_BANK, new WorldPoint(2808, 3440, 0), SHRUG, YAWN, item(MAPLE_LONGBOW), item(GREEN_DHIDE_CHAPS), item(IRON_MED_HELM)),
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/hotcold/HotColdLocation.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/hotcold/HotColdLocation.java
index 22632b4df..01926fe63 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/hotcold/HotColdLocation.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/hotcold/HotColdLocation.java
@@ -151,6 +151,7 @@ public enum HotColdLocation
 	VARLAMORE_BAZAAR(MASTER, new WorldPoint(1680, 3107, 0), VARLAMORE, "In the centre of the Bazaar in Civitas illa Fortis.", BRASSICAN_MAGE),
 	VARLAMORE_LOCUS_OASIS(MASTER, new WorldPoint(1695, 2990, 0), VARLAMORE, "Amongst the trees at the Locus Oasis.", BRASSICAN_MAGE),
 	VARLAMORE_SUNSET_COAST(MASTER, new WorldPoint(1534, 2997, 0), VARLAMORE, "North-east corner of Sunset Coast.", ANCIENT_WIZARDS),
+	VARLAMORE_VILLA_LUCENS(MASTER, new WorldPoint(1469, 2933, 0), VARLAMORE, "On the Villa Lucens theatre stage in Aldarin. Completion of Death on the Isle is required.", BRASSICAN_MAGE),
 	WESTERN_PROVINCE_EAGLES_PEAK(MASTER, new WorldPoint(2297, 3529, 0), WESTERN_PROVINCE, "North-west of Eagles' Peak.", BRASSICAN_MAGE),
 	WESTERN_PROVINCE_PISCATORIS(MASTER, new WorldPoint(2334, 3685, 0), WESTERN_PROVINCE, "Piscatoris Fishing Colony", ANCIENT_WIZARDS),
 	WESTERN_PROVINCE_PISCATORIS_HUNTER_AREA(MASTER, new WorldPoint(2359, 3564, 0), WESTERN_PROVINCE, "Eastern part of Piscatoris Hunter area, south-west of the Falconry.", BRASSICAN_MAGE),
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 24a137054..ad29096fe 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
@@ -342,6 +342,10 @@ public class IdleNotifierPlugin extends Plugin
 			case HERBLORE_PESTLE_AND_MORTAR:
 			case HERBLORE_POTIONMAKING:
 			case HERBLORE_MAKE_TAR:
+			case HERBLORE_MIXOLOGY_CONCENTRATE:
+			case HERBLORE_MIXOLOGY_CRYSTALIZE:
+			case HERBLORE_MIXOLOGY_HOMOGENIZE:
+			case HERBLORE_MIXOLOGY_REFINER:
 			/* Magic */
 			case MAGIC_CHARGING_ORBS:
 			case MAGIC_LUNAR_PLANK_MAKE:
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/itemcharges/ItemWithCharge.java b/runelite-client/src/main/java/net/runelite/client/plugins/itemcharges/ItemWithCharge.java
index ed815bb78..774295aad 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/itemcharges/ItemWithCharge.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/itemcharges/ItemWithCharge.java
@@ -546,6 +546,22 @@ enum ItemWithCharge
 	FORGOTTEN_BR2(POTION, FORGOTTEN_BREW2, 2),
 	FORGOTTEN_BR3(POTION, FORGOTTEN_BREW3, 3),
 	FORGOTTEN_BR4(POTION, FORGOTTEN_BREW4, 4),
+	MENAPHITE_REM1(POTION, MENAPHITE_REMEDY1, 1),
+	MENAPHITE_REM2(POTION, MENAPHITE_REMEDY2, 2),
+	MENAPHITE_REM3(POTION, MENAPHITE_REMEDY3, 3),
+	MENAPHITE_REM4(POTION, MENAPHITE_REMEDY4, 4),
+	EXTENDED_ANTI_VENOM1(POTION, EXTENDED_ANTIVENOM1, 1),
+	EXTENDED_ANTI_VENOM2(POTION, EXTENDED_ANTIVENOM2, 2),
+	EXTENDED_ANTI_VENOM3(POTION, EXTENDED_ANTIVENOM3, 3),
+	EXTENDED_ANTI_VENOM4(POTION, EXTENDED_ANTIVENOM4, 4),
+	GOADING1(POTION, GOADING_POTION1, 1),
+	GOADING2(POTION, GOADING_POTION2, 2),
+	GOADING3(POTION, GOADING_POTION3, 3),
+	GOADING4(POTION, GOADING_POTION4, 4),
+	PRAYER_REGENERATION1(POTION, PRAYER_REGENERATION_POTION1, 1),
+	PRAYER_REGENERATION2(POTION, PRAYER_REGENERATION_POTION2, 2),
+	PRAYER_REGENERATION3(POTION, PRAYER_REGENERATION_POTION3, 3),
+	PRAYER_REGENERATION4(POTION, PRAYER_REGENERATION_POTION4, 4),
 	;
 
 	private final ItemChargeType type;
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/ItemStatChanges.java b/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/ItemStatChanges.java
index e526a7be6..eab1a0a39 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/ItemStatChanges.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/ItemStatChanges.java
@@ -34,9 +34,13 @@ import static net.runelite.api.ItemID.*;
 import static net.runelite.client.plugins.itemstats.Builders.*;
 import net.runelite.client.plugins.itemstats.delta.DeltaPercentage;
 import net.runelite.client.plugins.itemstats.food.Anglerfish;
+import net.runelite.client.plugins.itemstats.food.CookedBream;
+import net.runelite.client.plugins.itemstats.food.CookedMossLizard;
+import net.runelite.client.plugins.itemstats.special.SunlightMoth;
 import net.runelite.client.plugins.itemstats.potions.Ambrosia;
 import net.runelite.client.plugins.itemstats.potions.AncientBrew;
 import net.runelite.client.plugins.itemstats.potions.MixedPotion;
+import net.runelite.client.plugins.itemstats.potions.MoonlightPotion;
 import net.runelite.client.plugins.itemstats.potions.PrayerPotion;
 import net.runelite.client.plugins.itemstats.potions.SaradominBrew;
 import net.runelite.client.plugins.itemstats.potions.StaminaPotion;
@@ -56,6 +60,13 @@ public class ItemStatChanges
 		init();
 	}
 
+	public static final SingleEffect ATTACK_POT = boost(ATTACK, perc(.10, 3));
+	public static final SingleEffect STRENGTH_POT = boost(STRENGTH, perc(.10, 3));
+	public static final SingleEffect DEFENCE_POT = boost(DEFENCE, perc(.10, 3));
+	public static final SingleEffect SUPER_ATTACK_POT = boost(ATTACK, perc(.15, 5));
+	public static final SingleEffect SUPER_STRENGTH_POT = boost(STRENGTH, perc(.15, 5));
+	public static final SingleEffect SUPER_DEFENCE_POT = boost(DEFENCE, perc(.15, 5));
+
 	private void init()
 	{
 		add(food(-5), POISON_KARAMBWAN);
@@ -105,6 +116,8 @@ public class ItemStatChanges
 		add(food(maxHP -> (int) Math.ceil(maxHP * .06)), STRAWBERRY);
 		add(food(maxHP -> (int) Math.ceil(maxHP * .05)), WATERMELON_SLICE);
 		add(food(perc(.1, 1)), COOKED_SWEETCORN, SWEETCORN_7088 /* Bowl of cooked sweetcorn */);
+		add(new CookedBream(), COOKED_BREAM);
+		add(new CookedMossLizard(), COOKED_MOSS_LIZARD);
 		add(combo(food(1), boost(DEFENCE, perc(.02, 1))), CABBAGE_1967 /* Draynor Manor */);
 		add(combo(food(8), heal(RUN_ENERGY, 5)), PAPAYA_FRUIT);
 		add(combo(food(3), boost(ATTACK, perc(.02, 2))), CUP_OF_TEA_1978 /* Standard tea */);
@@ -168,6 +181,12 @@ public class ItemStatChanges
 		add(combo(food(2), heal(PRAYER, perc(.04, -2))), BLOODY_BRACER);
 		add(combo(food(1), boost(AGILITY, 1), heal(STRENGTH, -1)), ELVEN_DAWN);
 		add(combo(boost(RANGED, 4), new BoostedStatBoost(STRENGTH, false, perc(.04, -2)), new BoostedStatBoost(MAGIC, false, perc(.04, -2))), LIZARDKICKER);
+		add(combo(food(1), boost(HUNTER, 2), dec(STRENGTH, 1), dec(ATTACK, 2)), TRAPPERS_TIPPLE);
+		add(combo(food(1), boost(AGILITY, 1), boost(STRENGTH, 1), new BoostedStatBoost(ATTACK, false, perc(.05, -1))), SUNBEAM_ALE);
+		add(combo(food(5), boost(STRENGTH, perc(.05, 1)), new BoostedStatBoost(ATTACK, false, perc(0.02, -3))), MOONLITE);
+		add(combo(food(5), boost(STRENGTH, perc(.05, 1)), new BoostedStatBoost(ATTACK, false, perc(0.02, -3))), SUNSHINE);
+		add(combo(food(5), boost(STRENGTH, 5), new BoostedStatBoost(ATTACK, false, perc(.02, -3))), RUM_28896);
+		add(combo(food(16), boost(WOODCUTTING, 1), heal(ATTACK, -5), heal(FLETCHING, -1)), ECLIPSE_RED);
 
 		// Sq'irk Juice
 		add(heal(RUN_ENERGY, 5), WINTER_SQIRKJUICE);
@@ -176,73 +195,68 @@ public class ItemStatChanges
 		add(combo(heal(RUN_ENERGY, 20), boost(THIEVING, 3)), SUMMER_SQIRKJUICE);
 
 		// Combat potions
-		final SingleEffect attackPot = boost(ATTACK, perc(.10, 3));
-		final SingleEffect strengthPot = boost(STRENGTH, perc(.10, 3));
-		final SingleEffect defencePot = boost(DEFENCE, perc(.10, 3));
-		final Effect combatPot = combo(attackPot, strengthPot);
+		final Effect combatPot = combo(ATTACK_POT, STRENGTH_POT);
 		final Effect magicEssence = boost(MAGIC, 3);
 		final SingleEffect magicPot = boost(MAGIC, 4);
 		final SingleEffect imbuedHeart = boost(MAGIC, perc(.10, 1));
 		final SingleEffect rangingPot = boost(RANGED, perc(.10, 4));
-		final SingleEffect superAttackPot = boost(ATTACK, perc(.15, 5));
-		final SingleEffect superStrengthPot = boost(STRENGTH, perc(.15, 5));
-		final SingleEffect superDefencePot = boost(DEFENCE, perc(.15, 5));
 		final SingleEffect superMagicPot = boost(MAGIC, perc(.15, 5));
 		final SingleEffect superRangingPot = boost(RANGED, perc(.15, 5));
 		final SingleEffect divinePot = heal(HITPOINTS, -10);
 		final Effect zamorakBrew = combo(boost(ATTACK, perc(.20, 2)), boost(STRENGTH, perc(.12, 2)), heal(PRAYER, perc(.10, 0)), new BoostedStatBoost(DEFENCE, false, perc(.10, -2)), new BoostedStatBoost(HITPOINTS, false, perc(-.12, 0)));
 		final Effect ancientBrew = new AncientBrew(.05, 2);
-		add(attackPot, ATTACK_POTION1, ATTACK_POTION2, ATTACK_POTION3, ATTACK_POTION4);
-		add(strengthPot, STRENGTH_POTION1, STRENGTH_POTION2, STRENGTH_POTION3, STRENGTH_POTION4);
-		add(defencePot, DEFENCE_POTION1, DEFENCE_POTION2, DEFENCE_POTION3, DEFENCE_POTION4);
+		add(ATTACK_POT, ATTACK_POTION1, ATTACK_POTION2, ATTACK_POTION3, ATTACK_POTION4);
+		add(STRENGTH_POT, STRENGTH_POTION1, STRENGTH_POTION2, STRENGTH_POTION3, STRENGTH_POTION4);
+		add(DEFENCE_POT, DEFENCE_POTION1, DEFENCE_POTION2, DEFENCE_POTION3, DEFENCE_POTION4);
 		add(magicPot, MAGIC_POTION1, MAGIC_POTION2, MAGIC_POTION3, MAGIC_POTION4);
 		add(rangingPot, RANGING_POTION1, RANGING_POTION2, RANGING_POTION3, RANGING_POTION4,
 			RANGING_POTION4_23551, RANGING_POTION3_23553, RANGING_POTION2_23555, RANGING_POTION1_23557 /* LMS */);
 		add(combatPot, COMBAT_POTION1, COMBAT_POTION2, COMBAT_POTION3, COMBAT_POTION4,
 			COMBAT_POTION4_26150, COMBAT_POTION3_26151, COMBAT_POTION2_26152, COMBAT_POTION1_26153 /* Deadman starter pack */);
-		add(superAttackPot, SUPER_ATTACK1, SUPER_ATTACK2, SUPER_ATTACK3, SUPER_ATTACK4);
-		add(superStrengthPot, SUPER_STRENGTH1, SUPER_STRENGTH2, SUPER_STRENGTH3, SUPER_STRENGTH4);
-		add(superDefencePot, SUPER_DEFENCE1, SUPER_DEFENCE2, SUPER_DEFENCE3, SUPER_DEFENCE4);
+		add(SUPER_ATTACK_POT, SUPER_ATTACK1, SUPER_ATTACK2, SUPER_ATTACK3, SUPER_ATTACK4);
+		add(SUPER_STRENGTH_POT, SUPER_STRENGTH1, SUPER_STRENGTH2, SUPER_STRENGTH3, SUPER_STRENGTH4);
+		add(SUPER_DEFENCE_POT, SUPER_DEFENCE1, SUPER_DEFENCE2, SUPER_DEFENCE3, SUPER_DEFENCE4);
 		add(magicEssence, MAGIC_ESSENCE1, MAGIC_ESSENCE2, MAGIC_ESSENCE3, MAGIC_ESSENCE4);
-		add(combo(superAttackPot, superStrengthPot, superDefencePot), SUPER_COMBAT_POTION1, SUPER_COMBAT_POTION2, SUPER_COMBAT_POTION3, SUPER_COMBAT_POTION4);
+		add(combo(SUPER_ATTACK_POT, SUPER_STRENGTH_POT, SUPER_DEFENCE_POT), SUPER_COMBAT_POTION1, SUPER_COMBAT_POTION2, SUPER_COMBAT_POTION3, SUPER_COMBAT_POTION4);
 		add(zamorakBrew, ZAMORAK_BREW1, ZAMORAK_BREW2, ZAMORAK_BREW3, ZAMORAK_BREW4);
 		add(new SaradominBrew(0.15, 0.2, 0.1, 2, 2), SARADOMIN_BREW1, SARADOMIN_BREW2, SARADOMIN_BREW3,
 			SARADOMIN_BREW4, SARADOMIN_BREW4_23575, SARADOMIN_BREW3_23577, SARADOMIN_BREW2_23579, SARADOMIN_BREW1_23581 /* LMS */);
 		add(superRangingPot, SUPER_RANGING_1, SUPER_RANGING_2, SUPER_RANGING_3, SUPER_RANGING_4);
 		add(superMagicPot, SUPER_MAGIC_POTION_1, SUPER_MAGIC_POTION_2, SUPER_MAGIC_POTION_3, SUPER_MAGIC_POTION_4);
-		add(combo(rangingPot, superDefencePot), BASTION_POTION1, BASTION_POTION2, BASTION_POTION3, BASTION_POTION4);
-		add(combo(magicPot, superDefencePot), BATTLEMAGE_POTION1, BATTLEMAGE_POTION2, BATTLEMAGE_POTION3, BATTLEMAGE_POTION4);
+		add(combo(rangingPot, SUPER_DEFENCE_POT), BASTION_POTION1, BASTION_POTION2, BASTION_POTION3, BASTION_POTION4);
+		add(combo(magicPot, SUPER_DEFENCE_POT), BATTLEMAGE_POTION1, BATTLEMAGE_POTION2, BATTLEMAGE_POTION3, BATTLEMAGE_POTION4);
 		add(combo(magicPot, divinePot), DIVINE_MAGIC_POTION1, DIVINE_MAGIC_POTION2, DIVINE_MAGIC_POTION3, DIVINE_MAGIC_POTION4);
 		add(combo(rangingPot, divinePot), DIVINE_RANGING_POTION1, DIVINE_RANGING_POTION2, DIVINE_RANGING_POTION3, DIVINE_RANGING_POTION4);
-		add(combo(superAttackPot, divinePot), DIVINE_SUPER_ATTACK_POTION1, DIVINE_SUPER_ATTACK_POTION2, DIVINE_SUPER_ATTACK_POTION3, DIVINE_SUPER_ATTACK_POTION4);
-		add(combo(superStrengthPot, divinePot), DIVINE_SUPER_STRENGTH_POTION1, DIVINE_SUPER_STRENGTH_POTION2, DIVINE_SUPER_STRENGTH_POTION3, DIVINE_SUPER_STRENGTH_POTION4);
-		add(combo(superDefencePot, divinePot), DIVINE_SUPER_DEFENCE_POTION1, DIVINE_SUPER_DEFENCE_POTION2, DIVINE_SUPER_DEFENCE_POTION3, DIVINE_SUPER_DEFENCE_POTION4);
-		add(combo(superAttackPot, superStrengthPot, superDefencePot, divinePot), DIVINE_SUPER_COMBAT_POTION1, DIVINE_SUPER_COMBAT_POTION2, DIVINE_SUPER_COMBAT_POTION3, DIVINE_SUPER_COMBAT_POTION4);
-		add(combo(rangingPot, superDefencePot, divinePot), DIVINE_BASTION_POTION1, DIVINE_BASTION_POTION2, DIVINE_BASTION_POTION3, DIVINE_BASTION_POTION4);
-		add(combo(magicPot, superDefencePot, divinePot), DIVINE_BATTLEMAGE_POTION1, DIVINE_BATTLEMAGE_POTION2, DIVINE_BATTLEMAGE_POTION3, DIVINE_BATTLEMAGE_POTION4);
-		add(combo(superAttackPot, superStrengthPot, superDefencePot, rangingPot, imbuedHeart),
+		add(combo(SUPER_ATTACK_POT, divinePot), DIVINE_SUPER_ATTACK_POTION1, DIVINE_SUPER_ATTACK_POTION2, DIVINE_SUPER_ATTACK_POTION3, DIVINE_SUPER_ATTACK_POTION4);
+		add(combo(SUPER_STRENGTH_POT, divinePot), DIVINE_SUPER_STRENGTH_POTION1, DIVINE_SUPER_STRENGTH_POTION2, DIVINE_SUPER_STRENGTH_POTION3, DIVINE_SUPER_STRENGTH_POTION4);
+		add(combo(SUPER_DEFENCE_POT, divinePot), DIVINE_SUPER_DEFENCE_POTION1, DIVINE_SUPER_DEFENCE_POTION2, DIVINE_SUPER_DEFENCE_POTION3, DIVINE_SUPER_DEFENCE_POTION4);
+		add(combo(SUPER_ATTACK_POT, SUPER_STRENGTH_POT, SUPER_DEFENCE_POT, divinePot), DIVINE_SUPER_COMBAT_POTION1, DIVINE_SUPER_COMBAT_POTION2, DIVINE_SUPER_COMBAT_POTION3, DIVINE_SUPER_COMBAT_POTION4);
+		add(combo(rangingPot, SUPER_DEFENCE_POT, divinePot), DIVINE_BASTION_POTION1, DIVINE_BASTION_POTION2, DIVINE_BASTION_POTION3, DIVINE_BASTION_POTION4);
+		add(combo(magicPot, SUPER_DEFENCE_POT, divinePot), DIVINE_BATTLEMAGE_POTION1, DIVINE_BATTLEMAGE_POTION2, DIVINE_BATTLEMAGE_POTION3, DIVINE_BATTLEMAGE_POTION4);
+		add(combo(SUPER_ATTACK_POT, SUPER_STRENGTH_POT, SUPER_DEFENCE_POT, rangingPot, imbuedHeart),
 			CASTLEWARS_BREW4, CASTLEWARS_BREW3, CASTLEWARS_BREW2, CASTLEWARS_BREW1);
-		add(combo(superAttackPot, superStrengthPot),
+		add(combo(SUPER_ATTACK_POT, SUPER_STRENGTH_POT),
 			SUPER_COMBAT_POTION4_23543, SUPER_COMBAT_POTION3_23545, SUPER_COMBAT_POTION2_23547, SUPER_COMBAT_POTION1_23549 /* LMS */);
 		add(ancientBrew, ANCIENT_BREW1, ANCIENT_BREW2, ANCIENT_BREW3, ANCIENT_BREW4);
 		add(new AncientBrew(.08, 3), FORGOTTEN_BREW1, FORGOTTEN_BREW2, FORGOTTEN_BREW3, FORGOTTEN_BREW4);
+		add(new MoonlightPotion(), MOONLIGHT_POTION1, MOONLIGHT_POTION2, MOONLIGHT_POTION3, MOONLIGHT_POTION4);
 
 		// Mixed combat potions
-		add(new MixedPotion(3, attackPot), ATTACK_MIX1, ATTACK_MIX2);
-		add(new MixedPotion(3, strengthPot), STRENGTH_MIX1, STRENGTH_MIX2);
+		add(new MixedPotion(3, ATTACK_POT), ATTACK_MIX1, ATTACK_MIX2);
+		add(new MixedPotion(3, STRENGTH_POT), STRENGTH_MIX1, STRENGTH_MIX2);
 		add(new MixedPotion(3, combatPot), COMBAT_MIX1, COMBAT_MIX2);
-		add(new MixedPotion(6, defencePot), DEFENCE_MIX1, DEFENCE_MIX2);
+		add(new MixedPotion(6, DEFENCE_POT), DEFENCE_MIX1, DEFENCE_MIX2);
 		add(new MixedPotion(6, magicPot), MAGIC_MIX1, MAGIC_MIX2);
 		add(new MixedPotion(6, rangingPot), RANGING_MIX1, RANGING_MIX2);
-		add(new MixedPotion(6, superAttackPot), SUPERATTACK_MIX1, SUPERATTACK_MIX2);
-		add(new MixedPotion(6, superStrengthPot), SUPER_STR_MIX1, SUPER_STR_MIX2);
-		add(new MixedPotion(6, superDefencePot), SUPER_DEF_MIX1, SUPER_DEF_MIX2);
+		add(new MixedPotion(6, SUPER_ATTACK_POT), SUPERATTACK_MIX1, SUPERATTACK_MIX2);
+		add(new MixedPotion(6, SUPER_STRENGTH_POT), SUPER_STR_MIX1, SUPER_STR_MIX2);
+		add(new MixedPotion(6, SUPER_DEFENCE_POT), SUPER_DEF_MIX1, SUPER_DEF_MIX2);
 		add(new MixedPotion(6, magicEssence), MAGIC_ESSENCE_MIX1, MAGIC_ESSENCE_MIX2);
 		add(new MixedPotion(6, zamorakBrew), ZAMORAK_MIX1, ZAMORAK_MIX2);
 		add(new MixedPotion(6, ancientBrew), ANCIENT_MIX1, ANCIENT_MIX2);
 
 		// Regular overload (NMZ)
-		add(combo(superAttackPot, superStrengthPot, superDefencePot, superRangingPot, superMagicPot, heal(HITPOINTS, -50)), OVERLOAD_1, OVERLOAD_2, OVERLOAD_3, OVERLOAD_4);
+		add(combo(SUPER_ATTACK_POT, SUPER_STRENGTH_POT, SUPER_DEFENCE_POT, superRangingPot, superMagicPot, heal(HITPOINTS, -50)), OVERLOAD_1, OVERLOAD_2, OVERLOAD_3, OVERLOAD_4);
 
 		// Bandages (Castle Wars)
 		add(new CastleWarsBandage(), BANDAGES);
@@ -358,6 +372,15 @@ public class ItemStatChanges
 		add(combo(heal(HITPOINTS, perc(.15, 1)), heal(RUN_ENERGY, 100)), BANDAGES_25202);
 		add(combo(boost(ATTACK, perc(.15, 5)), boost(STRENGTH, perc(.15, 5)), boost(DEFENCE, perc(.15, 5)), boost(RANGED, perc(.15, 5)), boost(MAGIC, perc(.15, 5)), heal(PRAYER, perc(.25, 8))), POTION_OF_POWER1, POTION_OF_POWER2, POTION_OF_POWER3, POTION_OF_POWER4);
 
+		// Moths & Butterflies
+		add(new SunlightMoth(.2, 6), SUNLIGHT_MOTH_28890, SUNLIGHT_MOTH_MIX_1, SUNLIGHT_MOTH_MIX_2);
+		add(heal(PRAYER, 22), MOONLIGHT_MOTH_28893, MOONLIGHT_MOTH_MIX_1, MOONLIGHT_MOTH_MIX_2);
+		add(heal(HITPOINTS, 15), SNOWY_KNIGHT);
+		add(heal(HITPOINTS, 8), SNOWY_KNIGHT_MIX_1, SNOWY_KNIGHT_MIX_2);
+		add(boost(ATTACK, perc(.15, 4)), RUBY_HARVEST, RUBY_HARVEST_MIX_1, RUBY_HARVEST_MIX_2);
+		add(boost(STRENGTH, perc(.15, 4)), BLACK_WARLOCK, BLACK_WARLOCK_MIX_1, BLACK_WARLOCK_MIX_2);
+		add(boost(DEFENCE, perc(.15, 4)), SAPPHIRE_GLACIALIS, SAPPHIRE_GLACIALIS_MIX_1, SAPPHIRE_GLACIALIS_MIX_2);
+
 		log.debug("{} items; {} behaviours loaded", effects.size(), new HashSet<>(effects.values()).size());
 	}
 
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/food/CookedBream.java b/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/food/CookedBream.java
new file mode 100644
index 000000000..bd725dccc
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/food/CookedBream.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2024 Macweese
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ *    list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package net.runelite.client.plugins.itemstats.food;
+
+import net.runelite.api.Client;
+import net.runelite.client.plugins.itemstats.FoodBase;
+import static net.runelite.client.plugins.itemstats.stats.Stats.COOKING;
+import static net.runelite.client.plugins.itemstats.stats.Stats.FISHING;
+
+public class CookedBream extends FoodBase
+{
+	@Override
+	public int heals(Client client)
+	{
+		int cooking = COOKING.getValue(client) / 3;
+		int fishing = FISHING.getValue(client) / 3;
+		return Math.max(7, Math.min(cooking, fishing));
+	}
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/food/CookedMossLizard.java b/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/food/CookedMossLizard.java
new file mode 100644
index 000000000..6103028de
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/food/CookedMossLizard.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2024 Macweese
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ *    list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package net.runelite.client.plugins.itemstats.food;
+
+import net.runelite.api.Client;
+import net.runelite.client.plugins.itemstats.FoodBase;
+import static net.runelite.client.plugins.itemstats.stats.Stats.COOKING;
+import static net.runelite.client.plugins.itemstats.stats.Stats.HUNTER;
+
+public class CookedMossLizard extends FoodBase
+{
+	@Override
+	public int heals(Client client)
+	{
+		int cooking = COOKING.getValue(client) / 3;
+		int hunter = HUNTER.getValue(client) / 2;
+		return Math.min(cooking, hunter);
+	}
+}
\ No newline at end of file
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/potions/MoonlightPotion.java b/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/potions/MoonlightPotion.java
new file mode 100644
index 000000000..b3230328d
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/potions/MoonlightPotion.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (c) 2024 Macweese
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ *    list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package net.runelite.client.plugins.itemstats.potions;
+
+import java.util.ArrayList;
+import java.util.Optional;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import net.runelite.api.Client;
+import net.runelite.api.Skill;
+import net.runelite.client.plugins.itemstats.Effect;
+import net.runelite.client.plugins.itemstats.ItemStatChanges;
+import net.runelite.client.plugins.itemstats.SimpleStatBoost;
+import net.runelite.client.plugins.itemstats.SingleEffect;
+import net.runelite.client.plugins.itemstats.StatBoost;
+import net.runelite.client.plugins.itemstats.StatChange;
+import net.runelite.client.plugins.itemstats.StatsChanges;
+import net.runelite.client.plugins.itemstats.delta.DeltaPercentage;
+import net.runelite.client.plugins.itemstats.stats.Stat;
+import java.util.Arrays;
+import java.util.Comparator;
+import static net.runelite.client.plugins.itemstats.stats.Stats.ATTACK;
+import static net.runelite.client.plugins.itemstats.stats.Stats.DEFENCE;
+import static net.runelite.client.plugins.itemstats.stats.Stats.HERBLORE;
+import static net.runelite.client.plugins.itemstats.stats.Stats.PRAYER;
+import static net.runelite.client.plugins.itemstats.stats.Stats.STRENGTH;
+
+@RequiredArgsConstructor
+public class MoonlightPotion implements Effect
+{
+	@Override
+	public StatsChanges calculate(Client client)
+	{
+		StatsChanges changes = new StatsChanges(4);
+		ArrayList<StatChange> statChanges = new ArrayList<>();
+
+		getStatChange(client, ATTACK).ifPresent(e -> statChanges.add(e.getEffect().effect(client)));
+		getStatChange(client, DEFENCE).ifPresent(e -> statChanges.add(e.getEffect().effect(client)));
+		getStatChange(client, STRENGTH).ifPresent(e -> statChanges.add(e.getEffect().effect(client)));
+		getStatChange(client, PRAYER).ifPresent(e -> statChanges.add(e.getEffect().effect(client)));
+
+		changes.setStatChanges(statChanges.toArray(StatChange[]::new));
+		return changes;
+	}
+
+	private Optional<StatEffect> getStatChange(Client client, Stat stat)
+	{
+		return Arrays.stream(StatEffect.values())
+			.filter(t -> t.getStat() == stat && client.getBoostedSkillLevel(Skill.HERBLORE) >= t.getLevelRequirement())
+			.max(Comparator.comparingInt(StatEffect::getLevelRequirement));
+	}
+}
+
+@Getter
+enum StatEffect
+{
+	ATTACK_POTION(ATTACK, 3, ItemStatChanges.ATTACK_POT),
+	DEFENCE_POTION(DEFENCE, 30, ItemStatChanges.DEFENCE_POT),
+	// Misnomer: Moonlight potion gives a stronger effect than Super Defence on defence but requires the level for,
+	// and activates the var/effect timer for—albeit without the drain prevention from—Divine Super Defence potion
+	DIVINE_SUPER_DEFENCE_POTION(DEFENCE, 70, new SimpleStatBoost(DEFENCE, true, new DeltaPercentage(.2, 7))),
+	STRENGTH_POTION(STRENGTH, 12, ItemStatChanges.STRENGTH_POT),
+	SUPER_ATTACK_POTION(ATTACK, 45, ItemStatChanges.SUPER_ATTACK_POT),
+	SUPER_DEFENCE_POTION(DEFENCE, 66, ItemStatChanges.SUPER_DEFENCE_POT),
+	SUPER_STRENGTH_POTION(STRENGTH, 55, ItemStatChanges.SUPER_STRENGTH_POT),
+	PRAYER_POTION(PRAYER, 38, new StatBoost(PRAYER, false)
+	{
+		@Override
+		public int heals(Client client)
+		{
+			int pray = PRAYER.getMaximum(client);
+			int herb = HERBLORE.getValue(client);
+			return herb < 38 ? 0 : (int) Math.max(pray * 0.25, herb * 0.3) + 7;
+		}
+	}),
+	;
+
+	private final Stat stat;
+	private final int levelRequirement;
+	private final SingleEffect effect;
+
+	StatEffect(Stat stat, int levelRequirement, SingleEffect effect)
+	{
+		this.stat = stat;
+		this.levelRequirement = levelRequirement;
+		this.effect = effect;
+	}
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/special/SunlightMoth.java b/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/special/SunlightMoth.java
new file mode 100644
index 000000000..d99e05c53
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/special/SunlightMoth.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2024, Macweese
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ *    list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package net.runelite.client.plugins.itemstats.special;
+
+import net.runelite.api.Client;
+import net.runelite.client.plugins.itemstats.Effect;
+import net.runelite.client.plugins.itemstats.SimpleStatBoost;
+import net.runelite.client.plugins.itemstats.StatChange;
+import net.runelite.client.plugins.itemstats.StatsChanges;
+import net.runelite.client.plugins.itemstats.stats.Stat;
+import java.util.Comparator;
+import java.util.stream.Stream;
+import static net.runelite.client.plugins.itemstats.Builders.perc;
+import static net.runelite.client.plugins.itemstats.stats.Stats.*;
+
+public class SunlightMoth implements Effect
+{
+	private static final Stat[] RESTORED_STATS = {
+		ATTACK, DEFENCE, STRENGTH, RANGED, MAGIC, COOKING,
+		WOODCUTTING, FLETCHING, FISHING, FIREMAKING, CRAFTING, SMITHING, MINING,
+		HERBLORE, AGILITY, THIEVING, SLAYER, FARMING, RUNECRAFT, HUNTER,
+		CONSTRUCTION
+	};
+
+	public final double percentRestored;
+	private final int delta;
+
+	public SunlightMoth(double percentRestored, int delta)
+	{
+		this.percentRestored = percentRestored;
+		this.delta = delta;
+	}
+
+	@Override
+	public StatsChanges calculate(Client client)
+	{
+		StatsChanges changes = new StatsChanges(0);
+
+		SimpleStatBoost calcRestore = new SimpleStatBoost(null, false, perc(percentRestored, delta));
+		SimpleStatBoost calcHeal = new SimpleStatBoost(HITPOINTS, false, perc(0, 8));
+
+		changes.setStatChanges(Stream.concat(
+			Stream.of(new Stat[]{HITPOINTS})
+				.map(stat -> calcHeal.effect(client)),
+			Stream.of(RESTORED_STATS)
+				.filter(stat -> stat.getValue(client) < stat.getMaximum(client))
+				.map(stat ->
+				{
+					calcRestore.setStat(stat);
+					return calcRestore.effect(client);
+				})
+		).toArray(StatChange[]::new));
+		changes.setPositivity(Stream.of(changes.getStatChanges())
+				.map(StatChange::getPositivity)
+				.max(Comparator.naturalOrder()).orElse(null));
+		return changes;
+	}
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/npchighlight/NpcIndicatorsPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/npchighlight/NpcIndicatorsPlugin.java
index 1e7d100ba..6e92bc229 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/npchighlight/NpcIndicatorsPlugin.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/npchighlight/NpcIndicatorsPlugin.java
@@ -87,6 +87,8 @@ import net.runelite.client.util.WildcardMatcher;
 public class NpcIndicatorsPlugin extends Plugin
 {
 	private static final int MAX_ACTOR_VIEW_RANGE = 15;
+	// Longest known respawn time is currently held by Suspicious Water.
+	private static final int MAX_RESPAWN_TIME_TICKS = 500;
 
 	// Option added to NPC menu
 	private static final String TAG = "Tag";
@@ -748,7 +750,9 @@ public class NpcIndicatorsPlugin extends Plugin
 						// By killing a monster and leaving the area before seeing it again, an erroneously lengthy
 						// respawn time can be recorded. Thus, if the respawn time is already set and is greater than
 						// the observed time, assume that the lower observed respawn time is correct.
-						if (mn.getRespawnTime() == -1 || respawnTime < mn.getRespawnTime())
+						// Similarly, if the observed respawn time exceeds the maximum known respawn time,
+						// we can ignore the calculated time as obviously incorrect.
+						if ((mn.getRespawnTime() == -1 || respawnTime < mn.getRespawnTime()) && respawnTime <= MAX_RESPAWN_TIME_TICKS)
 						{
 							mn.setRespawnTime(respawnTime);
 						}
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 698250a67..1a170ee5b 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
@@ -50,6 +50,8 @@ import net.runelite.api.widgets.InterfaceID;
 import net.runelite.api.widgets.Widget;
 import static net.runelite.api.widgets.WidgetConfig.DRAG;
 import static net.runelite.api.widgets.WidgetConfig.DRAG_ON;
+import net.runelite.api.widgets.WidgetSizeMode;
+import net.runelite.api.widgets.WidgetType;
 import net.runelite.api.widgets.WidgetUtil;
 import net.runelite.client.callback.ClientThread;
 import net.runelite.client.chat.ChatMessageManager;
@@ -407,6 +409,8 @@ class PrayerReorder
 
 				++index;
 			}
+
+			createWarning(unlocked);
 		}
 
 		if (isInterfaceOpen(InterfaceID.QUICK_PRAYER))
@@ -455,6 +459,23 @@ class PrayerReorder
 		}
 	}
 
+	private void createWarning(boolean unlocked)
+	{
+		Widget w = client.getWidget(ComponentID.PRAYER_PARENT);
+		w.deleteAllChildren();
+
+		if (unlocked)
+		{
+			Widget c = w.createChild(WidgetType.RECTANGLE);
+			c.setHeightMode(WidgetSizeMode.MINUS);
+			c.setWidthMode(WidgetSizeMode.MINUS);
+			c.setTextColor(0xff0000);
+			c.setFilled(true);
+			c.setOpacity(220);
+			c.revalidate();
+		}
+	}
+
 	private boolean isInterfaceOpen(@Interface int interfaceId)
 	{
 		return client.getWidget(interfaceId, 0) != null;
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/skillcalculator/skills/AgilityAction.java b/runelite-client/src/main/java/net/runelite/client/plugins/skillcalculator/skills/AgilityAction.java
index 383958503..ed6d2dcd0 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/skillcalculator/skills/AgilityAction.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/skillcalculator/skills/AgilityAction.java
@@ -42,7 +42,7 @@ public enum AgilityAction implements NamedSkillAction
 {
 	GNOME_STRONGHOLD_COURSE("Gnome Stronghold", 1, 110.5f, ItemID.SWAMP_TOAD),
 	DRAYNOR_VILLAGE_ROOFTOP("Draynor Village Rooftop", 1, 120, ItemID.MARK_OF_GRACE),
-	SHAYZIEN_BASIC_COURSE("Shayzien Basic Course", 1, 153.5f, ItemID.SHAYZIEN_HELM_1),
+	SHAYZIEN_BASIC_COURSE("Shayzien Basic Course", 1, 153.5f, ItemID.SHAYZIEN_BANNER),
 	LEAPING_TROUT("Leaping trout", 15, 5, ItemID.LEAPING_TROUT),
 	AL_KHARID_ROOFTOP("Al Kharid Rooftop", 20, 216, ItemID.MARK_OF_GRACE),
 	LEAPING_SALMON("Leaping salmon", 30, 6, ItemID.LEAPING_SALMON),
@@ -51,16 +51,16 @@ public enum AgilityAction implements NamedSkillAction
 	BARBARIAN_OUTPOST("Barbarian Outpost", 35, 152.5f, ItemID.STEEL_BATTLEAXE),
 	CANIFIS_ROOFTOP("Canifis Rooftop", 40, 240, ItemID.MARK_OF_GRACE),
 	LEAPING_STURGEON("Leaping sturgeon", 45, 7, ItemID.LEAPING_STURGEON),
-	SHAYZIEN_ADVANCED_COURSE("Shayzien Advanced Course", 45, 508, ItemID.SHAYZIEN_HELM_5),
+	SHAYZIEN_ADVANCED_COURSE("Shayzien Advanced Course", 45, 508, ItemID.SHAYZIEN_BANNER),
 	APE_ATOLL_COURSE("Ape Atoll", 48, 580, ItemID.GORILLA_GREEGREE),
-	COLOSSAL_WYRM_BASIC_COURSE("Colossal Wyrm Basic", 50, 504, ItemID.TERMITES),
+	COLOSSAL_WYRM_BASIC_COURSE("Colossal Wyrm Basic", 50, 504.1f, ItemID.TERMITES),
 	FALADOR_ROOFTOP("Falador Rooftop", 50, 586, ItemID.MARK_OF_GRACE),
 	WILDERNESS_AGILITY_COURSE_TICKET("Wilderness Agility Ticket", 52, 200, ItemID.WILDERNESS_AGILITY_TICKET),
 	WILDERNESS_AGILITY_COURSE("Wilderness Agility Course", 52, 571.4f, ItemID.SKULL),
 	HALLOWED_SEPULCHRE_FLOOR_1("Hallowed Sepulchre Floor 1", 52, 575, ItemID.RING_OF_ENDURANCE),
 	SEERS_VILLAGE_ROOFTOP("Seers' Village Rooftop", 60, 570, ItemID.MARK_OF_GRACE),
 	WEREWOLF_AGILITY_COURSE("Werewolf Agility Course", 60, 730, ItemID.STICK),
-	COLOSSAL_WYRM_ADVANCED_COURSE("Colossal Wyrm Advanced", 62, 685, ItemID.BONE_SQUIRREL),
+	COLOSSAL_WYRM_ADVANCED_COURSE("Colossal Wyrm Advanced", 62, 749.6f, ItemID.TERMITES),
 	HALLOWED_SEPULCHRE_FLOOR_2("Hallowed Sepulchre Floor 2", 62, 925, ItemID.RING_OF_ENDURANCE),
 	POLLNIVNEACH_ROOFTOP("Pollnivneach Rooftop", 70, 890, ItemID.MARK_OF_GRACE),
 	DORGESH_KAAN_AGILITY_COURSE("Dorgesh-Kaan Agility Course", 70, 2750, ItemID.POWERBOX),
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/skillcalculator/skills/HerbloreAction.java b/runelite-client/src/main/java/net/runelite/client/plugins/skillcalculator/skills/HerbloreAction.java
index 985867b35..f56a58e47 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/skillcalculator/skills/HerbloreAction.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/skillcalculator/skills/HerbloreAction.java
@@ -71,47 +71,47 @@ public enum HerbloreAction implements ItemSkillAction
 	HUASCA(ItemID.HUASCA, 58, 11.8f),
 	PRAYER_REGENERATION_POTION(ItemID.PRAYER_REGENERATION_POTION3, 58, 132),
 	SNAPDRAGON(ItemID.SNAPDRAGON, 59, 11.8f),
-	MAMMOTHMIGHT_MIX(ItemID.MAMMOTHMIGHT_MIX, 60, 135),
 	WEAPON_POISON(ItemID.WEAPON_POISON, 60, 137.5f),
-	MYSTIC_MANA_AMALGAM(ItemID.MYSTIC_MANA_AMALGAM, 60, 175),
-	MARLEYS_MOONLIGHT(ItemID.MARLEYS_MOONLIGHT, 60, 215),
+	ALCOAUGMENTATOR(ItemID.ALCOAUGMENTATOR, 60, 190),
+	LIPLACK_LIQUOR(ItemID.LIPLACK_LIQUOR, 60, 190),
+	MAMMOTHMIGHT_MIX(ItemID.MAMMOTHMIGHT_MIX, 60, 190),
 	SUPER_RESTORE_3(ItemID.SUPER_RESTORE3, 63, 142.5f),
-	MIXALOT(ItemID.MIXALOT, 64, 255),
+	MYSTIC_MANA_AMALGAM(ItemID.MYSTIC_MANA_AMALGAM, 63, 215),
 	CADANTINE(ItemID.CADANTINE, 65, 12.5f),
 	SANFEW_SERUM_3(ItemID.SANFEW_SERUM3, 65, 160),
 	SUPER_DEFENCE_3(ItemID.SUPER_DEFENCE3, 66, 150),
+	MARLEYS_MOONLIGHT(ItemID.MARLEYS_MOONLIGHT, 66, 240),
 	LANTADYME(ItemID.LANTADYME, 67, 13.1f),
 	ANTIDOTE_PLUS_4(ItemID.ANTIDOTE4, 68, 155),
-	AZURE_AURA_MIX(ItemID.AZURE_AURA_MIX, 68, 215),
 	ANTIFIRE_POTION_3(ItemID.ANTIFIRE_POTION3, 69, 157.5f),
+	AZURE_AURA_MIX(ItemID.AZURE_AURA_MIX, 69, 265),
 	DIVINE_SUPER_ATTACK_POTION_4(ItemID.DIVINE_SUPER_ATTACK_POTION4, 70, 2),
 	DIVINE_SUPER_DEFENCE_POTION_4(ItemID.DIVINE_SUPER_DEFENCE_POTION4, 70, 2),
 	DIVINE_SUPER_STRENGTH_POTION_4(ItemID.DIVINE_SUPER_STRENGTH_POTION4, 70, 2),
 	DWARF_WEED(ItemID.DWARF_WEED, 70, 13.8f),
 	RANGING_POTION_3(ItemID.RANGING_POTION3, 72, 162.5f),
-	AQUALUX_AMALGAM(ItemID.AQUALUX_AMALGAM, 72, 295),
+	AQUALUX_AMALGAM(ItemID.AQUALUX_AMALGAM, 72, 290),
 	WEAPON_POISON_PLUS(ItemID.WEAPON_POISON_5937, 73, 165),
 	DIVINE_RANGING_POTION_4(ItemID.DIVINE_RANGING_POTION4, 74, 2),
 	TORSTOL(ItemID.TORSTOL, 75, 15),
+	MEGALITE_LIQUID(ItemID.MEGALITE_LIQUID, 75, 315),
 	MAGIC_POTION_3(ItemID.MAGIC_POTION3, 76, 172.5f),
-	ALCOAUGMENTATOR(ItemID.ALCOAUGMENTATOR, 76, 255),
 	STAMINA_POTION_3(ItemID.STAMINA_POTION3, 77, 76.5f),
 	STAMINA_POTION_4(ItemID.STAMINA_POTION4, 77, 102),
 	DIVINE_MAGIC_POTION_4(ItemID.DIVINE_MAGIC_POTION4, 78, 2),
 	ZAMORAK_BREW_3(ItemID.ZAMORAK_BREW3, 78, 175),
+	ANTILEECH_LOTION(ItemID.ANTILEECH_LOTION, 78, 340),
 	ANTIDOTE_PLUS_PLUS_4(ItemID.ANTIDOTE4_5952, 79, 177.5f),
 	BASTION_POTION_3(ItemID.BASTION_POTION3, 80, 155),
 	BATTLEMAGE_POTION_3(ItemID.BATTLEMAGE_POTION3, 80, 155),
-	MEGALITE_LIQUID(ItemID.MEGALITE_LIQUID, 80, 295),
 	SARADOMIN_BREW_3(ItemID.SARADOMIN_BREW3, 81, 180),
+	MIXALOT(ItemID.MIXALOT, 81, 365),
 	WEAPON_POISON_PLUS_PLUS(ItemID.WEAPON_POISON_5940, 82, 190),
 	EXTENDED_ANTIFIRE_3(ItemID.EXTENDED_ANTIFIRE3, 84, 82.5f),
 	EXTENDED_ANTIFIRE_4(ItemID.EXTENDED_ANTIFIRE4, 84, 110),
-	ANTILEECH_LOTION(ItemID.ANTILEECH_LOTION, 84, 335),
 	ANCIENT_BREW_4(ItemID.ANCIENT_BREW4, 85, 190),
 	DIVINE_BASTION_POTION_4(ItemID.DIVINE_BASTION_POTION4, 86, 2),
 	DIVINE_BATTLEMAGE_POTION_4(ItemID.DIVINE_BATTLEMAGE_POTION4, 86, 2),
-	LIPLACK_LIQUOR(ItemID.LIPLACK_LIQUOR, 86, 375),
 	ANTIVENOM_3(ItemID.ANTIVENOM3, 87, 90),
 	ANTIVENOM_4(ItemID.ANTIVENOM4, 87, 120),
 	MENAPHITE_REMEDY_3(ItemID.MENAPHITE_REMEDY3, 88, 200),
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/slayer/Task.java b/runelite-client/src/main/java/net/runelite/client/plugins/slayer/Task.java
index e7c104c5b..f9c263050 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/slayer/Task.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/slayer/Task.java
@@ -43,6 +43,7 @@ enum Task
 	ADAMANT_DRAGONS("Adamant dragons", ItemID.ADAMANT_DRAGON_MASK),
 	ALCHEMICAL_HYDRA("The Alchemical Hydra", ItemID.IKKLE_HYDRA),
 	ANKOU("Ankou", ItemID.ANKOU_MASK),
+	ARAXXOR("Araxxor", ItemID.NID),
 	ARAXYTES("Araxytes", ItemID.ARAXYTE_HEAD, "Araxxor"),
 	AVIANSIES("Aviansies", ItemID.ENSOULED_AVIANSIE_HEAD, "Kree'arra", "Flight Kilisa", "Flockleader Geerin", "Wingman Skree"),
 	BANDITS("Bandits", ItemID.BANDIT, "Bandit", "Black Heather", "Donny the Lad", "Speedy Keith"),
@@ -124,7 +125,7 @@ enum Task
 	KURASK("Kurask", ItemID.KURASK),
 	LAVA_DRAGONS("Lava Dragons", ItemID.LAVA_SCALE, "Lava dragon"),
 	LESSER_DEMONS("Lesser demons", ItemID.LESSER_DEMON_MASK, "Zakl'n Gritch"),
-	LESSER_NAGUA("Lesser Nagua", ItemID.LESSER_NAGUA, "Sulphur Nagua"),
+	LESSER_NAGUA("Lesser Nagua", ItemID.LESSER_NAGUA, "Sulphur Nagua", "Frost Nagua", "Amoxliatl"),
 	LIZARDMEN("Lizardmen", ItemID.LIZARDMAN_FANG, "Lizardman"),
 	LIZARDS("Lizards", ItemID.DESERT_LIZARD),
 	MAGIC_AXES("Magic axes", ItemID.IRON_BATTLEAXE),
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/spellbook/SpellbookPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/spellbook/SpellbookPlugin.java
index 813643977..fb735f009 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/spellbook/SpellbookPlugin.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/spellbook/SpellbookPlugin.java
@@ -47,6 +47,8 @@ import net.runelite.api.widgets.JavaScriptCallback;
 import net.runelite.api.widgets.Widget;
 import static net.runelite.api.widgets.WidgetConfig.DRAG;
 import static net.runelite.api.widgets.WidgetConfig.DRAG_ON;
+import net.runelite.api.widgets.WidgetSizeMode;
+import net.runelite.api.widgets.WidgetType;
 import net.runelite.api.widgets.WidgetUtil;
 import net.runelite.client.callback.ClientThread;
 import net.runelite.client.chat.ChatMessageManager;
@@ -289,6 +291,8 @@ public class SpellbookPlugin extends Plugin
 			return;
 		}
 
+		createWarning(reordering);
+
 		// this is called after ~magic_spellbook_redraw has built and sorted the array of visible spells
 		// based on the vanilla filtering
 
@@ -361,6 +365,23 @@ public class SpellbookPlugin extends Plugin
 		stack[size - 1] = numSpells = numNewSpells;
 	}
 
+	private void createWarning(boolean unlocked)
+	{
+		Widget w = client.getWidget(ComponentID.SPELLBOOK_PARENT);
+		w.deleteAllChildren();
+
+		if (unlocked)
+		{
+			Widget c = w.createChild(WidgetType.RECTANGLE);
+			c.setHeightMode(WidgetSizeMode.MINUS);
+			c.setWidthMode(WidgetSizeMode.MINUS);
+			c.setTextColor(0xff0000);
+			c.setFilled(true);
+			c.setOpacity(220);
+			c.revalidate();
+		}
+	}
+
 	private void initializeSpells(int spellbookEnum)
 	{
 		EnumComposition spellbook = client.getEnum(spellbookEnum);
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/TeleportLocationData.java b/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/TeleportLocationData.java
index 9297b8f91..3d96efda7 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/TeleportLocationData.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/TeleportLocationData.java
@@ -200,6 +200,7 @@ enum TeleportLocationData
 	KEY_MASTER_SCROLL(TeleportType.SCROLL, "Key Master Teleport", new WorldPoint(2686, 9882, 0), "scroll_teleport_icon.png"),
 	REVENANT_CAVE_SCROLL(TeleportType.SCROLL, "Revenant Cave Teleport", new WorldPoint(3127, 3833, 0), "scroll_teleport_icon.png"),
 	WATSON_SCROLL(TeleportType.SCROLL, "Watson Teleport", new WorldPoint(1645, 3579, 0), "scroll_teleport_icon.png"),
+	SPIDER_CAVE_SCROLL(TeleportType.SCROLL, "Spider cave teleport", new WorldPoint(3658, 3403, 0), "scroll_teleport_icon.png"),
 
 	// Skillcapes
 	ACHIEVEMENT_CAPE_TWO_PINTS(TeleportType.OTHER, "Achievement Cape", "Two-pints", new WorldPoint(2574, 3324, 0), "achievement_cape_icon.png"),
diff --git a/runelite-client/src/main/scripts/BankMainBuild.hash b/runelite-client/src/main/scripts/BankMainBuild.hash
index 006d22d9c..4d1c9b45d 100644
--- a/runelite-client/src/main/scripts/BankMainBuild.hash
+++ b/runelite-client/src/main/scripts/BankMainBuild.hash
@@ -1,1 +1,1 @@
-C2F0851465815DB751D28B1D6459FA00DB85273452BA6DE14BB2B5A10FB423AA
\ No newline at end of file
+AA424C74E9017ACF4DAB947F6F25501D2C65E53974355CB7CE860AE203C9969F
\ No newline at end of file
diff --git a/runelite-client/src/main/scripts/BankMainBuild.rs2asm b/runelite-client/src/main/scripts/BankMainBuild.rs2asm
index f05efe39d..677acd83d 100644
--- a/runelite-client/src/main/scripts/BankMainBuild.rs2asm
+++ b/runelite-client/src/main/scripts/BankMainBuild.rs2asm
@@ -204,7 +204,7 @@ LABEL174:
    iload                  18
    iconst                 1
    if_icmpeq              LABEL180
-   jump                   LABEL204
+   jump                   LABEL210
 LABEL180:
    iconst                 1
    iload                  8
@@ -223,14 +223,20 @@ LABEL180:
    iconst                 1
    iload                  3
    if_setsize            
+   iconst                 53
+   iconst                 39
+   iconst                 1
+   iconst                 1
+   iconst                 786447
+   if_setsize            
    iconst                 28
    iconst                 42
    iconst                 2
    iconst                 0
    iload                  1
    if_setposition        
-   jump                   LABEL225
-LABEL204:
+   jump                   LABEL237
+LABEL210:
    iconst                 0
    iload                  8
    if_sethide            
@@ -246,28 +252,34 @@ LABEL204:
    iconst                 1
    iload                  3
    if_setsize            
+   iconst                 53
+   iconst                 81
+   iconst                 1
+   iconst                 1
+   iconst                 786447
+   if_setsize            
    iconst                 12
    iconst                 42
    iconst                 2
    iconst                 0
    iload                  1
    if_setposition        
-LABEL225:
+LABEL237:
    get_varbit             4150
    iconst                 15
-   if_icmpeq              LABEL229
-   jump                   LABEL234
-LABEL229:
+   if_icmpeq              LABEL241
+   jump                   LABEL246
+LABEL241:
    iconst                 0
    iconst                 786447
    if_sethide            
    invoke                 6080
-   jump                   LABEL237
-LABEL234:
+   jump                   LABEL249
+LABEL246:
    iconst                 1
    iconst                 786447
    if_sethide            
-LABEL237:
+LABEL249:
    iload                  3
    iload                  2
    invoke                 231
@@ -279,28 +291,28 @@ LABEL237:
    multiply              
    add                   
    istore                 21
-LABEL248:
+LABEL260:
    iload                  20
    iload                  21
-   if_icmple              LABEL252
-   jump                   LABEL265
-LABEL252:
+   if_icmple              LABEL264
+   jump                   LABEL277
+LABEL264:
    iload                  2
    iload                  20
    cc_find               
    iconst                 1
-   if_icmpeq              LABEL258
-   jump                   LABEL260
-LABEL258:
+   if_icmpeq              LABEL270
+   jump                   LABEL272
+LABEL270:
    iconst                 1
    cc_sethide            
-LABEL260:
+LABEL272:
    iload                  20
    iconst                 1
    add                   
    istore                 20
-   jump                   LABEL248
-LABEL265:
+   jump                   LABEL260
+LABEL277:
    iconst                 0
    istore                 20
    iconst                 8
@@ -347,52 +359,52 @@ LABEL265:
    if_icmpeq              singletabbuildmode ;
    get_varbit             4150
    iconst                 0
-   if_icmple              LABEL309
+   if_icmple              LABEL321
    get_varbit             4150
    iconst                 9
-   if_icmpgt              LABEL309
-   jump                   LABEL766
-LABEL309:
+   if_icmpgt              LABEL321
+   jump                   LABEL778
+LABEL321:
    get_varbit             4150
    iconst                 15
-   if_icmpne              LABEL313
-   jump                   LABEL766
-LABEL313:
+   if_icmpne              LABEL325
+   jump                   LABEL778
+LABEL325:
    iload                  20
    iconst                 1220
-   if_icmplt              LABEL317
-   jump                   LABEL342
-LABEL317:
+   if_icmplt              LABEL329
+   jump                   LABEL354
+LABEL329:
    iload                  2
    iload                  20
    cc_find               
    iconst                 1
-   if_icmpeq              LABEL323
-   jump                   LABEL325
-LABEL323:
+   if_icmpeq              LABEL335
+   jump                   LABEL337
+LABEL335:
    iconst                 1
    cc_sethide            
-LABEL325:
+LABEL337:
    iconst                 95
    iload                  20
    inv_getobj            
    iconst                 -1
-   if_icmpne              LABEL331
-   jump                   LABEL337
-LABEL331:
+   if_icmpne              LABEL343
+   jump                   LABEL349
+LABEL343:
    iload                  29
    iconst                 1
    add                   
    iload                  20
    istore                 30
    istore                 29
-LABEL337:
+LABEL349:
    iload                  20
    iconst                 1
    add                   
    istore                 20
-   jump                   LABEL313
-LABEL342:
+   jump                   LABEL325
+LABEL354:
    get_varbit             4171
    get_varbit             4172
    add                   
@@ -413,14 +425,14 @@ LABEL342:
    istore                 31
    iload                  31
    iconst                 0
-   if_icmple              LABEL364
-   jump                   LABEL368
-LABEL364:
+   if_icmple              LABEL376
+   jump                   LABEL380
+LABEL376:
    iconst                 1220
    iconst                 1
    sub                   
    istore                 30
-LABEL368:
+LABEL380:
    iload                  31
    iload                  30
    iconst                 1
@@ -446,9 +458,9 @@ LABEL368:
    istore                 20
    get_varbit             4171
    iconst                 0
-   if_icmpgt              LABEL395
-   jump                   LABEL425
-LABEL395:
+   if_icmpgt              LABEL407
+   jump                   LABEL437
+LABEL407:
    iconst                 1
    iload                  2
    iload                  28
@@ -479,12 +491,12 @@ LABEL395:
    get_varbit             4171
    add                   
    istore                 20
-LABEL425:
+LABEL437:
    get_varbit             4172
    iconst                 0
-   if_icmpgt              LABEL429
-   jump                   LABEL459
-LABEL429:
+   if_icmpgt              LABEL441
+   jump                   LABEL471
+LABEL441:
    iconst                 2
    iload                  2
    iload                  28
@@ -515,12 +527,12 @@ LABEL429:
    get_varbit             4172
    add                   
    istore                 20
-LABEL459:
+LABEL471:
    get_varbit             4173
    iconst                 0
-   if_icmpgt              LABEL463
-   jump                   LABEL493
-LABEL463:
+   if_icmpgt              LABEL475
+   jump                   LABEL505
+LABEL475:
    iconst                 3
    iload                  2
    iload                  28
@@ -551,12 +563,12 @@ LABEL463:
    get_varbit             4173
    add                   
    istore                 20
-LABEL493:
+LABEL505:
    get_varbit             4174
    iconst                 0
-   if_icmpgt              LABEL497
-   jump                   LABEL527
-LABEL497:
+   if_icmpgt              LABEL509
+   jump                   LABEL539
+LABEL509:
    iconst                 4
    iload                  2
    iload                  28
@@ -587,12 +599,12 @@ LABEL497:
    get_varbit             4174
    add                   
    istore                 20
-LABEL527:
+LABEL539:
    get_varbit             4175
    iconst                 0
-   if_icmpgt              LABEL531
-   jump                   LABEL561
-LABEL531:
+   if_icmpgt              LABEL543
+   jump                   LABEL573
+LABEL543:
    iconst                 5
    iload                  2
    iload                  28
@@ -623,12 +635,12 @@ LABEL531:
    get_varbit             4175
    add                   
    istore                 20
-LABEL561:
+LABEL573:
    get_varbit             4176
    iconst                 0
-   if_icmpgt              LABEL565
-   jump                   LABEL595
-LABEL565:
+   if_icmpgt              LABEL577
+   jump                   LABEL607
+LABEL577:
    iconst                 6
    iload                  2
    iload                  28
@@ -659,12 +671,12 @@ LABEL565:
    get_varbit             4176
    add                   
    istore                 20
-LABEL595:
+LABEL607:
    get_varbit             4177
    iconst                 0
-   if_icmpgt              LABEL599
-   jump                   LABEL629
-LABEL599:
+   if_icmpgt              LABEL611
+   jump                   LABEL641
+LABEL611:
    iconst                 7
    iload                  2
    iload                  28
@@ -695,12 +707,12 @@ LABEL599:
    get_varbit             4177
    add                   
    istore                 20
-LABEL629:
+LABEL641:
    get_varbit             4178
    iconst                 0
-   if_icmpgt              LABEL633
-   jump                   LABEL663
-LABEL633:
+   if_icmpgt              LABEL645
+   jump                   LABEL675
+LABEL645:
    iconst                 8
    iload                  2
    iload                  28
@@ -731,12 +743,12 @@ LABEL633:
    get_varbit             4178
    add                   
    istore                 20
-LABEL663:
+LABEL675:
    get_varbit             4179
    iconst                 0
-   if_icmpgt              LABEL667
-   jump                   LABEL697
-LABEL667:
+   if_icmpgt              LABEL679
+   jump                   LABEL709
+LABEL679:
    iconst                 9
    iload                  2
    iload                  28
@@ -767,12 +779,12 @@ LABEL667:
    get_varbit             4179
    add                   
    istore                 20
-LABEL697:
+LABEL709:
    invoke                 514
    iconst                 1
-   if_icmpeq              LABEL701
-   jump                   LABEL742
-LABEL701:
+   if_icmpeq              LABEL713
+   jump                   LABEL754
+LABEL713:
    jump                   GET_INPUT_TEXT ;  Skip truncating of varcstr 22 by not calling 280
    invoke                 280
 GET_INPUT_TEXT:                          ;
@@ -782,9 +794,9 @@ GET_INPUT_TEXT:                          ;
    sload                  0
    string_length         
    iconst                 0
-   if_icmpgt              LABEL708
-   jump                   LABEL727
-LABEL708:
+   if_icmpgt              LABEL720
+   jump                   LABEL739
+LABEL720:
    sconst                 "Showing items: "
    sconst                 "<col=ff0000>"
    sload                  0
@@ -794,9 +806,9 @@ LABEL708:
    if_settext            
    get_varc_int           5
    iconst                 11
-   if_icmpeq              LABEL719
-   jump                   LABEL726
-LABEL719:
+   if_icmpeq              LABEL731
+   jump                   LABEL738
+LABEL731:
    sconst                 "Show items whose names contain the following text: ("
    iload                  27
    tostring              
@@ -808,9 +820,9 @@ LABEL719:
    pop_int               ; pop number of matches
    iconst                 10616873
    if_settext            
-LABEL726:
-   jump                   LABEL741
-LABEL727:
+LABEL738:
+   jump                   LABEL753
+LABEL739:
    sconst                 "Showing items: "
    sconst                 "<col=ff0000>"
    sconst                 "*"
@@ -820,21 +832,21 @@ LABEL727:
    if_settext            
    get_varc_int           5
    iconst                 11
-   if_icmpeq              LABEL738
-   jump                   LABEL741
-LABEL738:
+   if_icmpeq              LABEL750
+   jump                   LABEL753
+LABEL750:
    sconst                 "Show items whose names contain the following text:"
    sconst                 "setSearchBankInputText"  ; load event name
    runelite_callback     ; invoke callback
    iconst                 10616873
    if_settext            
-LABEL741:
-   jump                   LABEL745
-LABEL742:
+LABEL753:
+   jump                   LABEL757
+LABEL754:
    sconst                 "The Bank of Gielinor"
    iload                  5
    if_settext            
-LABEL745:
+LABEL757:
    iload                  0
    iload                  1
    iload                  2
@@ -878,18 +890,18 @@ singletabbuildmode:
    get_varbit             4179  ;
    add                          ;
    istore                 31    ; store start to the first item in the "All items" tab
-LABEL766:
+LABEL778:
   ; if (~bankmain_searching = 1) {
    invoke                 514
    iconst                 1
-   if_icmpeq              LABEL770
-   jump                   LABEL774
-LABEL770:
+   if_icmpeq              LABEL782
+   jump                   LABEL786
+LABEL782:
    iconst                 1
    iconst                 1
    iconst                 1
    invoke                 299
-LABEL774:
+LABEL786:
   ; after ~meslayer close
    iconst                 -1
    istore                 32
@@ -903,12 +915,12 @@ LABEL774:
    istore                 34
    iconst                 0
    istore                 35
-LABEL786:
+LABEL798:
    iload                  20
    iconst                 1220
-   if_icmplt              LABEL790
-   jump                   LABEL870
-LABEL790:
+   if_icmplt              LABEL802
+   jump                   LABEL882
+LABEL802:
    ; item index = (loop index + offset) % bank size
    iload                  20   ; loop index
    iload                  31   ; offset
@@ -923,9 +935,9 @@ LOAD_ITEM_INDEX:
    iload                  30   ; use item index instead of loop index
    cc_find               
    iconst                 1
-   if_icmpeq              LABEL796
-   jump                   LABEL865
-LABEL796:
+   if_icmpeq              LABEL808
+   jump                   LABEL877
+LABEL808:
    iconst                 95
    jump                   LOAD_ITEM_INDEX2
    iload                  20
@@ -935,32 +947,32 @@ LOAD_ITEM_INDEX2:
    istore                 25
    iload                  25
    iconst                 -1
-   if_icmpne              LABEL804
-   jump                   LABEL808
-LABEL804:
+   if_icmpne              LABEL816
+   jump                   LABEL820
+LABEL816:
    iload                  29
    iconst                 1
    add                   
    istore                 29
-LABEL808:
+LABEL820:
    iload                  36         ; overriding single tab building mode?
    iconst                 1          ;
    if_icmpeq              filtertest ;
    iload                  20
    iload                  32
-   if_icmpge              LABEL812
-   jump                   LABEL863
-LABEL812:
+   if_icmpge              LABEL824
+   jump                   LABEL875
+LABEL824:
    iload                  20
    iload                  33
-   if_icmplt              LABEL816
-   jump                   LABEL863
+   if_icmplt              LABEL828
+   jump                   LABEL875
 filtertest:
    iload                  25        ; obj
    invoke                 279       ; ~bankmain_filtertest
    iconst                 1         ;
-   if_icmpne              LABEL863  ;
-LABEL816:
+   if_icmpne              LABEL875  ;
+LABEL828:
    iconst                 0
    cc_sethide            
    iload                  25
@@ -997,49 +1009,49 @@ LOAD_ITEM_INDEX3:
    istore                 28
    iload                  34
    iload                  22
-   if_icmplt              LABEL851
-   jump                   LABEL856
-LABEL851:
+   if_icmplt              LABEL863
+   jump                   LABEL868
+LABEL863:
    iload                  34
    iconst                 1
    add                   
    istore                 34
-   jump                   LABEL862
-LABEL856:
+   jump                   LABEL874
+LABEL868:
    iconst                 0
    iload                  35
    iconst                 1
    add                   
    istore                 35
    istore                 34
-LABEL862:
-   jump                   LABEL865
-LABEL863:
+LABEL874:
+   jump                   LABEL877
+LABEL875:
    iconst                 1
    cc_sethide            
-LABEL865:
+LABEL877:
    iload                  20
    iconst                 1
    add                   
    istore                 20
-   jump                   LABEL786
-LABEL870:
+   jump                   LABEL798
+LABEL882:
    iconst                 1
    iconst                 786447
    if_sethide            
    get_varbit             4150
    iconst                 15
-   if_icmpeq              LABEL877
-   jump                   LABEL884
-LABEL877:
+   if_icmpeq              LABEL889
+   jump                   LABEL896
+LABEL889:
    sconst                 "Potion store"
    iload                  5
    if_settext            
    iconst                 0
    iconst                 786447
    if_sethide            
-   jump                   LABEL904
-LABEL884:
+   jump                   LABEL916
+LABEL896:
    iload                  36                     ; overriding single tab building mode?
    iconst                 1                      ;
    if_icmpne              tabtitle               ; set normal "Tab" title
@@ -1050,9 +1062,9 @@ LABEL884:
 tabtitle:                                        ;
    get_varbit             4170
    iconst                 2
-   if_icmpeq              LABEL888
-   jump                   LABEL898
-LABEL888:
+   if_icmpeq              LABEL900
+   jump                   LABEL910
+LABEL900:
    sconst                 "Tab "
    iconst                 105
    iconst                 115
@@ -1062,8 +1074,8 @@ LABEL888:
    join_string            2
    iload                  5
    if_settext            
-   jump                   LABEL904
-LABEL898:
+   jump                   LABEL916
+LABEL910:
    sconst                 "Tab "
    get_varbit             4150
    tostring              
@@ -1071,7 +1083,7 @@ LABEL898:
    iload                  5
    if_settext            
 FinishBuilding:
-LABEL904:
+LABEL916:
    iload                  0
    iload                  1
    iload                  2
diff --git a/runelite-jshell/pom.xml b/runelite-jshell/pom.xml
index d921b66e5..61f866178 100644
--- a/runelite-jshell/pom.xml
+++ b/runelite-jshell/pom.xml
@@ -30,7 +30,7 @@
     <parent>
         <groupId>net.runelite</groupId>
         <artifactId>runelite-parent</artifactId>
-        <version>1.10.39-SNAPSHOT</version>
+        <version>1.10.41-SNAPSHOT</version>
     </parent>
 
     <artifactId>jshell</artifactId>
diff --git a/runelite-maven-plugin/pom.xml b/runelite-maven-plugin/pom.xml
index 76ff2979d..a3cb2f0d7 100644
--- a/runelite-maven-plugin/pom.xml
+++ b/runelite-maven-plugin/pom.xml
@@ -29,7 +29,7 @@
 	<parent>
 		<groupId>net.runelite</groupId>
 		<artifactId>runelite-parent</artifactId>
-		<version>1.10.39-SNAPSHOT</version>
+		<version>1.10.41-SNAPSHOT</version>
 	</parent>
 
 	<artifactId>runelite-maven-plugin</artifactId>


Test patch:
diff --git a/runelite-client/src/test/java/net/runelite/client/plugins/itemstats/ItemStatEffectTest.java b/runelite-client/src/test/java/net/runelite/client/plugins/itemstats/ItemStatEffectTest.java
index b9ed3a5c2..075dfc098 100644
--- a/runelite-client/src/test/java/net/runelite/client/plugins/itemstats/ItemStatEffectTest.java
+++ b/runelite-client/src/test/java/net/runelite/client/plugins/itemstats/ItemStatEffectTest.java
@@ -247,6 +247,176 @@ public class ItemStatEffectTest
 		assertEquals(0, skillChange(Skill.PRAYER, 99, 123, ambrosia));
 	}
 
+	@Test
+	public void testMoonlightPotion()
+	{
+		final Effect moonlightPotion = new ItemStatChanges().get(ItemID.MOONLIGHT_POTION1);
+
+		when(client.getBoostedSkillLevel(Skill.HERBLORE)).thenReturn(11);
+		assertEquals(12, skillChange(Skill.ATTACK, 99, 1, moonlightPotion));
+		assertEquals(0, skillChange(Skill.STRENGTH, 99, 99, moonlightPotion));
+		assertEquals(0, skillChange(Skill.DEFENCE, 40, 33, moonlightPotion));
+		assertEquals(0, skillChange(Skill.PRAYER, 99, 54, moonlightPotion));
+
+		when(client.getBoostedSkillLevel(Skill.HERBLORE)).thenReturn(38);
+		assertEquals(12, skillChange(Skill.ATTACK, 99, 99, moonlightPotion));
+		assertEquals(12, skillChange(Skill.STRENGTH, 99, 99, moonlightPotion));
+		assertEquals(4, skillChange(Skill.DEFENCE, 40, 43, moonlightPotion));
+		assertEquals(31, skillChange(Skill.PRAYER, 99, 0, moonlightPotion));
+		assertEquals(9, skillChange(Skill.PRAYER, 99, 90, moonlightPotion));
+
+		when(client.getBoostedSkillLevel(Skill.HERBLORE)).thenReturn(54);
+		assertEquals(3, skillChange(Skill.ATTACK, 60, 71, moonlightPotion));
+		assertEquals(2, skillChange(Skill.STRENGTH, 60, 67, moonlightPotion));
+		assertEquals(2, skillChange(Skill.DEFENCE, 60, 67, moonlightPotion));
+		assertEquals(31, skillChange(Skill.PRAYER, 99, 10, moonlightPotion));
+
+		when(client.getBoostedSkillLevel(Skill.HERBLORE)).thenReturn(65);
+		assertEquals(12, skillChange(Skill.ATTACK, 50, 19, moonlightPotion));
+		assertEquals(13, skillChange(Skill.STRENGTH, 58, 22, moonlightPotion));
+		assertEquals(0, skillChange(Skill.DEFENCE, 34, 40, moonlightPotion));
+		assertEquals(26, skillChange(Skill.PRAYER, 42, 0, moonlightPotion));
+		assertEquals(4, skillChange(Skill.PRAYER, 42, 38, moonlightPotion));
+
+		when(client.getBoostedSkillLevel(Skill.HERBLORE)).thenReturn(77);
+		assertEquals(30, skillChange(Skill.PRAYER, 77, 18, moonlightPotion));
+
+		when(client.getBoostedSkillLevel(Skill.HERBLORE)).thenReturn(80);
+		assertEquals(6, skillChange(Skill.ATTACK, 80, 91, moonlightPotion));
+		assertEquals(7, skillChange(Skill.STRENGTH, 92, 103, moonlightPotion));
+		assertEquals(8, skillChange(Skill.DEFENCE, 75, 89, moonlightPotion));
+		assertEquals(31, skillChange(Skill.PRAYER, 77, 18, moonlightPotion));
+
+		when(client.getBoostedSkillLevel(Skill.HERBLORE)).thenReturn(99);
+		assertEquals(19, skillChange(Skill.ATTACK, 99, 99, moonlightPotion));
+		assertEquals(19, skillChange(Skill.STRENGTH, 99, 99, moonlightPotion));
+		assertEquals(26, skillChange(Skill.DEFENCE, 99, 99, moonlightPotion));
+		assertEquals(36, skillChange(Skill.PRAYER, 99, 0, moonlightPotion));
+
+		when(client.getBoostedSkillLevel(Skill.HERBLORE)).thenReturn(102);
+		assertEquals(37, skillChange(Skill.PRAYER, 99, 0, moonlightPotion));
+
+		when(client.getBoostedSkillLevel(Skill.HERBLORE)).thenReturn(104);
+		assertEquals(33, skillChange(Skill.PRAYER, 34, 1, moonlightPotion));
+		assertEquals(38, skillChange(Skill.PRAYER, 54, 0, moonlightPotion));
+	}
+
+	@Test
+	public void testMoonlightMoth()
+	{
+		final Effect moonlightMoth = new ItemStatChanges().get(ItemID.MOONLIGHT_MOTH_28893);
+
+		assertEquals(0, skillChange(Skill.PRAYER, 1, 1, moonlightMoth));
+		assertEquals(5, skillChange(Skill.PRAYER, 7, 2, moonlightMoth));
+		assertEquals(22, skillChange(Skill.PRAYER, 30, 0, moonlightMoth));
+		assertEquals(19, skillChange(Skill.PRAYER, 68, 49, moonlightMoth));
+		assertEquals(22, skillChange(Skill.PRAYER, 99, 70, moonlightMoth));
+
+		final Effect moonlightMothMix = new ItemStatChanges().get(ItemID.MOONLIGHT_MOTH_MIX_1);
+
+		assertEquals(0, skillChange(Skill.PRAYER, 1, 1, moonlightMothMix));
+		assertEquals(5, skillChange(Skill.PRAYER, 7, 2, moonlightMothMix));
+		assertEquals(22, skillChange(Skill.PRAYER, 30, 0, moonlightMothMix));
+		assertEquals(19, skillChange(Skill.PRAYER, 68, 49, moonlightMothMix));
+		assertEquals(22, skillChange(Skill.PRAYER, 99, 70, moonlightMothMix));
+	}
+
+	@Test
+	public void testSunlightMoth()
+	{
+		final Effect sunlightMoth = new ItemStatChanges().get(ItemID.SUNLIGHT_MOTH_28890);
+
+		assertEquals(16, skillChange(Skill.AGILITY, 50, 1, sunlightMoth));
+		assertEquals(0, skillChange(Skill.ATTACK, 70, 70, sunlightMoth));
+		assertEquals(17, skillChange(Skill.CONSTRUCTION, 66, 49, sunlightMoth));
+		assertEquals(0, skillChange(Skill.COOKING, 70, 70, sunlightMoth));
+		assertEquals(12, skillChange(Skill.CRAFTING, 70, 58, sunlightMoth));
+		assertEquals(21, skillChange(Skill.DEFENCE, 75, 2, sunlightMoth));
+		assertEquals(13, skillChange(Skill.FARMING, 83, 70, sunlightMoth));
+		assertEquals(1, skillChange(Skill.FIREMAKING, 71, 70, sunlightMoth));
+		assertEquals(19, skillChange(Skill.FISHING, 89, 70, sunlightMoth));
+		assertEquals(7, skillChange(Skill.FLETCHING, 77, 70, sunlightMoth));
+		assertEquals(0, skillChange(Skill.HERBLORE, 1, 1, sunlightMoth));
+		assertEquals(1, skillChange(Skill.HITPOINTS, 84, 83, sunlightMoth));
+		assertEquals(0, skillChange(Skill.HUNTER, 75, 77, sunlightMoth));
+		assertEquals(12, skillChange(Skill.MAGIC, 30, 0, sunlightMoth));
+		assertEquals(19, skillChange(Skill.MINING, 68, 49, sunlightMoth));
+		assertEquals(0, skillChange(Skill.PRAYER, 99, 70, sunlightMoth));
+		assertEquals(25, skillChange(Skill.RANGED, 99, 70, sunlightMoth));
+		assertEquals(25, skillChange(Skill.RUNECRAFT, 99, 70, sunlightMoth));
+		assertEquals(12, skillChange(Skill.SLAYER, 99, 87, sunlightMoth));
+		assertEquals(0, skillChange(Skill.SMITHING, 31, 31, sunlightMoth));
+		assertEquals(0, skillChange(Skill.STRENGTH, 68, 81, sunlightMoth));
+		assertEquals(11, skillChange(Skill.THIEVING, 28, 10, sunlightMoth));
+		assertEquals(3, skillChange(Skill.WOODCUTTING, 54, 51, sunlightMoth));
+
+		final Effect sunlightMothMix = new ItemStatChanges().get(ItemID.SUNLIGHT_MOTH_MIX_1);
+
+		assertEquals(16, skillChange(Skill.AGILITY, 50, 1, sunlightMothMix));
+		assertEquals(0, skillChange(Skill.ATTACK, 70, 70, sunlightMothMix));
+		assertEquals(17, skillChange(Skill.CONSTRUCTION, 66, 49, sunlightMothMix));
+		assertEquals(0, skillChange(Skill.COOKING, 70, 70, sunlightMothMix));
+		assertEquals(12, skillChange(Skill.CRAFTING, 70, 58, sunlightMothMix));
+		assertEquals(21, skillChange(Skill.DEFENCE, 75, 2, sunlightMothMix));
+		assertEquals(13, skillChange(Skill.FARMING, 83, 70, sunlightMothMix));
+		assertEquals(1, skillChange(Skill.FIREMAKING, 71, 70, sunlightMothMix));
+		assertEquals(19, skillChange(Skill.FISHING, 89, 70, sunlightMothMix));
+		assertEquals(7, skillChange(Skill.FLETCHING, 77, 70, sunlightMothMix));
+		assertEquals(0, skillChange(Skill.HERBLORE, 1, 1, sunlightMothMix));
+		assertEquals(1, skillChange(Skill.HITPOINTS, 84, 83, sunlightMothMix));
+		assertEquals(0, skillChange(Skill.HUNTER, 75, 77, sunlightMothMix));
+		assertEquals(12, skillChange(Skill.MAGIC, 30, 0, sunlightMothMix));
+		assertEquals(19, skillChange(Skill.MINING, 68, 49, sunlightMothMix));
+		assertEquals(0, skillChange(Skill.PRAYER, 99, 70, sunlightMothMix));
+		assertEquals(25, skillChange(Skill.RANGED, 99, 70, sunlightMothMix));
+		assertEquals(25, skillChange(Skill.RUNECRAFT, 99, 70, sunlightMothMix));
+		assertEquals(12, skillChange(Skill.SLAYER, 99, 87, sunlightMothMix));
+		assertEquals(0, skillChange(Skill.SMITHING, 31, 31, sunlightMothMix));
+		assertEquals(0, skillChange(Skill.STRENGTH, 68, 81, sunlightMothMix));
+		assertEquals(11, skillChange(Skill.THIEVING, 28, 10, sunlightMothMix));
+		assertEquals(3, skillChange(Skill.WOODCUTTING, 54, 51, sunlightMothMix));
+	}
+
+	@Test
+	public void testRubyHarvest()
+	{
+		final Effect rubyHarvest = new ItemStatChanges().get(ItemID.RUBY_HARVEST);
+
+		assertEquals(16, skillChange(Skill.ATTACK, 82, 82, rubyHarvest));
+	}
+
+	@Test
+	public void testSapphireGlacial()
+	{
+		final Effect sapphireGlacial = new ItemStatChanges().get(ItemID.SAPPHIRE_GLACIALIS);
+
+		assertEquals(13, skillChange(Skill.DEFENCE, 64, 64, sapphireGlacial));
+	}
+
+	@Test
+	public void testBlackWarlock()
+	{
+		final Effect blackWarlock = new ItemStatChanges().get(ItemID.BLACK_WARLOCK);
+
+		assertEquals(13, skillChange(Skill.STRENGTH, 64, 64, blackWarlock));
+	}
+
+	@Test
+	public void testSnowyKnight()
+	{
+		final Effect snowyKnight = new ItemStatChanges().get(ItemID.SNOWY_KNIGHT);
+
+		assertEquals(5, skillChange(Skill.HITPOINTS, 49, 44, snowyKnight));
+		assertEquals(0, skillChange(Skill.HITPOINTS, 64, 64, snowyKnight));
+		assertEquals(15, skillChange(Skill.HITPOINTS, 99, 77, snowyKnight));
+
+		final Effect snowyKnightMix = new ItemStatChanges().get(ItemID.SNOWY_KNIGHT_MIX_1);
+
+		assertEquals(5, skillChange(Skill.HITPOINTS, 50, 45, snowyKnightMix));
+		assertEquals(0, skillChange(Skill.HITPOINTS, 64, 64, snowyKnightMix));
+		assertEquals(8, skillChange(Skill.HITPOINTS, 99, 10, snowyKnightMix));
+	}
+
 	@Test
 	public void prayerRestoreVariants()
 	{