@Subscribe
	public void onChatMessage(ChatMessage chatMessage)
	{
		if (chatMessage.getType() != ChatMessageType.TRADE
			&& chatMessage.getType() != ChatMessageType.GAMEMESSAGE
			&& chatMessage.getType() != ChatMessageType.SPAM
			&& chatMessage.getType() != ChatMessageType.FRIENDSCHATNOTIFICATION)
		{
			return;
		}

		String message = chatMessage.getMessage();
		Matcher matcher = KILLCOUNT_PATTERN.matcher(message);
		if (matcher.find())
		{
			final String boss = matcher.group("boss");
			final int kc = Integer.parseInt(matcher.group("kc"));
			final String pre = matcher.group("pre");
			final String post = matcher.group("post");

			if (Strings.isNullOrEmpty(pre) && Strings.isNullOrEmpty(post))
			{
				unsetKc(boss);
				return;
			}

			String renamedBoss = KILLCOUNT_RENAMES
				.getOrDefault(boss, boss)
				// The config service doesn't support keys with colons in them
				.replace(":", "");
			if (boss != renamedBoss)
			{
				// Unset old TOB kc
				unsetKc(boss);
				unsetPb(boss);
				unsetKc(boss.replace(":", "."));
				unsetPb(boss.replace(":", "."));
				// Unset old story mode
				unsetKc("Theatre of Blood Story Mode");
				unsetPb("Theatre of Blood Story Mode");
			}

			setKc(renamedBoss, kc);
			// We either already have the pb, or need to remember the boss for the upcoming pb
			if (lastPb > -1)
			{
				log.debug("Got out-of-order personal best for {}: {}", renamedBoss, lastPb);

				if (renamedBoss.contains("Theatre of Blood"))
				{
					// TOB team size isn't sent in the kill message, but can be computed from varbits
					int tobTeamSize = tobTeamSize();
					lastTeamSize = tobTeamSize == 1 ? "Solo" : (tobTeamSize + " players");
				}
				else if (renamedBoss.contains("Tombs of Amascut"))
				{
					// TOA team size isn't sent in the kill message, but can be computed from varbits
					int toaTeamSize = toaTeamSize();
					lastTeamSize = toaTeamSize == 1 ? "Solo" : (toaTeamSize + " players");
				}

				final double pb = getPb(renamedBoss);
				// If a raid with a team size, only update the pb if it is lower than the existing pb
				// so that the pb is the overall lowest of any team size
				if (lastTeamSize == null || pb == 0 || lastPb < pb)
				{
					log.debug("Setting overall pb (old: {})", pb);
					setPb(renamedBoss, lastPb);
				}
				if (lastTeamSize != null)
				{
					log.debug("Setting team size pb: {}", lastTeamSize);
					setPb(renamedBoss + " " + lastTeamSize, lastPb);
				}

				lastPb = -1;
				lastTeamSize = null;
			}
			else
			{
				lastBossKill = renamedBoss;
				lastBossTime = client.getTickCount();
			}
			return;
		}

		matcher = DUEL_ARENA_WINS_PATTERN.matcher(message);
		if (matcher.find())
		{
			final int oldWins = getKc("Duel Arena Wins");
			final int wins = matcher.group(2).equals("one") ? 1 :
				Integer.parseInt(matcher.group(2).replace(",", ""));
			final String result = matcher.group(1);
			int winningStreak = getKc("Duel Arena Win Streak");
			int losingStreak = getKc("Duel Arena Lose Streak");

			if (result.equals("won") && wins > oldWins)
			{
				losingStreak = 0;
				winningStreak += 1;
			}
			else if (result.equals("were defeated"))
			{
				losingStreak += 1;
				winningStreak = 0;
			}
			else
			{
				log.warn("unrecognized duel streak chat message: {}", message);
			}

			setKc("Duel Arena Wins", wins);
			setKc("Duel Arena Win Streak", winningStreak);
			setKc("Duel Arena Lose Streak", losingStreak);
		}

		matcher = DUEL_ARENA_LOSSES_PATTERN.matcher(message);
		if (matcher.find())
		{
			int losses = matcher.group(1).equals("one") ? 1 :
				Integer.parseInt(matcher.group(1).replace(",", ""));

			setKc("Duel Arena Losses", losses);
		}

		matcher = KILL_DURATION_PATTERN.matcher(message);
		if (matcher.find())
		{
			matchPb(matcher);
		}

		matcher = NEW_PB_PATTERN.matcher(message);
		if (matcher.find())
		{
			matchPb(matcher);
		}

		matcher = RAIDS_PB_PATTERN.matcher(message);
		if (matcher.find())
		{
			matchPb(matcher);
		}

		matcher = RAIDS_DURATION_PATTERN.matcher(message);
		if (matcher.find())
		{
			matchPb(matcher);
		}

		matcher = HS_PB_PATTERN.matcher(message);
		if (matcher.find())
		{
			int floor = Integer.parseInt(matcher.group("floor"));
			String floortime = matcher.group("floortime");
			String floorpb = matcher.group("floorpb");
			String otime = matcher.group("otime");
			String opb = matcher.group("opb");

			String pb = MoreObjects.firstNonNull(floorpb, floortime);
			setPb("Hallowed Sepulchre Floor " + floor, timeStringToSeconds(pb));

			if (otime != null)
			{
				pb = MoreObjects.firstNonNull(opb, otime);
				setPb("Hallowed Sepulchre", timeStringToSeconds(pb));
			}
		}

		matcher = HS_KC_FLOOR_PATTERN.matcher(message);
		if (matcher.find())
		{
			int floor = Integer.parseInt(matcher.group(1));
			int kc = Integer.parseInt(matcher.group(2).replaceAll(",", ""));
			setKc("Hallowed Sepulchre Floor " + floor, kc);
		}

		matcher = HS_KC_GHC_PATTERN.matcher(message);
		if (matcher.find())
		{
			int kc = Integer.parseInt(matcher.group(1).replaceAll(",", ""));
			setKc("Hallowed Sepulchre", kc);
		}

		matcher = HUNTER_RUMOUR_KC_PATTERN.matcher(message);
		if (matcher.find())
		{
			int kc = Integer.parseInt(matcher.group(1).replaceAll(",", ""));
			setKc("Hunter Rumours", kc);
		}

		if (lastBossKill != null && lastBossTime != client.getTickCount())
		{
			lastBossKill = null;
			lastBossTime = -1;
		}

		matcher = COLLECTION_LOG_ITEM_PATTERN.matcher(message);
		if (matcher.find())
		{
			String item = matcher.group(1);
			int petId = findPet(item);

			if (petId != -1)
			{
				final List<Integer> petList = new ArrayList<>(getPetList());
				if (!petList.contains(petId))
				{
					log.debug("New pet added: {}/{}", item, petId);
					petList.add(petId);
					setPetList(petList);
				}
			}
		}

		matcher = GUARDIANS_OF_THE_RIFT_PATTERN.matcher(message);
		if (matcher.find())
		{
			int kc = Integer.parseInt(matcher.group(1));
			setKc("Guardians of the Rift", kc);
		}
	}
@Test
	public void testCoXKill()
	{
		ChatMessage chatMessage = new ChatMessage(null, FRIENDSCHATNOTIFICATION, "", "<col=ef20ff>Congratulations - your raid is complete!</col><br>Team size: <col=ff0000>24+ players</col> Duration:</col> <col=ff0000>37:04.20</col> (new personal best)</col>>", null, 0);
		chatCommandsPlugin.onChatMessage(chatMessage);

		chatMessage = new ChatMessage(null, GAMEMESSAGE, "", "Your completed Chambers of Xeric count is: <col=ff0000>51</col>.", null, 0);
		chatCommandsPlugin.onChatMessage(chatMessage);

		verify(configManager).setRSProfileConfiguration("killcount", "chambers of xeric", 51);
		verify(configManager).setRSProfileConfiguration("personalbest", "chambers of xeric", 37 * 60 + 4.2);
		verify(configManager).setRSProfileConfiguration("personalbest", "chambers of xeric 24+ players", 37 * 60 + 4.2);
	}