Repository: runelite/runelite
Pull request #17662 (2024-04-01 06:11:03)

timers: sectionize and add varlamore buffs
This PR:
- Renames timers to timers & buffs
- Sectionizes the config
- Adds the curse of the moons buff (#17586 unlisted)
- Adds the colosseum doom buff (#17586 unlisted)
- Makes vengeance into a buff counter (added later at the request of NFC)
- Adds a timer for the moonlight potion (added later since varbit wasn't initially available/known). Please refer to https://github.com/runelite/runelite/pull/17662#issuecomment-2067964626 for info about this commit

I've also included footage of ingame tests below. If you'd like me to split up the sectionizing into a separate PR, change/drop a commit, or perform other ingame tests etc., please let me know.

<details>
 <summary>Colosseum Doom tests</summary>

- Infobox persists between waves and gets removed when the player dies: https://youtu.be/zW9vs9m8DiQ
- Infobox gets removed while dying to doom: https://youtu.be/mHv0mvBWkUE
- Infobox gets removed when leaving the arena: https://youtu.be/FEEeGlXtDrg
- Infobox gets removed when teleporting out: https://youtu.be/aWUR-A_mcJs
- The varbit apparently does not get reset to 0 when teleporting out, so I added the regionId code because of that. This is before I added that regionId code: https://youtu.be/7pjB3K7_7wQ
  
</details>

<details>
<summary>Curse of the Moons tests</summary>
  
- Full run: https://youtu.be/XfM9v1dxRoQ
- Eclipse moon infobox gets removed when teleporting out: https://youtu.be/m78s1E7CIWI
- Eclipse moon infobox gets removed when leaving: https://youtu.be/P-86THsJp6k
- Eclipse moon infobox gets removed when dying: https://youtu.be/r9dhxFMGLIM
- Blue moon infobox gets removed when dying + shows off the ``joint lock up`` at 18 stacks: https://youtu.be/GFHiiJC8wKI
  - EHC's infobox text also changes color at 18 stacks, so this is not against the statement.
- Blue moon infobox gets removed when leaving: https://youtu.be/VH5dyxp7Fl4
- Blue moon infobox gets removed when tping out: https://youtu.be/nAqkg-6EcoU
  
</details>

Alternatively I could use <https://abextm.github.io/cache2/#/viewer/sprite/1361> as icon, which the official client does IIRC, but these icons make a lot more sense to me than a random yellow sprite.

YvesW: For some reason I can't respond to the comment (maybe because it's marked as ``outdated``), so I'll respond here instead.
Re: "This looks better. Do we know enough about the eclipse moon's stacks to add a similar description for it?":

Originally the ``Eclipse Moon`` wiki page contained information about the Curse of the Moon effect. However, when Spine moved that info to the ``Moons of Peril/Strategies#Eclipse Moon`` page, he removed the info about the stacks, because he was not sure what a "glancing blow" actually did. Since I did not want to add potentially incorrect information to the javadoc, I decided against adding it before.
I see now that the actual effect has been added to the ``Moons of Peril/Strategies#Eclipse Moon`` page since then. I've added it to the javadoc!
YvesW: As discussed before, I've added the moonlight potion's timer.
The moonlight potion is a special one though. See the [wiki](https://oldschool.runescape.wiki/w/Moonlight_potion#Formulas) for its effects. The prayer restore and attack/strength boost are not relevant for the timers (no divine timer).
- When <70 herblore, it will work as a normal or super att/str/def potion. No divine effects/timers.
- When >= 70 herblore:
  - The moonlight potion will act as a super attack and super strenght potion. These are non-divine; they keep draining over time, also after the potion's varbit/divine effect has ran out. Not relevant for this PR.
  - The defence buff is relevant though.
    - Ingame effect: its defence buff is bigger than that of a super defence potion. Its timer/varbit will tick down like a divine, after which your defence will be reset to its base level. Interestingly enough, unlike divines, the buff itself does drain over time.
    - Varbits: when a player takes a sip of the potion, ``Varbits.MOONLIGHT_POTION`` (10029) and ``Varbits.DIVINE_SUPER_DEFENCE`` will be set. You will even receive the ``Your divine potion effect is about to expire.`` and ``The effects of the divine potion have worn off.`` chatbox messages when it runs out.

Please note that by drinking a moonlight potion while already under the effect of one, you can get **desync** between the ``Varbits.MOONLIGHT_POTION`` and ``Varbits.DIVINE_SUPER_DEFENCE``, where the latter is 1 tick greater. This would cause both a moonlight potion and divine def timer to show up, and the moonlight potion timer would end 1 tick too soon. Thus, I've opted to implement a workaround.
I was not able to recreate this desync with divine SCBs; I suspect it's unique for the moonlight potion.

Video of the desync before the workaround:

https://github.com/runelite/runelite/assets/7929021/f0713e0f-b873-4380-aa0e-6d9d595e2cbc

Desync visible in the var inspector:
![moonlight_potion_desync_inspector](https://github.com/runelite/runelite/assets/7929021/d5e10ca0-7ef5-4e99-b558-28d760521063)


No desync:
```
8431 changed to 0 tick: 514
10029 changed to 0 tick: 514
DEFENCE boosted lvl: 99 level: 99 at tick: 514
```

Desync:
```
10029 changed to 1 tick: 1070
8431 changed to 1 tick: 1071
10029 changed to 0 tick: 1071 (<- moonlight timer varbit runs out at tick 1071)
ATTACK boosted lvl: 113 level: 99 at tick: 1071 (<- this is the normal stat drain)
DEFENCE boosted lvl: 120 level: 99 at tick: 1071 (<- this is the normal stat drain)
STRENGTH boosted lvl: 113 level: 99 at tick: 1071 (<- this is the normal stat drain)
8431 changed to 0 tick: 1072 (<- super def timer varbit runs out at tick 1072)
DEFENCE boosted lvl: 99 level: 99 at tick: 1072 (<- moonlight potion def effect runs out at tick 1072)
```

---

I also tested some interactions, partially because the moonlight/super def desync made me somewhat suspicious. Nothing really unexpected, except maybe when leaving the dungeon while under the effects of a divine SCB and moonlight potion. If you want to change this, please let me know, but I doubt it's worth it.
- Divine SCB & moonlight interaction
  - Drinking a divine SCB after moonlight potion
    - Varbits: moonlight varbit keeps ticking down, divine att/str/def/scb get set to new value.
    - Ingame stats: def buf remains >super def until it has slowly ticked down to divine def levels.
    - Timers: both the moonlight and divine SCB timers are shown as expected.
  - Drinking a moonlight potion after a divine SCB
    - Varbits: super attack/str/SCB varbits keep ticking down, the divine def/moonlight potion get set to a new value. The above described desync can of course happen when drinking multiple sips of the moonlight potion.
    - Stats: the def buff remains >super def until the moonlight potion times out, att/str get set to base level after the divine SCB times out (as expected).
    - Timers: divine SCB and moonlight timers are shown as expected (only tested after desync workaround).
- When leaving the dungeon
  - When under the effect of the moonlight potion, both the moonlight varbit and super def varbit get set to 0. All stats are removed.
  - While also under the effect of a divine SCB, the moonlight and super defense varbits get set to 0. The divine super att/str/SCB varbits keep ticking. All stats get reset to base level. If you drink a regular SCB, your attack and strength get divine boosted (remain at 118, boost disappears when divine SCB timer runs out). Your defense gets normal super defence boosted (not divine; normal drain occurs).
- When dying, all stats are removed and the SCB effect is gone.


All testing has been done on the beta worlds.
YvesW: I rebased my branch onto master before making any changes, then created a branch from this varlamore-buff-bar branch (before making any changes) and pushed it (https://github.com/YvesW/runelite/tree/varlamore-buff-bar-backup-14-july). This with the aim that you could more easily compare my changes compared to your last review.
I'm either doing something wrong, or https://github.com/YvesW/runelite/compare/varlamore-buff-bar-backup-14-july...YvesW:varlamore-buff-bar looks to be incorrect though. It looks like it's comparing with master instead of the branch I created.

Patch:
diff --git a/runelite-api/src/main/java/net/runelite/api/SpriteID.java b/runelite-api/src/main/java/net/runelite/api/SpriteID.java
index 3fb2f0eeb..1e1b4cfc4 100644
--- a/runelite-api/src/main/java/net/runelite/api/SpriteID.java
+++ b/runelite-api/src/main/java/net/runelite/api/SpriteID.java
@@ -1683,6 +1683,9 @@ public final class SpriteID
 	public static final int HEALTHBAR_PURPLE_BACK_140PX = 4725;
 	public static final int HEALTHBAR_PURPLE_FRONT_160PX = 4726;
 	public static final int HEALTHBAR_PURPLE_BACK_160PX = 4727;
+	/* Unmapped: 4728~4765 */
+	public static final int COLOSSEUM_DOOM = 4766;
+	/* Unmapped: 4767~4842 */
 	public static final int PRAYER_RP_ANCIENT_STRENGTH = 4843;
 	public static final int PRAYER_RP_ANCIENT_SIGHT = 4844;
 	public static final int PRAYER_RP_ANCIENT_WILL = 4845;
diff --git a/runelite-api/src/main/java/net/runelite/api/Varbits.java b/runelite-api/src/main/java/net/runelite/api/Varbits.java
index b72cc8b39..6ad752084 100644
--- a/runelite-api/src/main/java/net/runelite/api/Varbits.java
+++ b/runelite-api/src/main/java/net/runelite/api/Varbits.java
@@ -89,6 +89,15 @@ public final class Varbits
 	public static final int DIVINE_BASTION = 13664;
 	public static final int DIVINE_BATTLEMAGE = 13665;
 
+	/**
+	 * Moonlight potion timer.
+	 * When at least 70 herblore, the moonlight potion's defense effect will be removed when this timer runs out.
+	 * If the player drinks a dose of moonlight potion while already under its effects, desync between
+	 * Varbits.MOONLIGHT_POTION and Varbits.DIVINE_SUPER_DEFENCE can occur, with the latter being 1 tick greater.
+	 * In case of desync, the moonlight defence effect will be removed once Varbits.DIVINE_SUPER_DEFENCE becomes 0.
+	 */
+	public static final int MOONLIGHT_POTION = 10029;
+
 	/**
 	 * Ring of endurance effect timer, stamina duration extended from using the ring of endurance
 	 * Number of game ticks remaining on ring of endurance effect in intervals of 10; for a value X there are 10 * X game ticks remaining.
@@ -892,4 +901,19 @@ public final class Varbits
 
 	public static final int SPELLBOOK = 4070;
 	public static final int SPELLBOOK_SUBMENU = 9730;
+
+	/**
+	 * The amount of Curse of the Moons stacks received when fighting the Blue Moon or Eclipse Moon.
+	 * The varbit value remains 0 when fighting the Blood Moon.
+	 * When fighting the Blue Moon, the player's joints will lock up at 18 stacks, which causes their next attack to be
+	 * canceled and 18 stacks to be removed.
+	 * When fighting the Eclipse Moon, the stacks increase the chance of a player's attack glancing off the shield of
+	 * the Eclipse Moon. Glancing attacks reduce the player's max hit by two times the flat armour of the Eclipse Moon.
+	 */
+	public static final int CURSE_OF_THE_MOONS = 9853;
+
+	/**
+	 * The amount of Doom stacks received in the Fortis Colosseum.
+	 */
+	public static final int COLOSSEUM_DOOM = 9801;
 }
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timers/GameIndicator.java b/runelite-client/src/main/java/net/runelite/client/plugins/timers/GameIndicator.java
deleted file mode 100644
index 55151e340..000000000
--- a/runelite-client/src/main/java/net/runelite/client/plugins/timers/GameIndicator.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (c) 2018, Tyler <https://github.com/tylerthardy>
- * 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.timers;
-
-import java.awt.Color;
-import lombok.AccessLevel;
-import lombok.Getter;
-import net.runelite.api.SpriteID;
-
-@Getter(AccessLevel.PACKAGE)
-enum GameIndicator
-{
-	VENGEANCE_ACTIVE(SpriteID.SPELL_VENGEANCE_OTHER, GameTimerImageType.SPRITE, "Vengeance active");
-
-	private final String description;
-	private String text;
-	private Color textColor;
-	private final int imageId;
-	private final GameTimerImageType imageType;
-
-	GameIndicator(int imageId, GameTimerImageType idType, String description, String text, Color textColor)
-	{
-		this.imageId = imageId;
-		this.imageType = idType;
-		this.description = description;
-		this.text = text;
-		this.textColor = textColor;
-	}
-
-	GameIndicator(int imageId, GameTimerImageType idType, String description)
-	{
-		this(imageId, idType, description, "", null);
-	}
-}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timers/IndicatorIndicator.java b/runelite-client/src/main/java/net/runelite/client/plugins/timersandbuffs/BuffCounter.java
similarity index 66%
rename from runelite-client/src/main/java/net/runelite/client/plugins/timers/IndicatorIndicator.java
rename to runelite-client/src/main/java/net/runelite/client/plugins/timersandbuffs/BuffCounter.java
index 03990ce5e..9204f4d0a 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/timers/IndicatorIndicator.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/timersandbuffs/BuffCounter.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2018, Tyler <https://github.com/tylerthardy>
+ * Copyright (c) 2024, YvesW <https://github.com/YvesW>
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -22,35 +22,38 @@
  * (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.timers;
+package net.runelite.client.plugins.timersandbuffs;
 
 import java.awt.Color;
+import lombok.AccessLevel;
 import lombok.Getter;
-import net.runelite.client.plugins.Plugin;
-import net.runelite.client.ui.overlay.infobox.InfoBox;
-import net.runelite.client.ui.overlay.infobox.InfoBoxPriority;
+import net.runelite.client.ui.overlay.infobox.Counter;
 
-public class IndicatorIndicator extends InfoBox
+@Getter(AccessLevel.PACKAGE)
+class BuffCounter extends Counter
 {
-	@Getter
-	private final GameIndicator indicator;
+	private final TimersAndBuffsPlugin plugin;
+	private final GameCounter gameCounter;
 
-	IndicatorIndicator(GameIndicator indicator, Plugin plugin)
+	BuffCounter(
+		TimersAndBuffsPlugin plugin,
+		GameCounter gameCounter,
+		int count)
 	{
-		super(null, plugin);
-		this.indicator = indicator;
-		setPriority(InfoBoxPriority.MED);
+		super(null, plugin, count);
+		this.plugin = plugin;
+		this.gameCounter = gameCounter;
 	}
 
 	@Override
 	public String getText()
 	{
-		return indicator.getText();
+		return gameCounter.isShouldDisplayCount() ? Integer.toString(getCount()) : "";
 	}
 
 	@Override
 	public Color getTextColor()
 	{
-		return indicator.getTextColor();
+		return gameCounter.getColorBoundaryType().shouldRecolor(getCount(), gameCounter.getBoundary()) ? gameCounter.getColor() : Color.WHITE;
 	}
-}
+}
\ No newline at end of file
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timers/ElapsedTimer.java b/runelite-client/src/main/java/net/runelite/client/plugins/timersandbuffs/ElapsedTimer.java
similarity index 94%
rename from runelite-client/src/main/java/net/runelite/client/plugins/timers/ElapsedTimer.java
rename to runelite-client/src/main/java/net/runelite/client/plugins/timersandbuffs/ElapsedTimer.java
index b9571ae11..0482e9d61 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/timers/ElapsedTimer.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/timersandbuffs/ElapsedTimer.java
@@ -22,7 +22,7 @@
  * (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.timers;
+package net.runelite.client.plugins.timersandbuffs;
 
 import lombok.Getter;
 import net.runelite.client.ui.overlay.infobox.InfoBox;
@@ -39,7 +39,7 @@ class ElapsedTimer extends InfoBox
 	private final Instant lastTime;
 
 	// Creates a timer that counts up if lastTime is null, or a paused timer if lastTime is defined
-	ElapsedTimer(BufferedImage image, TimersPlugin plugin, Instant startTime, Instant lastTime)
+	ElapsedTimer(BufferedImage image, TimersAndBuffsPlugin plugin, Instant startTime, Instant lastTime)
 	{
 		super(image, plugin);
 		this.startTime = startTime;
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timersandbuffs/GameCounter.java b/runelite-client/src/main/java/net/runelite/client/plugins/timersandbuffs/GameCounter.java
new file mode 100644
index 000000000..f49302607
--- /dev/null
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/timersandbuffs/GameCounter.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2024, YvesW <https://github.com/YvesW>
+ * 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.timersandbuffs;
+
+import java.awt.Color;
+import java.util.function.BiPredicate;
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import net.runelite.api.ItemID;
+import net.runelite.api.SpriteID;
+
+@Getter(AccessLevel.PACKAGE)
+@AllArgsConstructor
+enum GameCounter
+{
+	COLOSSEUM_DOOM(SpriteID.COLOSSEUM_DOOM, GameTimerImageType.SPRITE, "Doom"),
+	CURSE_OF_THE_MOONS_BLUE(ItemID.BLUE_MOON_HELM, GameTimerImageType.ITEM, "Curse of the Moons (Blue Moon)", ColorBoundaryType.GREATER_THAN_EQUAL_TO, 18, Color.RED),
+	CURSE_OF_THE_MOONS_ECLIPSE(ItemID.ECLIPSE_MOON_HELM, GameTimerImageType.ITEM, "Curse of the Moons (Eclipse Moon)"),
+	VENGEANCE_ACTIVE(SpriteID.SPELL_VENGEANCE_OTHER, GameTimerImageType.SPRITE, "Vengeance active", false),
+	;
+
+	private final int imageId;
+	private final GameTimerImageType imageType;
+	private final String description;
+	private final ColorBoundaryType colorBoundaryType;
+	private final int boundary;
+	private final Color color;
+	private final boolean shouldDisplayCount;
+
+	GameCounter(int imageId, GameTimerImageType idType, String description, ColorBoundaryType colorBoundaryType, int boundary, Color color)
+	{
+		this(imageId, idType, description, colorBoundaryType, boundary, color, true);
+	}
+
+	GameCounter(int imageId, GameTimerImageType idType, String description, boolean shouldDisplayCount)
+	{
+		this(imageId, idType, description, ColorBoundaryType.NO_BOUNDARY, 0, Color.WHITE, shouldDisplayCount);
+	}
+
+	GameCounter(int imageId, GameTimerImageType idType, String description)
+	{
+		this(imageId, idType, description, ColorBoundaryType.NO_BOUNDARY, 0, Color.WHITE, true);
+	}
+
+	@AllArgsConstructor
+	enum ColorBoundaryType
+	{
+		GREATER_THAN_EQUAL_TO((count, boundary) -> count >= boundary),
+		LESS_THAN_EQUAL_TO((count, boundary) -> count <= boundary),
+		NO_BOUNDARY((count, boundary) -> false);
+
+		final BiPredicate<Integer, Integer> shouldRecolorPredicate;
+
+		boolean shouldRecolor(int count, int boundary)
+		{
+			return shouldRecolorPredicate.test(count, boundary);
+		}
+	}
+}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timers/GameTimer.java b/runelite-client/src/main/java/net/runelite/client/plugins/timersandbuffs/GameTimer.java
similarity index 98%
rename from runelite-client/src/main/java/net/runelite/client/plugins/timers/GameTimer.java
rename to runelite-client/src/main/java/net/runelite/client/plugins/timersandbuffs/GameTimer.java
index 02e9c5a47..377e02ec8 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/timers/GameTimer.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/timersandbuffs/GameTimer.java
@@ -24,7 +24,7 @@
  * (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.timers;
+package net.runelite.client.plugins.timersandbuffs;
 
 import java.time.Duration;
 import java.time.temporal.ChronoUnit;
@@ -74,6 +74,7 @@ enum GameTimer
 	DIVINE_MAGIC(ItemID.DIVINE_MAGIC_POTION4, GameTimerImageType.ITEM, "Divine Magic", false),
 	DIVINE_BASTION(ItemID.DIVINE_BASTION_POTION4, GameTimerImageType.ITEM, "Divine Bastion", false),
 	DIVINE_BATTLEMAGE(ItemID.DIVINE_BATTLEMAGE_POTION4, GameTimerImageType.ITEM, "Divine Battlemage", false),
+	MOONLIGHT_POTION(ItemID.MOONLIGHT_POTION4, GameTimerImageType.ITEM, "Moonlight potion", false),
 	ANTIPOISON(ItemID.ANTIPOISON4, GameTimerImageType.ITEM, "Antipoison", false),
 	ANTIVENOM(ItemID.ANTIVENOM4, GameTimerImageType.ITEM, "Anti-venom", false),
 	TELEBLOCK(SpriteID.SPELL_TELE_BLOCK, GameTimerImageType.SPRITE, "Teleblock", false),
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timers/GameTimerImageType.java b/runelite-client/src/main/java/net/runelite/client/plugins/timersandbuffs/GameTimerImageType.java
similarity index 96%
rename from runelite-client/src/main/java/net/runelite/client/plugins/timers/GameTimerImageType.java
rename to runelite-client/src/main/java/net/runelite/client/plugins/timersandbuffs/GameTimerImageType.java
index 1d51d05e3..9c5c3766c 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/timers/GameTimerImageType.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/timersandbuffs/GameTimerImageType.java
@@ -22,7 +22,7 @@
  * (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.timers;
+package net.runelite.client.plugins.timersandbuffs;
 
 enum GameTimerImageType
 {
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timers/TimerTimer.java b/runelite-client/src/main/java/net/runelite/client/plugins/timersandbuffs/TimerTimer.java
similarity index 97%
rename from runelite-client/src/main/java/net/runelite/client/plugins/timers/TimerTimer.java
rename to runelite-client/src/main/java/net/runelite/client/plugins/timersandbuffs/TimerTimer.java
index 8dfb1ce36..bc41a6d0d 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/timers/TimerTimer.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/timersandbuffs/TimerTimer.java
@@ -22,7 +22,7 @@
  * (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.timers;
+package net.runelite.client.plugins.timersandbuffs;
 
 import java.time.Duration;
 import java.time.temporal.ChronoUnit;
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timers/TimersConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/timersandbuffs/TimersAndBuffsConfig.java
similarity index 72%
rename from runelite-client/src/main/java/net/runelite/client/plugins/timers/TimersConfig.java
rename to runelite-client/src/main/java/net/runelite/client/plugins/timersandbuffs/TimersAndBuffsConfig.java
index daf383090..2502e5891 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/timers/TimersConfig.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/timersandbuffs/TimersAndBuffsConfig.java
@@ -22,22 +22,52 @@
  * (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.timers;
+package net.runelite.client.plugins.timersandbuffs;
 
 import net.runelite.client.config.Config;
 import net.runelite.client.config.ConfigGroup;
 import net.runelite.client.config.ConfigItem;
+import net.runelite.client.config.ConfigSection;
 import java.time.Instant;
 
-@ConfigGroup(TimersConfig.GROUP)
-public interface TimersConfig extends Config
+@ConfigGroup(TimersAndBuffsConfig.GROUP)
+public interface TimersAndBuffsConfig extends Config
 {
 	String GROUP = "timers";
 
+	@ConfigSection(
+		name = "Bosses",
+		description = "Timers and buffs related to bosses",
+		position = 0
+	)
+	String bossesSection = "bossesSection";
+
+	@ConfigSection(
+		name = "Potions & Consumables",
+		description = "Timers and buffs related to potions/consumables/boosts",
+		position = 1
+	)
+	String consumablesSection = "consumablesSection";
+
+	@ConfigSection(
+		name = "Spells",
+		description = "Timers and buffs related to spells you cast",
+		position = 2
+	)
+	String spellsSection = "spellsSection";
+
+	@ConfigSection(
+		name = "Miscellaneous",
+		description = "Timers and buffs related to miscellaneous items or activities",
+		position = 3
+	)
+	String miscellaneousSection = "miscellaneousSection";
+
 	@ConfigItem(
 		keyName = "showHomeMinigameTeleports",
 		name = "Teleport cooldown timers",
-		description = "Configures whether timers for home and minigame teleport cooldowns are displayed"
+		description = "Configures whether timers for home and minigame teleport cooldowns are displayed",
+		section = miscellaneousSection
 	)
 	default boolean showHomeMinigameTeleports()
 	{
@@ -47,7 +77,8 @@ public interface TimersConfig extends Config
 	@ConfigItem(
 		keyName = "showAntipoison",
 		name = "Antipoison/Venom timers",
-		description = "Configures whether timers for poison and venom protection are displayed"
+		description = "Configures whether timers for poison and venom protection are displayed",
+		section = consumablesSection
 	)
 	default boolean showAntiPoison()
 	{
@@ -57,7 +88,8 @@ public interface TimersConfig extends Config
 	@ConfigItem(
 		keyName = "showAntiFire",
 		name = "Antifire timer",
-		description = "Configures whether antifire timer is displayed"
+		description = "Configures whether antifire timer is displayed",
+		section = consumablesSection
 	)
 	default boolean showAntiFire()
 	{
@@ -67,7 +99,8 @@ public interface TimersConfig extends Config
 	@ConfigItem(
 		keyName = "showStamina",
 		name = "Stamina timer",
-		description = "Configures whether stamina timer is displayed"
+		description = "Configures whether stamina timer is displayed",
+		section = consumablesSection
 	)
 	default boolean showStamina()
 	{
@@ -77,7 +110,8 @@ public interface TimersConfig extends Config
 	@ConfigItem(
 		keyName = "showOverload",
 		name = "Overload timer",
-		description = "Configures whether overload timer is displayed"
+		description = "Configures whether overload timer is displayed",
+		section = consumablesSection
 	)
 	default boolean showOverload()
 	{
@@ -87,7 +121,8 @@ public interface TimersConfig extends Config
 	@ConfigItem(
 		keyName = "showLiquidAdrenaline",
 		name = "Liquid adrenaline timer",
-		description = "Configures whether liquid adrenaline timer is displayed"
+		description = "Configures whether liquid adrenaline timer is displayed",
+		section = consumablesSection
 	)
 	default boolean showLiquidAdrenaline()
 	{
@@ -97,7 +132,8 @@ public interface TimersConfig extends Config
 	@ConfigItem(
 		keyName = "showMenaphiteRemedy",
 		name = "Menaphite remedy timer",
-		description = "Configures whether Menaphite remedy timer is displayed"
+		description = "Configures whether Menaphite remedy timer is displayed",
+		section = consumablesSection
 	)
 	default boolean showMenaphiteRemedy()
 	{
@@ -107,7 +143,8 @@ public interface TimersConfig extends Config
 	@ConfigItem(
 		keyName = "showSilkDressing",
 		name = "Silk dressing timer",
-		description = "Configures whether silk dressing timer is displayed"
+		description = "Configures whether silk dressing timer is displayed",
+		section = consumablesSection
 	)
 	default boolean showSilkDressing()
 	{
@@ -117,7 +154,8 @@ public interface TimersConfig extends Config
 	@ConfigItem(
 		keyName = "showBlessedCrystalScarab",
 		name = "Blessed crystal scarab timer",
-		description = "Configures whether blessed crystal scarab timer is displayed"
+		description = "Configures whether blessed crystal scarab timer is displayed",
+		section = consumablesSection
 	)
 	default boolean showBlessedCrystalScarab()
 	{
@@ -127,7 +165,8 @@ public interface TimersConfig extends Config
 	@ConfigItem(
 		keyName = "showPrayerEnhance",
 		name = "Prayer enhance timer",
-		description = "Configures whether prayer enhance timer is displayed"
+		description = "Configures whether prayer enhance timer is displayed",
+		section = consumablesSection
 	)
 	default boolean showPrayerEnhance()
 	{
@@ -137,7 +176,8 @@ public interface TimersConfig extends Config
 	@ConfigItem(
 		keyName = "showDivine",
 		name = "Divine potion timer",
-		description = "Configures whether divine potion timer is displayed"
+		description = "Configures whether divine potion timer is displayed",
+		section = consumablesSection
 	)
 	default boolean showDivine()
 	{
@@ -147,7 +187,8 @@ public interface TimersConfig extends Config
 	@ConfigItem(
 		keyName = "showCannon",
 		name = "Cannon timer",
-		description = "Configures whether cannon timer is displayed"
+		description = "Configures whether cannon timer is displayed",
+		section = miscellaneousSection
 	)
 	default boolean showCannon()
 	{
@@ -157,7 +198,8 @@ public interface TimersConfig extends Config
 	@ConfigItem(
 		keyName = "showMagicImbue",
 		name = "Magic imbue timer",
-		description = "Configures whether magic imbue timer is displayed"
+		description = "Configures whether magic imbue timer is displayed",
+		section = spellsSection
 	)
 	default boolean showMagicImbue()
 	{
@@ -167,7 +209,8 @@ public interface TimersConfig extends Config
 	@ConfigItem(
 		keyName = "showCharge",
 		name = "Charge timer",
-		description = "Configures whether to show a timer for the Charge spell"
+		description = "Configures whether to show a timer for the Charge spell",
+		section = spellsSection
 	)
 	default boolean showCharge()
 	{
@@ -177,7 +220,8 @@ public interface TimersConfig extends Config
 	@ConfigItem(
 		keyName = "showImbuedHeart",
 		name = "Imbued heart timer",
-		description = "Configures whether imbued heart timer is displayed"
+		description = "Configures whether imbued heart timer is displayed",
+		section = consumablesSection
 	)
 	default boolean showImbuedHeart()
 	{
@@ -187,7 +231,8 @@ public interface TimersConfig extends Config
 	@ConfigItem(
 		keyName = "showVengeance",
 		name = "Vengeance timer",
-		description = "Configures whether vengeance and vengeance other timer is displayed"
+		description = "Configures whether vengeance and vengeance other timer is displayed",
+		section = spellsSection
 	)
 	default boolean showVengeance()
 	{
@@ -197,7 +242,8 @@ public interface TimersConfig extends Config
 	@ConfigItem(
 		keyName = "showVengeanceActive",
 		name = "Vengeance active",
-		description = "Configures whether an indicator for vengeance being active is displayed"
+		description = "Configures whether an indicator for vengeance being active is displayed",
+		section = spellsSection
 	)
 	default boolean showVengeanceActive()
 	{
@@ -207,7 +253,8 @@ public interface TimersConfig extends Config
 	@ConfigItem(
 		keyName = "showHealGroup",
 		name = "Heal Group timer",
-		description = "Configures whether heal group timer is displayed"
+		description = "Configures whether heal group timer is displayed",
+		section = spellsSection
 	)
 	default boolean showHealGroup()
 	{
@@ -217,7 +264,8 @@ public interface TimersConfig extends Config
 	@ConfigItem(
 		keyName = "showTeleblock",
 		name = "Teleblock timer",
-		description = "Configures whether teleblock timer is displayed"
+		description = "Configures whether teleblock timer is displayed",
+		section = miscellaneousSection
 	)
 	default boolean showTeleblock()
 	{
@@ -227,7 +275,8 @@ public interface TimersConfig extends Config
 	@ConfigItem(
 		keyName = "showFreezes",
 		name = "Freeze timer",
-		description = "Configures whether freeze timer is displayed"
+		description = "Configures whether freeze timer is displayed",
+		section = miscellaneousSection
 	)
 	default boolean showFreezes()
 	{
@@ -237,7 +286,8 @@ public interface TimersConfig extends Config
 	@ConfigItem(
 		keyName = "showGodWarsAltar",
 		name = "God wars altar timer",
-		description = "Configures whether god wars altar timer is displayed"
+		description = "Configures whether god wars altar timer is displayed",
+		section = bossesSection
 	)
 	default boolean showGodWarsAltar()
 	{
@@ -247,7 +297,8 @@ public interface TimersConfig extends Config
 	@ConfigItem(
 		keyName = "showTzhaarTimers",
 		name = "Fight Caves and Inferno timers",
-		description = "Display elapsed time in the Fight Caves and Inferno"
+		description = "Display elapsed time in the Fight Caves and Inferno",
+		section = bossesSection
 	)
 	default boolean showTzhaarTimers()
 	{
@@ -287,7 +338,8 @@ public interface TimersConfig extends Config
 	@ConfigItem(
 		keyName = "showStaffOfTheDead",
 		name = "Staff of the Dead timer",
-		description = "Configures whether staff of the dead timer is displayed"
+		description = "Configures whether staff of the dead timer is displayed",
+		section = miscellaneousSection
 	)
 	default boolean showStaffOfTheDead()
 	{
@@ -297,7 +349,8 @@ public interface TimersConfig extends Config
 	@ConfigItem(
 		keyName = "showAbyssalSireStun",
 		name = "Abyssal Sire stun timer",
-		description = "Configures whether Abyssal Sire stun timer is displayed"
+		description = "Configures whether Abyssal Sire stun timer is displayed",
+		section = bossesSection
 	)
 	default boolean showAbyssalSireStun()
 	{
@@ -307,7 +360,8 @@ public interface TimersConfig extends Config
 	@ConfigItem(
 		keyName = "showDfsSpecial",
 		name = "Dragonfire Shield special timer",
-		description = "Configures whether the special attack cooldown timer for the Dragonfire Shield is displayed"
+		description = "Configures whether the special attack cooldown timer for the Dragonfire Shield is displayed",
+		section = miscellaneousSection
 	)
 	default boolean showDFSSpecial()
 	{
@@ -317,7 +371,8 @@ public interface TimersConfig extends Config
 	@ConfigItem(
 		keyName = "showArceuus",
 		name = "Arceuus spells duration",
-		description = "Whether to show Arceuus spellbook spell timers"
+		description = "Whether to show Arceuus spellbook spell timers",
+		section = spellsSection
 	)
 	default boolean showArceuus()
 	{
@@ -327,7 +382,8 @@ public interface TimersConfig extends Config
 	@ConfigItem(
 		keyName = "showArceuusCooldown",
 		name = "Arceuus spells cooldown",
-		description = "Whether to show cooldown timers for Arceuus spellbook spells"
+		description = "Whether to show cooldown timers for Arceuus spellbook spells",
+		section = spellsSection
 	)
 	default boolean showArceuusCooldown()
 	{
@@ -337,7 +393,8 @@ public interface TimersConfig extends Config
 	@ConfigItem(
 		keyName = "showPickpocketStun",
 		name = "Pickpocket stun timer",
-		description = "Configures whether pickpocket stun timer is displayed"
+		description = "Configures whether pickpocket stun timer is displayed",
+		section = miscellaneousSection
 	)
 	default boolean showPickpocketStun()
 	{
@@ -347,7 +404,8 @@ public interface TimersConfig extends Config
 	@ConfigItem(
 		keyName = "showFarmersAffinity",
 		name = "Farmer's Affinity",
-		description = "Configures whether Farmer's Affinity (Puro-Puro) timer is displayed"
+		description = "Configures whether Farmer's Affinity (Puro-Puro) timer is displayed",
+		section = miscellaneousSection
 	)
 	default boolean showFarmersAffinity()
 	{
@@ -357,10 +415,44 @@ public interface TimersConfig extends Config
 	@ConfigItem(
 		keyName = "showSpellbookSwap",
 		name = "Spellbook Swap timer",
-		description = "Configures whether Spellbook Swap timer is displayed"
+		description = "Configures whether Spellbook Swap timer is displayed",
+		section = spellsSection
 	)
 	default boolean showSpellbookSwap()
 	{
 		return true;
 	}
+
+	@ConfigItem(
+		keyName = "showCurseOfTheMoons",
+		name = "Curse of the Moons buff",
+		description = "Configures whether Curse of the Moons buff is displayed",
+		section = bossesSection
+	)
+	default boolean showCurseOfTheMoons()
+	{
+		return true;
+	}
+
+	@ConfigItem(
+		keyName = "showColosseumDoom",
+		name = "Colosseum Doom buff",
+		description = "Configures whether Fortis Colosseum Doom buff is displayed",
+		section = bossesSection
+	)
+	default boolean showColosseumDoom()
+	{
+		return true;
+	}
+
+	@ConfigItem(
+		keyName = "showMoonlightPotion",
+		name = "Moonlight potion timer",
+		description = "Configures whether Moonlight potion timer is displayed",
+		section = consumablesSection
+	)
+	default boolean showMoonlightPotion()
+	{
+		return true;
+	}
 }
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timers/TimersPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/timersandbuffs/TimersAndBuffsPlugin.java
similarity index 89%
rename from runelite-client/src/main/java/net/runelite/client/plugins/timers/TimersPlugin.java
rename to runelite-client/src/main/java/net/runelite/client/plugins/timersandbuffs/TimersAndBuffsPlugin.java
index 35d6b3fe8..4ce09737d 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/timers/TimersPlugin.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/timersandbuffs/TimersAndBuffsPlugin.java
@@ -24,7 +24,7 @@
  * (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.timers;
+package net.runelite.client.plugins.timersandbuffs;
 
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.ImmutableSet;
@@ -76,19 +76,20 @@ import net.runelite.client.game.ItemVariationMapping;
 import net.runelite.client.game.SpriteManager;
 import net.runelite.client.plugins.Plugin;
 import net.runelite.client.plugins.PluginDescriptor;
-import static net.runelite.client.plugins.timers.GameIndicator.VENGEANCE_ACTIVE;
-import static net.runelite.client.plugins.timers.GameTimer.*;
+import static net.runelite.client.plugins.timersandbuffs.GameCounter.*;
+import static net.runelite.client.plugins.timersandbuffs.GameTimer.*;
 import net.runelite.client.ui.overlay.infobox.InfoBoxManager;
 import net.runelite.client.util.RSTimeUnit;
 import org.apache.commons.lang3.ArrayUtils;
 
 @PluginDescriptor(
-	name = "Timers",
-	description = "Show various timers in an infobox",
-	tags = {"combat", "items", "magic", "potions", "prayer", "overlay", "abyssal", "sire", "inferno", "fight", "caves", "cape", "timer", "tzhaar", "thieving", "pickpocket", "hunter", "impling", "puro"}
+	name = "Timers & Buffs",
+	configName = "TimersPlugin",
+	description = "Show various timers and buffs in an infobox",
+	tags = {"combat", "items", "magic", "potions", "prayer", "overlay", "abyssal", "sire", "inferno", "fight", "caves", "cape", "timer", "tzhaar", "thieving", "pickpocket", "hunter", "impling", "puro", "buff"}
 )
 @Slf4j
-public class TimersPlugin extends Plugin
+public class TimersAndBuffsPlugin extends Plugin
 {
 	private static final String ABYSSAL_SIRE_STUN_MESSAGE = "The Sire has been disorientated temporarily.";
 	private static final String CANNON_BASE_MESSAGE = "You place the cannon base on the ground.";
@@ -144,6 +145,9 @@ public class TimersPlugin extends Plugin
 	private WorldPoint lastPoint;
 	private ElapsedTimer tzhaarTimer;
 
+	private final Map<GameCounter, BuffCounter> varCounters = new EnumMap<>(GameCounter.class);
+	private static final int ECLIPSE_MOON_REGION_ID = 6038;
+
 	@Inject
 	private ItemManager itemManager;
 
@@ -154,15 +158,15 @@ public class TimersPlugin extends Plugin
 	private Client client;
 
 	@Inject
-	private TimersConfig config;
+	private TimersAndBuffsConfig config;
 
 	@Inject
 	private InfoBoxManager infoBoxManager;
 
 	@Provides
-	TimersConfig getConfig(ConfigManager configManager)
+	TimersAndBuffsConfig getConfig(ConfigManager configManager)
 	{
-		return configManager.getConfig(TimersConfig.class);
+		return configManager.getConfig(TimersAndBuffsConfig.class);
 	}
 
 	@Override
@@ -186,6 +190,8 @@ public class TimersPlugin extends Plugin
 		nextSuperAntifireTick = 0;
 		removeTzhaarTimer();
 		varTimers.clear();
+		infoBoxManager.removeIf(buffCounter -> buffCounter instanceof BuffCounter);
+		varCounters.clear();
 	}
 
 	@Subscribe
@@ -295,14 +301,7 @@ public class TimersPlugin extends Plugin
 
 		if (event.getVarbitId() == Varbits.VENGEANCE_ACTIVE && config.showVengeanceActive())
 		{
-			if (event.getValue() == 1)
-			{
-				createGameIndicator(VENGEANCE_ACTIVE);
-			}
-			else
-			{
-				removeGameIndicator(VENGEANCE_ACTIVE);
-			}
+			updateVarCounter(VENGEANCE_ACTIVE, event.getValue());
 		}
 
 		if (event.getVarbitId() == Varbits.DEATH_CHARGE && config.showArceuus())
@@ -477,11 +476,20 @@ public class TimersPlugin extends Plugin
 		{
 			if (client.getVarbitValue(Varbits.DIVINE_SUPER_COMBAT) > event.getValue()
 				|| client.getVarbitValue(Varbits.DIVINE_BASTION) > event.getValue()
-				|| client.getVarbitValue(Varbits.DIVINE_BATTLEMAGE) > event.getValue())
+				|| client.getVarbitValue(Varbits.DIVINE_BATTLEMAGE) > event.getValue()
+				// When drinking a dose of moonlight potion while already under its effects, desync between
+				// Varbits.MOONLIGHT_POTION and Varbits.DIVINE_SUPER_DEFENCE can occur, with the latter being 1 tick
+				// greater
+				|| client.getVarbitValue(Varbits.MOONLIGHT_POTION) >= event.getValue())
 			{
 				return;
 			}
 
+			if (client.getVarbitValue(Varbits.MOONLIGHT_POTION) < event.getValue())
+			{
+				removeVarTimer(MOONLIGHT_POTION);
+			}
+
 			updateVarTimer(DIVINE_SUPER_DEFENCE, event.getValue(), IntUnaryOperator.identity());
 		}
 
@@ -575,12 +583,43 @@ public class TimersPlugin extends Plugin
 		{
 			updateVarTimer(GOD_WARS_ALTAR, event.getValue(), i -> i * 100);
 		}
+
+		if (event.getVarbitId() == Varbits.CURSE_OF_THE_MOONS && config.showCurseOfTheMoons())
+		{
+			final int regionID = WorldPoint.fromLocal(client, client.getLocalPlayer().getLocalLocation()).getRegionID();
+			if (regionID == ECLIPSE_MOON_REGION_ID)
+			{
+				updateVarCounter(CURSE_OF_THE_MOONS_ECLIPSE, event.getValue());
+			}
+			else
+			{
+				updateVarCounter(CURSE_OF_THE_MOONS_BLUE, event.getValue());
+			}
+		}
+
+		if (event.getVarbitId() == Varbits.COLOSSEUM_DOOM && config.showColosseumDoom())
+		{
+			updateVarCounter(COLOSSEUM_DOOM, event.getValue());
+		}
+
+		if (event.getVarbitId() == Varbits.MOONLIGHT_POTION && config.showMoonlightPotion())
+		{
+			int moonlightValue = event.getValue();
+			// Increase the timer by 1 tick in case of desync due to drinking a dose of moonlight potion while already
+			// under its effects. Otherwise, the timer would be 1 tick shorter than it is meant to be.
+			if (client.getVarbitValue(Varbits.DIVINE_SUPER_DEFENCE) == moonlightValue + 1)
+			{
+				moonlightValue++;
+			}
+
+			updateVarTimer(MOONLIGHT_POTION, moonlightValue, IntUnaryOperator.identity());
+		}
 	}
 
 	@Subscribe
 	public void onConfigChanged(ConfigChanged event)
 	{
-		if (!event.getGroup().equals(TimersConfig.GROUP))
+		if (!event.getGroup().equals(TimersAndBuffsConfig.GROUP))
 		{
 			return;
 		}
@@ -674,7 +713,7 @@ public class TimersPlugin extends Plugin
 
 		if (!config.showVengeanceActive())
 		{
-			removeGameIndicator(VENGEANCE_ACTIVE);
+			removeVarCounter(VENGEANCE_ACTIVE);
 		}
 
 		if (!config.showTeleblock())
@@ -771,6 +810,22 @@ public class TimersPlugin extends Plugin
 		{
 			removeGameTimer(SPELLBOOK_SWAP);
 		}
+
+		if (!config.showCurseOfTheMoons())
+		{
+			removeVarCounter(CURSE_OF_THE_MOONS_BLUE);
+			removeVarCounter(CURSE_OF_THE_MOONS_ECLIPSE);
+		}
+
+		if (!config.showColosseumDoom())
+		{
+			removeVarCounter(COLOSSEUM_DOOM);
+		}
+
+		if (!config.showMoonlightPotion())
+		{
+			removeVarTimer(MOONLIGHT_POTION);
+		}
 	}
 
 	@Subscribe
@@ -1059,6 +1114,8 @@ public class TimersPlugin extends Plugin
 					config.tzhaarStartTime(null);
 					config.tzhaarLastTime(null);
 				}
+				// Varbits.COLOSSEUM_DOOM is not set to 0 when teleporting out of the Colosseum.
+				removeVarCounter(COLOSSEUM_DOOM);
 				break;
 			case LOGIN_SCREEN:
 				// fall through
@@ -1076,7 +1133,6 @@ public class TimersPlugin extends Plugin
 		}
 	}
 
-
 	@Subscribe
 	public void onGraphicChanged(GraphicChanged event)
 	{
@@ -1211,31 +1267,6 @@ public class TimersPlugin extends Plugin
 		infoBoxManager.removeIf(t -> t instanceof TimerTimer && ((TimerTimer) t).getTimer() == timer);
 	}
 
-	private IndicatorIndicator createGameIndicator(GameIndicator gameIndicator)
-	{
-		removeGameIndicator(gameIndicator);
-
-		IndicatorIndicator indicator = new IndicatorIndicator(gameIndicator, this);
-		switch (gameIndicator.getImageType())
-		{
-			case SPRITE:
-				spriteManager.getSpriteAsync(gameIndicator.getImageId(), 0, indicator);
-				break;
-			case ITEM:
-				indicator.setImage(itemManager.getImage(gameIndicator.getImageId()));
-				break;
-		}
-		indicator.setTooltip(gameIndicator.getDescription());
-		infoBoxManager.addInfoBox(indicator);
-
-		return indicator;
-	}
-
-	private void removeGameIndicator(GameIndicator indicator)
-	{
-		infoBoxManager.removeIf(t -> t instanceof IndicatorIndicator && ((IndicatorIndicator) t).getIndicator() == indicator);
-	}
-
 	private void updateVarTimer(final GameTimer gameTimer, final int varValue, final IntUnaryOperator tickDuration)
 	{
 		updateVarTimer(gameTimer, varValue, i -> i == 0, tickDuration);
@@ -1270,4 +1301,53 @@ public class TimersPlugin extends Plugin
 		removeGameTimer(gameTimer);
 		varTimers.remove(gameTimer);
 	}
+
+	private void updateVarCounter(final GameCounter gameCounter, final int varValue)
+	{
+		BuffCounter buffCounter = varCounters.get(gameCounter);
+
+		if (varValue == 0)
+		{
+			removeVarCounter(gameCounter);
+		}
+		else if (buffCounter == null)
+		{
+			buffCounter = createBuffCounter(gameCounter, varValue);
+			varCounters.put(gameCounter, buffCounter);
+		}
+		else
+		{
+			buffCounter.setCount(varValue);
+		}
+	}
+
+	private BuffCounter createBuffCounter(GameCounter gameCounter, int count)
+	{
+		removeBuffCounter(gameCounter);
+
+		BuffCounter buffCounter = new BuffCounter(this, gameCounter, count);
+		switch (gameCounter.getImageType())
+		{
+			case SPRITE:
+				spriteManager.getSpriteAsync(gameCounter.getImageId(), 0, buffCounter);
+				break;
+			case ITEM:
+				buffCounter.setImage(itemManager.getImage(gameCounter.getImageId()));
+				break;
+		}
+		buffCounter.setTooltip(gameCounter.getDescription());
+		infoBoxManager.addInfoBox(buffCounter);
+		return buffCounter;
+	}
+
+	private void removeVarCounter(final GameCounter gameCounter)
+	{
+		removeBuffCounter(gameCounter);
+		varCounters.remove(gameCounter);
+	}
+
+	private void removeBuffCounter(GameCounter gameCounter)
+	{
+		infoBoxManager.removeIf(b -> b instanceof BuffCounter && ((BuffCounter) b).getGameCounter() == gameCounter);
+	}
 }


Test patch:
diff --git a/runelite-client/src/test/java/net/runelite/client/plugins/timers/ElapsedTimerTest.java b/runelite-client/src/test/java/net/runelite/client/plugins/timersandbuffs/ElapsedTimerTest.java
similarity index 98%
rename from runelite-client/src/test/java/net/runelite/client/plugins/timers/ElapsedTimerTest.java
rename to runelite-client/src/test/java/net/runelite/client/plugins/timersandbuffs/ElapsedTimerTest.java
index 2b3c1e461..8851811e3 100644
--- a/runelite-client/src/test/java/net/runelite/client/plugins/timers/ElapsedTimerTest.java
+++ b/runelite-client/src/test/java/net/runelite/client/plugins/timersandbuffs/ElapsedTimerTest.java
@@ -22,7 +22,7 @@
  * (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.timers;
+package net.runelite.client.plugins.timersandbuffs;
 
 import java.time.Instant;
 import static org.junit.Assert.assertEquals;
diff --git a/runelite-client/src/test/java/net/runelite/client/plugins/timers/TimersPluginTest.java b/runelite-client/src/test/java/net/runelite/client/plugins/timersandbuffs/TimersAndBuffsPluginTest.java
similarity index 79%
rename from runelite-client/src/test/java/net/runelite/client/plugins/timers/TimersPluginTest.java
rename to runelite-client/src/test/java/net/runelite/client/plugins/timersandbuffs/TimersAndBuffsPluginTest.java
index 5499078c2..0b336adff 100644
--- a/runelite-client/src/test/java/net/runelite/client/plugins/timers/TimersPluginTest.java
+++ b/runelite-client/src/test/java/net/runelite/client/plugins/timersandbuffs/TimersAndBuffsPluginTest.java
@@ -22,7 +22,7 @@
  * (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.timers;
+package net.runelite.client.plugins.timersandbuffs;
 
 import com.google.inject.Guice;
 import com.google.inject.Inject;
@@ -66,14 +66,14 @@ import org.mockito.junit.MockitoJUnitRunner;
 import org.mockito.stubbing.Answer;
 
 @RunWith(MockitoJUnitRunner.class)
-public class TimersPluginTest
+public class TimersAndBuffsPluginTest
 {
 	@Inject
-	private TimersPlugin timersPlugin;
+	private TimersAndBuffsPlugin timersAndBuffsPlugin;
 
 	@Mock
 	@Bind
-	private TimersConfig timersConfig;
+	private TimersAndBuffsConfig timersAndBuffsConfig;
 
 	@Mock
 	@Bind
@@ -100,27 +100,27 @@ public class TimersPluginTest
 	@Test
 	public void removeTimersOnConfigChanged()
 	{
-		timersPlugin = spy(timersPlugin);
+		timersAndBuffsPlugin = spy(timersAndBuffsPlugin);
 		final ConfigChanged configChanged = new ConfigChanged();
-		configChanged.setGroup(TimersConfig.GROUP);
+		configChanged.setGroup(TimersAndBuffsConfig.GROUP);
 
-		timersPlugin.onConfigChanged(configChanged);
+		timersAndBuffsPlugin.onConfigChanged(configChanged);
 
 		for (GameTimer timer : GameTimer.values())
 		{
-			verify(timersPlugin).removeGameTimer(timer);
+			verify(timersAndBuffsPlugin).removeGameTimer(timer);
 		}
 	}
 
 	@Test
 	public void testDivineRanging()
 	{
-		when(timersConfig.showDivine()).thenReturn(true);
+		when(timersAndBuffsConfig.showDivine()).thenReturn(true);
 
 		VarbitChanged varbitChanged = new VarbitChanged();
 		varbitChanged.setVarbitId(Varbits.DIVINE_RANGING);
 		varbitChanged.setValue(500);
-		timersPlugin.onVarbitChanged(varbitChanged);
+		timersAndBuffsPlugin.onVarbitChanged(varbitChanged);
 
 		ArgumentCaptor<InfoBox> captor = ArgumentCaptor.forClass(InfoBox.class);
 		verify(infoBoxManager).addInfoBox(captor.capture());
@@ -131,31 +131,31 @@ public class TimersPluginTest
 	@Test
 	public void testDivineBastion()
 	{
-		when(timersConfig.showDivine()).thenReturn(true);
+		when(timersAndBuffsConfig.showDivine()).thenReturn(true);
 
 		VarbitChanged rangingVarbitChanged = new VarbitChanged();
 		rangingVarbitChanged.setVarbitId(Varbits.DIVINE_RANGING);
 		rangingVarbitChanged.setValue(500);
-		timersPlugin.onVarbitChanged(rangingVarbitChanged);
+		timersAndBuffsPlugin.onVarbitChanged(rangingVarbitChanged);
 		when(client.getVarbitValue(Varbits.DIVINE_RANGING)).thenReturn(500);
 
 		VarbitChanged superDefenceVarbitChanged = new VarbitChanged();
 		superDefenceVarbitChanged.setVarbitId(Varbits.DIVINE_SUPER_DEFENCE);
 		superDefenceVarbitChanged.setValue(500);
-		timersPlugin.onVarbitChanged(superDefenceVarbitChanged);
+		timersAndBuffsPlugin.onVarbitChanged(superDefenceVarbitChanged);
 		when(client.getVarbitValue(Varbits.DIVINE_SUPER_DEFENCE)).thenReturn(500);
 
 		VarbitChanged bastionVarbitChanged = new VarbitChanged();
 		bastionVarbitChanged.setVarbitId(Varbits.DIVINE_BASTION);
 		bastionVarbitChanged.setValue(500);
-		timersPlugin.onVarbitChanged(bastionVarbitChanged);
+		timersAndBuffsPlugin.onVarbitChanged(bastionVarbitChanged);
 
 		ArgumentCaptor<InfoBox> captor = ArgumentCaptor.forClass(InfoBox.class);
 		verify(infoBoxManager, times(3)).addInfoBox(captor.capture());
 		List<InfoBox> infoBoxes = captor.getAllValues();
 
 		ArgumentCaptor<Predicate<InfoBox>> prcaptor = ArgumentCaptor.forClass(Predicate.class);
-		verify(infoBoxManager, times(5)).removeIf(prcaptor.capture());
+		verify(infoBoxManager, times(6)).removeIf(prcaptor.capture());
 		List<Predicate<InfoBox>> filters = prcaptor.getAllValues();
 
 		// test defence, ranging, and bastion infoboxes added
@@ -165,22 +165,24 @@ public class TimersPluginTest
 
 		// test ranging and defence infoboxes removed
 		assertTrue(filters.get(0).test(infoBoxes.get(0)));  // divine ranging infobox added
-		assertTrue(filters.get(1).test(infoBoxes.get(1)));  // divine super defence infobox added
-		assertTrue(filters.get(2).test(infoBoxes.get(0)));  // divine ranging infobox removed
-		assertTrue(filters.get(3).test(infoBoxes.get(1)));  // divine super defence infobox removed
-		assertTrue(filters.get(4).test(infoBoxes.get(2)));  // divine bastion infobox added
+		// filters.get(1) tries to remove a non-existent moonlight potion timer since Varbits.DIVINE_SUPER_DEFENCE
+		// was changed
+		assertTrue(filters.get(2).test(infoBoxes.get(1)));  // divine super defence infobox added
+		assertTrue(filters.get(3).test(infoBoxes.get(0)));  // divine ranging infobox removed
+		assertTrue(filters.get(4).test(infoBoxes.get(1)));  // divine super defence infobox removed
+		assertTrue(filters.get(5).test(infoBoxes.get(2)));  // divine bastion infobox added
 	}
 
 	@Test
 	public void testDivineRangingAfterBastion()
 	{
-		when(timersConfig.showDivine()).thenReturn(true);
+		when(timersAndBuffsConfig.showDivine()).thenReturn(true);
 		when(client.getVarbitValue(Varbits.DIVINE_BASTION)).thenReturn(400);
 
 		VarbitChanged varbitChanged = new VarbitChanged();
 		varbitChanged.setVarbitId(Varbits.DIVINE_RANGING);
 		varbitChanged.setValue(500);
-		timersPlugin.onVarbitChanged(varbitChanged);
+		timersAndBuffsPlugin.onVarbitChanged(varbitChanged);
 
 		ArgumentCaptor<InfoBox> captor = ArgumentCaptor.forClass(InfoBox.class);
 		verify(infoBoxManager).addInfoBox(captor.capture());
@@ -191,14 +193,14 @@ public class TimersPluginTest
 	@Test
 	public void testStamina()
 	{
-		when(timersConfig.showStamina()).thenReturn(true);
+		when(timersAndBuffsConfig.showStamina()).thenReturn(true);
 		when(client.getVarbitValue(Varbits.RUN_SLOWED_DEPLETION_ACTIVE)).thenReturn(1);
 		when(client.getVarbitValue(Varbits.STAMINA_EFFECT)).thenReturn(20);
 		when(client.getVarbitValue(Varbits.RING_OF_ENDURANCE_EFFECT)).thenReturn(0);
 
 		VarbitChanged varbitChanged = new VarbitChanged();
 		varbitChanged.setVarbitId(Varbits.RUN_SLOWED_DEPLETION_ACTIVE); // just has to be one of the vars
-		timersPlugin.onVarbitChanged(varbitChanged);
+		timersAndBuffsPlugin.onVarbitChanged(varbitChanged);
 
 		ArgumentCaptor<InfoBox> captor = ArgumentCaptor.forClass(InfoBox.class);
 		verify(infoBoxManager).addInfoBox(captor.capture());
@@ -210,9 +212,9 @@ public class TimersPluginTest
 	@Test
 	public void testSireStunTimer()
 	{
-		when(timersConfig.showAbyssalSireStun()).thenReturn(true);
+		when(timersAndBuffsConfig.showAbyssalSireStun()).thenReturn(true);
 		ChatMessage chatMessage = new ChatMessage(null, ChatMessageType.GAMEMESSAGE, "", "The Sire has been disorientated temporarily.", "", 0);
-		timersPlugin.onChatMessage(chatMessage);
+		timersAndBuffsPlugin.onChatMessage(chatMessage);
 
 		ArgumentCaptor<InfoBox> captor = ArgumentCaptor.forClass(InfoBox.class);
 		verify(infoBoxManager).addInfoBox(captor.capture());
@@ -224,14 +226,14 @@ public class TimersPluginTest
 	@Test
 	public void testEndurance()
 	{
-		when(timersConfig.showStamina()).thenReturn(true);
+		when(timersAndBuffsConfig.showStamina()).thenReturn(true);
 		when(client.getVarbitValue(Varbits.RUN_SLOWED_DEPLETION_ACTIVE)).thenReturn(1);
 		when(client.getVarbitValue(Varbits.STAMINA_EFFECT)).thenReturn(20);
 		when(client.getVarbitValue(Varbits.RING_OF_ENDURANCE_EFFECT)).thenReturn(20);
 
 		VarbitChanged varbitChanged = new VarbitChanged();
 		varbitChanged.setVarbitId(Varbits.RUN_SLOWED_DEPLETION_ACTIVE); // just has to be one of the vars
-		timersPlugin.onVarbitChanged(varbitChanged);
+		timersAndBuffsPlugin.onVarbitChanged(varbitChanged);
 
 		ArgumentCaptor<InfoBox> captor = ArgumentCaptor.forClass(InfoBox.class);
 		verify(infoBoxManager).addInfoBox(captor.capture());
@@ -241,7 +243,7 @@ public class TimersPluginTest
 
 		// unwield ring
 		when(client.getVarbitValue(Varbits.RING_OF_ENDURANCE_EFFECT)).thenReturn(0);
-		timersPlugin.onVarbitChanged(varbitChanged);
+		timersAndBuffsPlugin.onVarbitChanged(varbitChanged);
 		int mins = (int) infoBox.getDuration().toMinutes();
 		assertEquals(2, mins);
 	}
@@ -249,8 +251,8 @@ public class TimersPluginTest
 	@Test
 	public void testTzhaarTimer()
 	{
-		when(timersConfig.showTzhaarTimers()).thenReturn(true);
-		when(client.getMapRegions()).thenReturn(new int[]{TimersPlugin.FIGHT_CAVES_REGION_ID});
+		when(timersAndBuffsConfig.showTzhaarTimers()).thenReturn(true);
+		when(client.getMapRegions()).thenReturn(new int[]{TimersAndBuffsPlugin.FIGHT_CAVES_REGION_ID});
 
 		class InstantRef
 		{
@@ -258,33 +260,33 @@ public class TimersPluginTest
 		}
 
 		InstantRef startTime = new InstantRef();
-		when(timersConfig.tzhaarStartTime()).then(a -> startTime.i);
+		when(timersAndBuffsConfig.tzhaarStartTime()).then(a -> startTime.i);
 		doAnswer((Answer<Void>) invocationOnMock ->
 		{
 			Object argument = invocationOnMock.getArguments()[0];
 			startTime.i = (Instant) argument;
 			return null;
-		}).when(timersConfig).tzhaarStartTime(nullable(Instant.class));
+		}).when(timersAndBuffsConfig).tzhaarStartTime(nullable(Instant.class));
 
 		InstantRef lastTime = new InstantRef();
-		when(timersConfig.tzhaarLastTime()).then(a -> lastTime.i);
+		when(timersAndBuffsConfig.tzhaarLastTime()).then(a -> lastTime.i);
 		doAnswer((Answer<Void>) invocationOnMock ->
 		{
 			Object argument = invocationOnMock.getArguments()[0];
 			lastTime.i = (Instant) argument;
 			return null;
-		}).when(timersConfig).tzhaarLastTime(nullable(Instant.class));
+		}).when(timersAndBuffsConfig).tzhaarLastTime(nullable(Instant.class));
 
 		// test timer creation: verify the infobox was added and that it is an ElapsedTimer
 		ChatMessage chatMessage = new ChatMessage(null, ChatMessageType.GAMEMESSAGE, "", "<col=ef1020>Wave: 1</col>", "", 0);
-		timersPlugin.onChatMessage(chatMessage);
+		timersAndBuffsPlugin.onChatMessage(chatMessage);
 		ArgumentCaptor<InfoBox> captor = ArgumentCaptor.forClass(InfoBox.class);
 		verify(infoBoxManager, times(1)).addInfoBox(captor.capture());
 		assertTrue(captor.getValue() instanceof ElapsedTimer);
 
 		// test timer pause: verify the added ElapsedTimer has a non-null lastTime
 		chatMessage = new ChatMessage(null, ChatMessageType.GAMEMESSAGE, "", "<col=ef1020>The Inferno has been paused. You may now log out.", "", 0);
-		timersPlugin.onChatMessage(chatMessage);
+		timersAndBuffsPlugin.onChatMessage(chatMessage);
 		verify(infoBoxManager, times(1)).removeInfoBox(captor.capture());
 		verify(infoBoxManager, times(2)).addInfoBox(captor.capture());
 		assertTrue(captor.getValue() instanceof ElapsedTimer);
@@ -294,7 +296,7 @@ public class TimersPluginTest
 
 		// test timer unpause: verify the last time is null after being unpaused
 		chatMessage = new ChatMessage(null, ChatMessageType.GAMEMESSAGE, "", "<col=ef1020>Wave: 2</col>", "", 0);
-		timersPlugin.onChatMessage(chatMessage);
+		timersAndBuffsPlugin.onChatMessage(chatMessage);
 		verify(infoBoxManager, times(2)).removeInfoBox(captor.capture());
 		verify(infoBoxManager, times(3)).addInfoBox(captor.capture());
 		assertTrue(captor.getValue() instanceof ElapsedTimer);
@@ -306,8 +308,8 @@ public class TimersPluginTest
 		final GameStateChanged gameStateChanged = new GameStateChanged();
 		gameStateChanged.setGameState(GameState.LOADING);
 		when(client.getMapRegions()).thenReturn(new int[0]);
-		timersPlugin.onChatMessage(chatMessage);
-		timersPlugin.onGameStateChanged(gameStateChanged);
+		timersAndBuffsPlugin.onChatMessage(chatMessage);
+		timersAndBuffsPlugin.onGameStateChanged(gameStateChanged);
 		verify(infoBoxManager, times(3)).removeInfoBox(captor.capture());
 		verify(infoBoxManager, times(3)).addInfoBox(captor.capture());
 	}
@@ -315,8 +317,8 @@ public class TimersPluginTest
 	@Test
 	public void testInfernoTimerStartOffset()
 	{
-		when(timersConfig.showTzhaarTimers()).thenReturn(true);
-		when(client.getMapRegions()).thenReturn(new int[]{TimersPlugin.INFERNO_REGION_ID});
+		when(timersAndBuffsConfig.showTzhaarTimers()).thenReturn(true);
+		when(client.getMapRegions()).thenReturn(new int[]{TimersAndBuffsPlugin.INFERNO_REGION_ID});
 
 		class InstantRef
 		{
@@ -324,16 +326,16 @@ public class TimersPluginTest
 		}
 
 		InstantRef startTime = new InstantRef();
-		when(timersConfig.tzhaarStartTime()).then(a -> startTime.i);
+		when(timersAndBuffsConfig.tzhaarStartTime()).then(a -> startTime.i);
 		doAnswer((Answer<Void>) invocationOnMock ->
 		{
 			Object argument = invocationOnMock.getArguments()[0];
 			startTime.i = (Instant) argument;
 			return null;
-		}).when(timersConfig).tzhaarStartTime(nullable(Instant.class));
+		}).when(timersAndBuffsConfig).tzhaarStartTime(nullable(Instant.class));
 
 		ChatMessage chatMessage = new ChatMessage(null, ChatMessageType.GAMEMESSAGE, "", "<col=ef1020>Wave: 1</col>", "", 0);
-		timersPlugin.onChatMessage(chatMessage);
+		timersAndBuffsPlugin.onChatMessage(chatMessage);
 
 		ArgumentCaptor<InfoBox> captor = ArgumentCaptor.forClass(InfoBox.class);
 		verify(infoBoxManager, times(1)).addInfoBox(captor.capture());
@@ -346,12 +348,12 @@ public class TimersPluginTest
 	@Test
 	public void testDeathChargeCast()
 	{
-		when(timersConfig.showArceuus()).thenReturn(true);
+		when(timersAndBuffsConfig.showArceuus()).thenReturn(true);
 		when(client.getRealSkillLevel(Skill.MAGIC)).thenReturn(50);
 		VarbitChanged varbitChanged = new VarbitChanged();
 		varbitChanged.setVarbitId(Varbits.DEATH_CHARGE);
 		varbitChanged.setValue(1);
-		timersPlugin.onVarbitChanged(varbitChanged);
+		timersAndBuffsPlugin.onVarbitChanged(varbitChanged);
 
 		ArgumentCaptor<InfoBox> ibcaptor = ArgumentCaptor.forClass(InfoBox.class);
 		verify(infoBoxManager).addInfoBox(ibcaptor.capture());
@@ -363,12 +365,12 @@ public class TimersPluginTest
 	@Test
 	public void testDeathChargeCooldown()
 	{
-		when(timersConfig.showArceuusCooldown()).thenReturn(true);
+		when(timersAndBuffsConfig.showArceuusCooldown()).thenReturn(true);
 
 		VarbitChanged varbitChanged = new VarbitChanged();
 		varbitChanged.setVarbitId(Varbits.DEATH_CHARGE_COOLDOWN);
 		varbitChanged.setValue(1);
-		timersPlugin.onVarbitChanged(varbitChanged);
+		timersAndBuffsPlugin.onVarbitChanged(varbitChanged);
 
 		ArgumentCaptor<InfoBox> ibcaptor = ArgumentCaptor.forClass(InfoBox.class);
 		verify(infoBoxManager).addInfoBox(ibcaptor.capture());
@@ -379,10 +381,10 @@ public class TimersPluginTest
 	@Test
 	public void testArceuusWard()
 	{
-		when(timersConfig.showArceuus()).thenReturn(true);
+		when(timersAndBuffsConfig.showArceuus()).thenReturn(true);
 		when(client.getRealSkillLevel(Skill.MAGIC)).thenReturn(57);
 		ChatMessage chatMessage = new ChatMessage(null, ChatMessageType.GAMEMESSAGE, "", "<col=0000b2>Your defence against Arceuus magic has been strengthened.</col>", "", 0);
-		timersPlugin.onChatMessage(chatMessage);
+		timersAndBuffsPlugin.onChatMessage(chatMessage);
 
 		ArgumentCaptor<InfoBox> captor = ArgumentCaptor.forClass(InfoBox.class);
 		verify(infoBoxManager).addInfoBox(captor.capture());
@@ -394,12 +396,12 @@ public class TimersPluginTest
 	@Test
 	public void testArceuusWardCooldown()
 	{
-		when(timersConfig.showArceuusCooldown()).thenReturn(true);
+		when(timersAndBuffsConfig.showArceuusCooldown()).thenReturn(true);
 
 		VarbitChanged varbitChanged = new VarbitChanged();
 		varbitChanged.setVarbitId(Varbits.WARD_OF_ARCEUUS_COOLDOWN);
 		varbitChanged.setValue(1);
-		timersPlugin.onVarbitChanged(varbitChanged);
+		timersAndBuffsPlugin.onVarbitChanged(varbitChanged);
 
 		ArgumentCaptor<InfoBox> captor = ArgumentCaptor.forClass(InfoBox.class);
 		verify(infoBoxManager).addInfoBox(captor.capture());
@@ -410,12 +412,12 @@ public class TimersPluginTest
 	@Test
 	public void testCorruptionCooldown()
 	{
-		when(timersConfig.showArceuusCooldown()).thenReturn(true);
+		when(timersAndBuffsConfig.showArceuusCooldown()).thenReturn(true);
 
 		VarbitChanged varbitChanged = new VarbitChanged();
 		varbitChanged.setVarbitId(Varbits.CORRUPTION_COOLDOWN);
 		varbitChanged.setValue(1);
-		timersPlugin.onVarbitChanged(varbitChanged);
+		timersAndBuffsPlugin.onVarbitChanged(varbitChanged);
 
 		ArgumentCaptor<InfoBox> captor = ArgumentCaptor.forClass(InfoBox.class);
 		verify(infoBoxManager).addInfoBox(captor.capture());
@@ -426,11 +428,11 @@ public class TimersPluginTest
 	@Test
 	public void testShadowVeil()
 	{
-		when(timersConfig.showArceuus()).thenReturn(true);
+		when(timersAndBuffsConfig.showArceuus()).thenReturn(true);
 		when(client.getRealSkillLevel(Skill.MAGIC)).thenReturn(57);
 
 		ChatMessage chatMessage = new ChatMessage(null, ChatMessageType.GAMEMESSAGE, "", "<col=6800bf>Your thieving abilities have been enhanced.</col>", "", 0);
-		timersPlugin.onChatMessage(chatMessage);
+		timersAndBuffsPlugin.onChatMessage(chatMessage);
 
 		ArgumentCaptor<InfoBox> captor = ArgumentCaptor.forClass(InfoBox.class);
 		verify(infoBoxManager).addInfoBox(captor.capture());
@@ -441,12 +443,12 @@ public class TimersPluginTest
 	@Test
 	public void testShadowVeilCooldown()
 	{
-		when(timersConfig.showArceuusCooldown()).thenReturn(true);
+		when(timersAndBuffsConfig.showArceuusCooldown()).thenReturn(true);
 
 		VarbitChanged varbitChanged = new VarbitChanged();
 		varbitChanged.setVarbitId(Varbits.SHADOW_VEIL_COOLDOWN);
 		varbitChanged.setValue(1);
-		timersPlugin.onVarbitChanged(varbitChanged);
+		timersAndBuffsPlugin.onVarbitChanged(varbitChanged);
 
 		ArgumentCaptor<InfoBox> captor = ArgumentCaptor.forClass(InfoBox.class);
 		verify(infoBoxManager).addInfoBox(captor.capture());
@@ -457,11 +459,11 @@ public class TimersPluginTest
 	@Test
 	public void testThrall()
 	{
-		when(timersConfig.showArceuus()).thenReturn(true);
+		when(timersAndBuffsConfig.showArceuus()).thenReturn(true);
 		when(client.getBoostedSkillLevel(Skill.MAGIC)).thenReturn(60);
 
 		ChatMessage chatMessage = new ChatMessage(null, ChatMessageType.GAMEMESSAGE, "", "<col=ef0083>You resurrect a greater zombified thrall.</col>", "", 0);
-		timersPlugin.onChatMessage(chatMessage);
+		timersAndBuffsPlugin.onChatMessage(chatMessage);
 
 		ArgumentCaptor<InfoBox> ibcaptor = ArgumentCaptor.forClass(InfoBox.class);
 		verify(infoBoxManager).addInfoBox(ibcaptor.capture());
@@ -473,12 +475,12 @@ public class TimersPluginTest
 	@Test
 	public void testThrallCooldown()
 	{
-		when(timersConfig.showArceuusCooldown()).thenReturn(true);
+		when(timersAndBuffsConfig.showArceuusCooldown()).thenReturn(true);
 
 		VarbitChanged varbitChanged = new VarbitChanged();
 		varbitChanged.setVarbitId(Varbits.RESURRECT_THRALL_COOLDOWN);
 		varbitChanged.setValue(1);
-		timersPlugin.onVarbitChanged(varbitChanged);
+		timersAndBuffsPlugin.onVarbitChanged(varbitChanged);
 
 		ArgumentCaptor<InfoBox> ibcaptor = ArgumentCaptor.forClass(InfoBox.class);
 		verify(infoBoxManager).addInfoBox(ibcaptor.capture());
@@ -490,12 +492,12 @@ public class TimersPluginTest
 	@Test
 	public void testImbuedHeartStart()
 	{
-		when(timersConfig.showImbuedHeart()).thenReturn(true);
+		when(timersAndBuffsConfig.showImbuedHeart()).thenReturn(true);
 
 		VarbitChanged varbitChanged = new VarbitChanged();
 		varbitChanged.setVarbitId(Varbits.IMBUED_HEART_COOLDOWN);
 		varbitChanged.setValue(70);
-		timersPlugin.onVarbitChanged(varbitChanged);
+		timersAndBuffsPlugin.onVarbitChanged(varbitChanged);
 
 		ArgumentCaptor<InfoBox> captor = ArgumentCaptor.forClass(InfoBox.class);
 		verify(infoBoxManager).addInfoBox(captor.capture());
@@ -507,15 +509,15 @@ public class TimersPluginTest
 	@Test
 	public void testImbuedHeartEnd()
 	{
-		when(timersConfig.showImbuedHeart()).thenReturn(true);
+		when(timersAndBuffsConfig.showImbuedHeart()).thenReturn(true);
 
 		VarbitChanged varbitChanged = new VarbitChanged();
 		varbitChanged.setVarbitId(Varbits.IMBUED_HEART_COOLDOWN);
 		varbitChanged.setValue(70);
-		timersPlugin.onVarbitChanged(varbitChanged); // Calls removeIf once (on createGameTimer)
+		timersAndBuffsPlugin.onVarbitChanged(varbitChanged); // Calls removeIf once (on createGameTimer)
 
 		ArgumentCaptor<Predicate<InfoBox>> prcaptor = ArgumentCaptor.forClass(Predicate.class);
-		TimerTimer imbuedHeartInfoBox = new TimerTimer(GameTimer.IMBUEDHEART, Duration.ofSeconds(420), timersPlugin);
+		TimerTimer imbuedHeartInfoBox = new TimerTimer(GameTimer.IMBUEDHEART, Duration.ofSeconds(420), timersAndBuffsPlugin);
 		verify(infoBoxManager, times (1)).addInfoBox(any());
 		verify(infoBoxManager, times(1)).removeIf(prcaptor.capture());
 		Predicate<InfoBox> pred = prcaptor.getValue();
@@ -524,7 +526,7 @@ public class TimersPluginTest
 		varbitChanged = new VarbitChanged();
 		varbitChanged.setVarbitId(Varbits.IMBUED_HEART_COOLDOWN);
 		varbitChanged.setValue(0);
-		timersPlugin.onVarbitChanged(varbitChanged); // Calls removeIf once
+		timersAndBuffsPlugin.onVarbitChanged(varbitChanged); // Calls removeIf once
 
 		verify(infoBoxManager, times(1)).addInfoBox(any());
 		verify(infoBoxManager, times(2)).removeIf(prcaptor.capture());
@@ -535,9 +537,9 @@ public class TimersPluginTest
 	@Test
 	public void testMartinPickpocket()
 	{
-		when(timersConfig.showPickpocketStun()).thenReturn(true);
+		when(timersAndBuffsConfig.showPickpocketStun()).thenReturn(true);
 		ChatMessage chatMessage = new ChatMessage(null, ChatMessageType.SPAM, "", "You fail to pick Martin's pocket.", "", 0);
-		timersPlugin.onChatMessage(chatMessage);
+		timersAndBuffsPlugin.onChatMessage(chatMessage);
 
 		ArgumentCaptor<InfoBox> captor = ArgumentCaptor.forClass(InfoBox.class);
 		verify(infoBoxManager).addInfoBox(captor.capture());
@@ -549,16 +551,16 @@ public class TimersPluginTest
 	@Test
 	public void testCoXOverload()
 	{
-		when(timersConfig.showOverload()).thenReturn(true);
+		when(timersAndBuffsConfig.showOverload()).thenReturn(true);
 		ArgumentCaptor<Predicate<InfoBox>> prcaptor = ArgumentCaptor.forClass(Predicate.class);
 
 		when(client.getVarbitValue(Varbits.IN_RAID)).thenReturn(1);
 		VarbitChanged varbitChanged = new VarbitChanged();
 		varbitChanged.setVarbitId(Varbits.COX_OVERLOAD_REFRESHES_REMAINING);
 		varbitChanged.setValue(15);
-		timersPlugin.onVarbitChanged(varbitChanged);
+		timersAndBuffsPlugin.onVarbitChanged(varbitChanged);
 
-		TimerTimer overloadInfobox = new TimerTimer(GameTimer.OVERLOAD_RAID, Duration.ofSeconds(225), timersPlugin);
+		TimerTimer overloadInfobox = new TimerTimer(GameTimer.OVERLOAD_RAID, Duration.ofSeconds(225), timersAndBuffsPlugin);
 		verify(infoBoxManager).addInfoBox(any());
 		verify(infoBoxManager).removeIf(prcaptor.capture());
 		Predicate<InfoBox> pred = prcaptor.getValue();
@@ -566,7 +568,7 @@ public class TimersPluginTest
 
 		// Remove on running out
 		varbitChanged.setValue(0);
-		timersPlugin.onVarbitChanged(varbitChanged);
+		timersAndBuffsPlugin.onVarbitChanged(varbitChanged);
 
 		verify(infoBoxManager).addInfoBox(any());
 		verify(infoBoxManager, times(2)).removeIf(any());
@@ -575,15 +577,15 @@ public class TimersPluginTest
 	@Test
 	public void testNMZOverload()
 	{
-		when(timersConfig.showOverload()).thenReturn(true);
+		when(timersAndBuffsConfig.showOverload()).thenReturn(true);
 		ArgumentCaptor<Predicate<InfoBox>> prcaptor = ArgumentCaptor.forClass(Predicate.class);
 
 		VarbitChanged varbitChanged = new VarbitChanged();
 		varbitChanged.setVarbitId(Varbits.NMZ_OVERLOAD_REFRESHES_REMAINING);
 		varbitChanged.setValue(9);
-		timersPlugin.onVarbitChanged(varbitChanged);
+		timersAndBuffsPlugin.onVarbitChanged(varbitChanged);
 
-		TimerTimer overloadInfobox = new TimerTimer(GameTimer.OVERLOAD, Duration.ofSeconds(135), timersPlugin);
+		TimerTimer overloadInfobox = new TimerTimer(GameTimer.OVERLOAD, Duration.ofSeconds(135), timersAndBuffsPlugin);
 		verify(infoBoxManager).addInfoBox(any());
 		verify(infoBoxManager).removeIf(prcaptor.capture());
 		Predicate<InfoBox> pred = prcaptor.getValue();
@@ -591,7 +593,7 @@ public class TimersPluginTest
 
 		// Remove on running out
 		varbitChanged.setValue(0);
-		timersPlugin.onVarbitChanged(varbitChanged);
+		timersAndBuffsPlugin.onVarbitChanged(varbitChanged);
 
 		verify(infoBoxManager).addInfoBox(any());
 		verify(infoBoxManager, times(2)).removeIf(any());