Skip to content

Commit

Permalink
kingdom: add notification message for favor and coffer value
Browse files Browse the repository at this point in the history
This allows setting a configurable threshold where you will receive a
message informing you your favor and/or coffer is low on login

Co-authored-by: Adam <Adam@sigterm.info>
  • Loading branch information
Brandt Hill and Adam- committed Feb 28, 2021
1 parent 9588d72 commit d0ec76e
Show file tree
Hide file tree
Showing 4 changed files with 252 additions and 16 deletions.
5 changes: 3 additions & 2 deletions runelite-api/src/main/java/net/runelite/api/Varbits.java
Original file line number Diff line number Diff line change
Expand Up @@ -387,9 +387,10 @@ public enum Varbits
MULTICOMBAT_AREA(4605),

/**
* Kingdom Management
* Kingdom of Miscellania Management
* Kingdom Approval is represented as a 7-bit unsigned integer; 127 corresponds to 100% approval
*/
KINGDOM_FAVOR(72),
KINGDOM_APPROVAL(72),
KINGDOM_COFFER(74),

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* Copyright (c) 2020, Brandt Hill <https://github.com/BrandtHill>
* 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.kingdomofmiscellania;

import net.runelite.client.config.Config;
import net.runelite.client.config.ConfigGroup;
import net.runelite.client.config.ConfigItem;
import net.runelite.client.config.Range;

