package com.toofifty.easygiantsfoundry; import com.google.inject.Provides; import static com.toofifty.easygiantsfoundry.EasyGiantsFoundryClientIDs.VARBIT_GAME_STAGE; import static com.toofifty.easygiantsfoundry.EasyGiantsFoundryClientIDs.VARBIT_HEAT; import com.toofifty.easygiantsfoundry.enums.Stage; 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.GameObject; import net.runelite.api.GameState; import net.runelite.api.gameval.InventoryID; import net.runelite.api.Item; import net.runelite.api.ItemContainer; import net.runelite.api.MenuAction; import net.runelite.api.Skill; import net.runelite.api.events.*; import net.runelite.api.widgets.Widget; import net.runelite.client.Notifier; import net.runelite.client.callback.ClientThread; import net.runelite.client.config.ConfigManager; import net.runelite.client.eventbus.Subscribe; import net.runelite.client.events.ConfigChanged; import net.runelite.client.plugins.Plugin; import net.runelite.client.plugins.PluginDescriptor; import net.runelite.client.ui.overlay.OverlayManager; import java.util.Objects; import java.util.Set; @Slf4j @PluginDescriptor( name = "Easy Giants' Foundry", description = "Helpful overlays for the Giants' Foundry minigame" ) public class EasyGiantsFoundryPlugin extends Plugin { private static final int TRIP_HAMMER = 44619; private static final int GRINDSTONE = 44620; private static final int POLISHING_WHEEL = 44621; private static final int LAVA_POOL = 44631; private static final int WATERFALL = 44632; private static final int CRUCIBLE = 44776; private static final int MOULD_JIG = 44777; private static final int STORAGE = 44778; private static final int KOVAC_NPC = 11472; private static final int PREFORM = 27010; private static final int REPUTATION_VARBIT = 3436; // 5 total items, includes Smiths gloves (i); private static final Set SMITHS_OUTFIT_IDS = Set.of(27023, 27025, 27027, 27029, 27031); private Stage oldStage; private int lastBoost; private boolean bonusNotified = false; @Getter private int reputation; @Inject private EasyGiantsFoundryState state; @Inject private EasyGiantsFoundryHelper helper; @Inject private MetalBarCounter metalBarCounter; @Inject private OverlayManager overlayManager; @Inject private FoundryOverlay2D overlay2d; @Inject private FoundryOverlay3D overlay3d; @Inject private MouldHelper mouldHelper; @Inject private EasyGiantsFoundryConfig config; @Inject private Notifier notifier; @Inject private Client client; @Inject private ClientThread clientThread; @Inject private ConfigManager configManager; @Override protected void startUp() { overlayManager.add(overlay2d); overlayManager.add(overlay3d); if (client.getGameState() == GameState.LOGGED_IN) { reputation = client.getVarpValue(REPUTATION_VARBIT); } } @Override protected void shutDown() { overlayManager.remove(overlay2d); overlayManager.remove(overlay3d); metalBarCounter.clear(); } @Subscribe public void onGameObjectSpawned(GameObjectSpawned event) { GameObject gameObject = event.getGameObject(); switch (gameObject.getId()) { case POLISHING_WHEEL: state.setEnabled(true); overlay3d.polishingWheel = gameObject; break; case GRINDSTONE: overlay3d.grindstone = gameObject; break; case LAVA_POOL: overlay3d.lavaPool = gameObject; break; case WATERFALL: overlay3d.waterfall = gameObject; break; case TRIP_HAMMER: overlay3d.tripHammer = gameObject; break; case MOULD_JIG: overlay3d.mouldJig = gameObject; break; case CRUCIBLE: overlay3d.crucible = gameObject; break; case STORAGE: overlay3d.storage = gameObject; break; } } @Subscribe public void onGameObjectDespawned(GameObjectDespawned event) { GameObject gameObject = event.getGameObject(); switch (gameObject.getId()) { case POLISHING_WHEEL: state.setEnabled(false); overlay3d.polishingWheel = null; break; case GRINDSTONE: overlay3d.grindstone = null; break; case LAVA_POOL: overlay3d.lavaPool = null; break; case WATERFALL: overlay3d.waterfall = null; break; case TRIP_HAMMER: overlay3d.tripHammer = null; break; case MOULD_JIG: overlay3d.mouldJig = null; break; case CRUCIBLE: overlay3d.crucible = null; break; case STORAGE: overlay3d.storage = null; break; } } @Subscribe public void onGameStateChanged(GameStateChanged event) { if (event.getGameState().equals(GameState.LOADING)) { state.setEnabled(false); } if (event.getGameState().equals(GameState.LOGGED_IN)) { reputation = client.getVarpValue(REPUTATION_VARBIT); } } @Subscribe public void onStatChanged(StatChanged statChanged) { final int curBoost = statChanged.getBoostedLevel(); // if the difference between current and last boost is != 0 then a stat boost (or drop) change occurred if (!statChanged.getSkill().equals(Skill.SMITHING) || curBoost != lastBoost || !state.isEnabled() || state.getCurrentStage() == null) { lastBoost = curBoost; return; } if (config.stageNotification().isEnabled() && state.getActionsLeftInStage() == config.StageNotificationsThreshold() && (oldStage == null || oldStage != state.getCurrentStage())) { notifier.notify(config.stageNotification(), "About to finish the current stage!"); oldStage = state.getCurrentStage(); } else if (config.heatNotification().isEnabled() && state.getActionsForHeatLevel() == config.HeatNotificationsThreshold()) { notifier.notify(config.heatNotification(), "About to run out of heat!"); } } @Subscribe public void onNpcSpawned(NpcSpawned event) { if (event.getNpc().getId() == KOVAC_NPC) { overlay3d.kovac = event.getNpc(); } } @Subscribe public void onNpcDespawned(NpcDespawned event) { if (event.getNpc().getId() == KOVAC_NPC) { overlay3d.kovac = null; } } @Subscribe public void onItemContainerChanged(ItemContainerChanged event) { if (event.getContainerId() == InventoryID.WORN) { if (event.getItemContainer().count(PREFORM) == 0) { state.reset(); oldStage = null; } else { updateSmithsOutfitPieces(); } } else if (event.getContainerId() == InventoryID.INV || event.getContainerId() == InventoryID.BANK) { metalBarCounter.put(event.getItemContainer()); } } public void onMenuEntryAdded(MenuEntryAdded event) { if (event.getOption().startsWith("Heat-preform") || event.getOption().startsWith("Dunk-preform")) { } else if (event.getOption().startsWith("Cool-preform") || event.getOption().startsWith("Quench-preform")) { } } @Subscribe public void onMenuOptionClicked(MenuOptionClicked event) { clientThread.invokeAtTickEnd(() -> { if (!(event.getMenuAction() == MenuAction.GAME_OBJECT_FIRST_OPTION || event.getMenuAction() == MenuAction.GAME_OBJECT_SECOND_OPTION || event.getMenuAction() == MenuAction.GAME_OBJECT_THIRD_OPTION || event.getMenuAction() == MenuAction.GAME_OBJECT_FOURTH_OPTION || event.getMenuAction() == MenuAction.GAME_OBJECT_FIFTH_OPTION || event.getMenuAction() == MenuAction.WIDGET_TARGET_ON_GAME_OBJECT || event.getMenuAction() == MenuAction.WALK)) { return; } if (!state.isEnabled()) return; if (event.getMenuTarget().contains("Crucible ")) { if (event.getMenuOption().equals("Pour")) { if (client.getVarbitValue(VARBIT_GAME_STAGE) == 1) { state.setLastKnownCrucibleScore((int) state.getCrucibleScore()); } // add persistent game message of the alloy value so user can reference later. client.addChatMessage(ChatMessageType.GAMEMESSAGE, "", "The score of the preform is " + ((int) state.getCrucibleScore() + state.getMouldScore()), null); } } // Could not find a varbit to capture, so capture the menu-option directly. // start the HeatActionStateMachine when varbit begins to update in onVarbitChanged() if (event.getMenuOption().startsWith("Heat-preform")) { state.heatActionStateMachine.stop(); state.heatActionStateMachine.setup(false, true, "heats"); } else if (event.getMenuOption().startsWith("Dunk-preform")) { state.heatActionStateMachine.stop(); state.heatActionStateMachine.setup(true, true, "dunks"); } else if (event.getMenuOption().startsWith("Cool-preform")) { state.heatActionStateMachine.stop(); state.heatActionStateMachine.setup(false, false, "cools"); } else if (event.getMenuOption().startsWith("Quench-preform")) { state.heatActionStateMachine.stop(); state.heatActionStateMachine.setup(true, false, "quenches"); } else if (!state.heatActionStateMachine.isIdle()) // canceled heating/cooling, stop the heating state-machine { state.heatActionStateMachine.stop(); } }); } @Subscribe public void onScriptPostFired(ScriptPostFired event) { if (event.getScriptId() == MouldHelper.DRAW_MOULD_LIST_SCRIPT || event.getScriptId() == MouldHelper.REDRAW_MOULD_LIST_SCRIPT || event.getScriptId() == MouldHelper.SELECT_MOULD_SCRIPT || event.getScriptId() == MouldHelper.RESET_MOULD_SCRIPT) { mouldHelper.selectBest(event.getScriptId()); updateMouldScore(); } } private void updateMouldScore() { state.setMouldScore(mouldHelper.getTotalScore()); // show mould score on Mould UI Title Widget mouldParent = client.getWidget(47054850); int mouldScore = state.getMouldScore(); if (mouldParent != null && mouldScore >= 0) { Widget title = Objects.requireNonNull(mouldParent.getChild(1)); // not sure why, the ":" character turns into ": ," when rendered; omitting it. title.setText("Giants' Foundry Mould Setup (Score " + mouldScore + ")"); } } // previous heat varbit value, used to filter out passive heat decay. private int previousHeat = 0; @Subscribe public void onVarbitChanged(VarbitChanged event) { if (event.getVarpId() == REPUTATION_VARBIT) { reputation = client.getVarpValue(REPUTATION_VARBIT); } // STAGE becomes 0 again after player picks up the preform if (event.getVarbitId() == VARBIT_GAME_STAGE && event.getValue() == 0) { // clear out the current and soon to be previous scores. state.setLastKnownCrucibleScore(-1); state.setMouldScore(-1); } // start the heating state-machine when the varbit updates // if heat varbit updated and the user clicked, start the state-machine if (event.getVarbitId() == VARBIT_HEAT) { // ignore passive heat decay, one heat per two ticks int delta = event.getValue() - previousHeat; // sign check: num * num > 0 == same sign if (delta != -1) { if (state.heatActionStateMachine.getActionname() != null) { // if the state-machine is idle, start it if (state.heatActionStateMachine.isIdle()) { state.heatActionStateMachine.start(state, config, previousHeat); } state.heatActionStateMachine.onTick(); } if (config.debugging()) { client.addChatMessage(ChatMessageType.GAMEMESSAGE, "", "Heat: " + event.getValue() + "" + "Delta: " + delta + " " + "Heating Ticks: " + state.heatActionStateMachine.heatingTicks + "" + " Cooling Ticks: " + state.heatActionStateMachine.coolingTicks + "" + " Remaining Ticks: " + state.heatActionStateMachine.getRemainingDuration(), ""); } } // client.addChatMessage(ChatMessageType.GAMEMESSAGE, "", "Delta: " + delta + " ", ""); previousHeat = event.getValue(); } } @Subscribe protected void onConfigChanged(ConfigChanged configChanged) { if (!EasyGiantsFoundryConfig.GROUP.equals(configChanged.getGroup())) { return; } if (EasyGiantsFoundryConfig.SOUND_ID.equals(configChanged.getKey())) { clientThread.invoke(() -> client.playSoundEffect(config.soundId())); } } @Subscribe public void onGameTick(GameTick event) { checkBonus(); } private void checkBonus() { if (!state.isEnabled() || state.getCurrentStage() == null || state.getCurrentStage().getHeat() != state.getCurrentHeat() || !BonusWidget.isActive(client)) { bonusNotified = false; return; } if (bonusNotified) { return; } state.addBonusActionReceived(); if (config.bonusNotification().isEnabled()) { notifier.notify(config.bonusNotification(), "Bonus - Click tool"); } if (config.bonusSoundNotify()) { client.playSoundEffect(config.soundId()); } bonusNotified = true; } private void updateSmithsOutfitPieces() { int pieces = 0; ItemContainer equipment = client.getItemContainer(InventoryID.WORN); if (equipment != null) { for (Item item : equipment.getItems()) { if (item != null && isSmithsOutfitPiece(item.getId())) { pieces++; } } } state.setSmithsOutfitPieces(pieces); } private boolean isSmithsOutfitPiece(int itemId) { return SMITHS_OUTFIT_IDS.contains(itemId); } @Provides EasyGiantsFoundryConfig provideConfig(ConfigManager configManager) { return configManager.getConfig(EasyGiantsFoundryConfig.class); } }