@ConfigGroup(KingdomConfig.CONFIG_GROUP_NAME)
public interface KingdomConfig extends Config
{
String CONFIG_GROUP_NAME = "kingdomofmiscellania";
int MAX_COFFER = 7_500_000;
int MAX_APPROVAL_PERCENT = 100;

@ConfigItem(
position = 1,
keyName = "sendNotifications",
name = "Send Notifications",
description = "Send chat notifications upon login showing current estimated coffer and approval"
)
default boolean shouldSendNotifications()
{
return false;
}

@Range(
max = MAX_COFFER
)
@ConfigItem(
position = 2,
keyName = "cofferThreshold",
name = "Coffer Threshold",
description = "Send notifications if coffer is below this value"
)
default int getCofferThreshold()
{
return MAX_COFFER;
}

@Range(
max = MAX_APPROVAL_PERCENT
)
@ConfigItem(
position = 3,
keyName = "approvalThreshold",
name = "Approval Threshold",
description = "Send notifications if approval percentage is below this value"
)
default int getApprovalThreshold()
{
return MAX_APPROVAL_PERCENT;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,20 +34,20 @@ public class KingdomCounter extends Counter

KingdomCounter(BufferedImage image, KingdomPlugin plugin)
{
super(image, plugin, plugin.getFavor());
super(image, plugin, plugin.getApproval());
this.plugin = plugin;
}

@Override
public String getText()
{
return KingdomPlugin.getFavorPercent(plugin.getFavor()) + "%";
return KingdomPlugin.getApprovalPercent(plugin.getApproval()) + "%";
}

@Override
public String getTooltip()
{
return "Favor: " + plugin.getFavor() + "/127" + "</br>"
return "Approval: " + plugin.getApproval() + "/" + KingdomPlugin.MAX_APPROVAL + "</br>"
+ "Coffer: " + QuantityFormatter.quantityToStackSize(plugin.getCoffer());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,32 +25,55 @@
package net.runelite.client.plugins.kingdomofmiscellania;

import com.google.common.collect.ImmutableSet;
import com.google.inject.Provides;
import java.time.Duration;
import java.time.Instant;
import javax.inject.Inject;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.ChatMessageType;
import net.runelite.api.Client;
import net.runelite.api.GameState;
import static net.runelite.api.ItemID.TEAK_CHEST;
import net.runelite.api.Quest;
import net.runelite.api.QuestState;
import net.runelite.api.VarPlayer;
import net.runelite.api.Varbits;
import net.runelite.api.events.GameStateChanged;
import net.runelite.api.events.GameTick;
import net.runelite.api.events.VarbitChanged;
import net.runelite.client.chat.ChatColorType;
import net.runelite.client.chat.ChatMessageBuilder;
import net.runelite.client.chat.ChatMessageManager;
import net.runelite.client.chat.QueuedMessage;
import net.runelite.client.config.ConfigManager;
import net.runelite.client.eventbus.Subscribe;
import net.runelite.client.game.ItemManager;
import net.runelite.client.plugins.Plugin;
import net.runelite.client.plugins.PluginDescriptor;
import net.runelite.client.ui.overlay.infobox.InfoBoxManager;
import net.runelite.client.util.QuantityFormatter;

@PluginDescriptor(
name = "Kingdom of Miscellania",
description = "Show amount of favor when inside Miscellania",
tags = {"favor", "favour", "managing", "overlay"},
description = "Show amount of approval when inside Miscellania",
tags = {"favor", "favour", "managing", "overlay", "approval", "coffer"},
enabledByDefault = false
)
@Slf4j
public class KingdomPlugin extends Plugin
{
private static final ImmutableSet<Integer> KINGDOM_REGION = ImmutableSet.of(10044, 10300);
private static final String CONFIG_LAST_CHANGED_KEY = "lastChanged";
private static final String CONFIG_COFFER_KEY = "coffer";
private static final String CONFIG_APPROVAL_KEY = "approval";
private static final String CHAT_MESSAGE_FORMAT = "Your Kingdom of Miscellania approval is %d%%, and your coffer has %s coins.";
private static final int MAX_WITHDRAWAL_BASE = 50_000;
private static final int MAX_WITHDRAWAL_ROYAL_TROUBLE = 75_000;
private static final float APPROVAL_DECREMENT_BASE = 0.025f;
private static final float APPROVAL_DECREMENT_ROYAL_TROUBLE = 0.010f;
static final int MAX_APPROVAL = 127;

private boolean loggingIn;

@Inject
private Client client;
Expand All @@ -61,8 +84,14 @@ public class KingdomPlugin extends Plugin
@Inject
private ItemManager itemManager;

@Getter
private int favor = 0, coffer = 0;
@Inject
private KingdomConfig config;

@Inject
private ConfigManager configManager;

@Inject
private ChatMessageManager chatMessageManager;

private KingdomCounter counter;

Expand All @@ -72,11 +101,28 @@ protected void shutDown() throws Exception
removeKingdomInfobox();
}

@Provides
KingdomConfig getConfig(ConfigManager configManager)
{
return configManager.getConfig(KingdomConfig.class);
}

@Subscribe
public void onVarbitChanged(VarbitChanged event)
{
favor = client.getVar(Varbits.KINGDOM_FAVOR);
coffer = client.getVar(Varbits.KINGDOM_COFFER);
final int coffer = client.getVar(Varbits.KINGDOM_COFFER);
final int approval = client.getVar(Varbits.KINGDOM_APPROVAL);

if (client.getGameState() == GameState.LOGGED_IN
&& isThroneOfMiscellaniaCompleted()
&& (isInKingdom() || coffer > 0 && approval > 0)
&& (getCoffer() != coffer || getApproval() != approval))
{
setLastChanged(Instant.now());
setCoffer(coffer);
setApproval(approval);
}

processInfobox();
}

Expand All @@ -87,19 +133,59 @@ public void onGameStateChanged(GameStateChanged event)
{
processInfobox();
}
else if (event.getGameState() == GameState.LOGGING_IN)
{
loggingIn = true;
}
}

@Subscribe
public void onGameTick(GameTick gameTick)
{
if (loggingIn)
{
loggingIn = false;
createNotification();
}
}

private void processInfobox()
{
if (client.getGameState() == GameState.LOGGED_IN && hasCompletedQuest() && isInKingdom())
if (client.getGameState() == GameState.LOGGED_IN && isThroneOfMiscellaniaCompleted() && isInKingdom())
{
addKingdomInfobox();
}
else
{
removeKingdomInfobox();
}
}

private void createNotification()
{
if (!config.shouldSendNotifications() || !isThroneOfMiscellaniaCompleted())
{
return;
}

if (getLastChanged() == null)
{
log.debug("Kingdom Of Miscellania values not yet set. Visit Miscellania to automatically set values.");
return;
}

Instant lastChanged = getLastChanged();
int lastCoffer = getCoffer();
int lastApproval = getApproval();
int estimatedCoffer = estimateCoffer(lastChanged, lastCoffer);
int estimatedApproval = estimateApproval(lastChanged, lastApproval);
if (estimatedCoffer < config.getCofferThreshold() || getApprovalPercent(estimatedApproval) < config.getApprovalThreshold())
{
sendChatMessage(String.format(
CHAT_MESSAGE_FORMAT,
getApprovalPercent(estimatedApproval),
QuantityFormatter.quantityToStackSize(estimatedCoffer)));
}
}

private void addKingdomInfobox()
Expand All @@ -122,20 +208,92 @@ private void removeKingdomInfobox()
}
}

private int estimateCoffer(Instant lastChanged, int lastCoffer)
{
int daysSince = (int) Duration.between(lastChanged, Instant.now()).toDays();
int maxDailyWithdrawal = isRoyalTroubleCompleted() ? MAX_WITHDRAWAL_ROYAL_TROUBLE : MAX_WITHDRAWAL_BASE;
int maxDailyThreshold = maxDailyWithdrawal * 10;

for (int i = 0; i < daysSince; i++)
{
lastCoffer -= (lastCoffer > maxDailyThreshold) ? maxDailyWithdrawal : lastCoffer / 10;
}
return lastCoffer;
}

private int estimateApproval(Instant lastChanged, int lastApproval)
{
int daysSince = (int) Duration.between(lastChanged, Instant.now()).toDays();
float dailyPercentage = isRoyalTroubleCompleted() ? APPROVAL_DECREMENT_ROYAL_TROUBLE : APPROVAL_DECREMENT_BASE;

lastApproval -= (int) (daysSince * dailyPercentage * MAX_APPROVAL);
return Math.max(lastApproval, 0);
}

private boolean isInKingdom()
{
return client.getLocalPlayer() != null
&& KINGDOM_REGION.contains(client.getLocalPlayer().getWorldLocation().getRegionID());
}

private boolean hasCompletedQuest()
private boolean isThroneOfMiscellaniaCompleted()
{
return client.getVar(VarPlayer.THRONE_OF_MISCELLANIA) > 0;
}

static int getFavorPercent(int favor)
private boolean isRoyalTroubleCompleted()
{
return Quest.ROYAL_TROUBLE.getState(client) == QuestState.FINISHED;
}

static int getApprovalPercent(int approval)
{
return (approval * 100) / MAX_APPROVAL;
}

private void sendChatMessage(String chatMessage)
{
final String message = new ChatMessageBuilder()
.append(ChatColorType.HIGHLIGHT)
.append(chatMessage)
.build();

chatMessageManager.queue(
QueuedMessage.builder()
.type(ChatMessageType.CONSOLE)
.runeLiteFormattedMessage(message)
.build());
}

private Instant getLastChanged()
{
return configManager.getRSProfileConfiguration(KingdomConfig.CONFIG_GROUP_NAME, CONFIG_LAST_CHANGED_KEY, Instant.class);
}

private void setLastChanged(Instant lastChanged)
{
configManager.setRSProfileConfiguration(KingdomConfig.CONFIG_GROUP_NAME, CONFIG_LAST_CHANGED_KEY, lastChanged);
}

int getCoffer()
{
return (favor * 100) / 127;
Integer coffer = configManager.getRSProfileConfiguration(KingdomConfig.CONFIG_GROUP_NAME, CONFIG_COFFER_KEY, int.class);
return coffer == null ? 0 : coffer;
}

private void setCoffer(int coffer)
{
configManager.setRSProfileConfiguration(KingdomConfig.CONFIG_GROUP_NAME, CONFIG_COFFER_KEY, coffer);
}

int getApproval()
{
Integer approval = configManager.getRSProfileConfiguration(KingdomConfig.CONFIG_GROUP_NAME, CONFIG_APPROVAL_KEY, int.class);
return approval == null ? 0 : approval;
}

private void setApproval(int approval)
{
configManager.setRSProfileConfiguration(KingdomConfig.CONFIG_GROUP_NAME, CONFIG_APPROVAL_KEY, approval);
}
}

0 comments on commit d0ec76e

Please sign in to comment.