runelite-client: add mage training arena plugin

This commit is contained in:
Jasper Ketelaar
2018-06-04 22:54:29 +02:00
committed by Adam
parent 7d107445c8
commit b7b8fc85fb
22 changed files with 1993 additions and 2 deletions

View File

@@ -0,0 +1,81 @@
/*
* Copyright (c) 2018, Jasper Ketelaar <Jasper0781@gmail.com>
* 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.mta;
import net.runelite.client.config.Config;
import net.runelite.client.config.ConfigGroup;
import net.runelite.client.config.ConfigItem;
@ConfigGroup(
keyName = "mta",
name = "Mage Training Arena",
description = "Configuration for the Mage Training Arena plugin"
)
public interface MTAConfig extends Config
{
@ConfigItem(
keyName = "alchemy",
name = "Enable alchemy room",
description = "Configures whether or not the alchemy room overlay is enabled.",
position = 0
)
default boolean alchemy()
{
return true;
}
@ConfigItem(
keyName = "graveyard",
name = "Enable graveyard room",
description = "Configures whether or not the graveyard room overlay is enabled.",
position = 1
)
default boolean graveyard()
{
return true;
}
@ConfigItem(
keyName = "telekinetic",
name = "Enable telekinetic room",
description = "Configures whether or not the telekinetic room overlay is enabled.",
position = 2
)
default boolean telekinetic()
{
return true;
}
@ConfigItem(
keyName = "enchantment",
name = "Enable enchantment room",
description = "Configures whether or not the enchantment room overlay is enabled.",
position = 3
)
default boolean enchantment()
{
return true;
}
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright (c) 2018, Jasper Ketelaar <Jasper0781@gmail.com>
* 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.mta;
import java.awt.Dimension;
import java.awt.Graphics2D;
import javax.inject.Inject;
import net.runelite.client.ui.FontManager;
import net.runelite.client.ui.overlay.Overlay;
import net.runelite.client.ui.overlay.OverlayLayer;
import net.runelite.client.ui.overlay.OverlayPosition;
public class MTAInventoryOverlay extends Overlay
{
private final MTAPlugin plugin;
@Inject
public MTAInventoryOverlay(MTAPlugin plugin)
{
this.plugin = plugin;
setPosition(OverlayPosition.DYNAMIC);
setLayer(OverlayLayer.ABOVE_WIDGETS);
}
@Override
public Dimension render(Graphics2D graphics)
{
for (MTARoom room : plugin.getRooms())
{
if (room.inside())
{
graphics.setFont(FontManager.getRunescapeBoldFont());
room.over(graphics);
}
}
return null;
}
}

View File

@@ -0,0 +1,102 @@
/*
* Copyright (c) 2018, Jasper Ketelaar <Jasper0781@gmail.com>
* 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.mta;
import com.google.common.eventbus.EventBus;
import com.google.inject.Provides;
import javax.inject.Inject;
import lombok.AccessLevel;
import lombok.Getter;
import net.runelite.api.Client;
import net.runelite.client.config.ConfigManager;
import net.runelite.client.plugins.Plugin;
import net.runelite.client.plugins.PluginDescriptor;
import net.runelite.client.plugins.mta.alchemy.AlchemyRoom;
import net.runelite.client.plugins.mta.enchantment.EnchantmentRoom;
import net.runelite.client.plugins.mta.graveyard.GraveyardRoom;
import net.runelite.client.plugins.mta.telekinetic.TelekineticRoom;
import net.runelite.client.ui.overlay.OverlayManager;
@PluginDescriptor(name = "Mage Training Arena")
public class MTAPlugin extends Plugin
{
@Inject
private Client client;
@Inject
private OverlayManager overlayManager;
@Inject
private AlchemyRoom alchemyRoom;
@Inject
private GraveyardRoom graveyardRoom;
@Inject
private TelekineticRoom telekineticRoom;
@Inject
private EnchantmentRoom enchantmentRoom;
@Inject
private EventBus eventBus;
@Inject
private MTASceneOverlay sceneOverlay;
@Inject
private MTAInventoryOverlay inventoryOverlay;
@Getter(AccessLevel.PROTECTED)
private MTARoom[] rooms;
@Provides
public MTAConfig getConfig(ConfigManager manager)
{
return manager.getConfig(MTAConfig.class);
}
@Override
public void startUp()
{
overlayManager.add(sceneOverlay);
overlayManager.add(inventoryOverlay);
this.rooms = new MTARoom[]{alchemyRoom, graveyardRoom, telekineticRoom, enchantmentRoom};
for (MTARoom room : rooms)
{
eventBus.register(room);
}
}
@Override
public void shutDown()
{
overlayManager.remove(sceneOverlay);
overlayManager.remove(inventoryOverlay);
for (MTARoom room : rooms)
{
eventBus.unregister(room);
}
}
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright (c) 2018, Jasper Ketelaar <Jasper0781@gmail.com>
* 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.mta;
import java.awt.Graphics2D;
import javax.inject.Inject;
import lombok.AccessLevel;
import lombok.Getter;
public abstract class MTARoom
{
@Getter(AccessLevel.PROTECTED)
protected final MTAConfig config;
@Inject
protected MTARoom(MTAConfig config)
{
this.config = config;
}
public abstract boolean inside();
public void under(Graphics2D graphics2D)
{
}
public void over(Graphics2D graphics2D)
{
}
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright (c) 2018, Jasper Ketelaar <Jasper0781@gmail.com>
* 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.mta;
import java.awt.Dimension;
import java.awt.Graphics2D;
import javax.inject.Inject;
import net.runelite.client.ui.FontManager;
import net.runelite.client.ui.overlay.Overlay;
import net.runelite.client.ui.overlay.OverlayLayer;
import net.runelite.client.ui.overlay.OverlayPosition;
public class MTASceneOverlay extends Overlay
{
private final MTAPlugin plugin;
@Inject
public MTASceneOverlay(MTAPlugin plugin)
{
this.plugin = plugin;
setPosition(OverlayPosition.DYNAMIC);
setLayer(OverlayLayer.ABOVE_SCENE);
}
@Override
public Dimension render(Graphics2D graphics)
{
for (MTARoom room : plugin.getRooms())
{
if (room.inside())
{
graphics.setFont(FontManager.getRunescapeFont());
room.under(graphics);
}
}
return null;
}
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright (c) 2018, Jasper Ketelaar <Jasper0781@gmail.com>
* 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.mta.alchemy;
import lombok.Getter;
import net.runelite.api.ItemID;
public enum AlchemyItem
{
LEATHER_BOOTS("Leather Boots", ItemID.LEATHER_BOOTS_6893),
ADAMANT_KITESHIELD("Adamant Kiteshield", ItemID.ADAMANT_KITESHIELD_6894),
ADAMANT_MED_HELM("Helm", ItemID.ADAMANT_MED_HELM_6895),
EMERALD("Emerald", ItemID.EMERALD_6896),
RUNE_LONGSWORD("Rune Longsword", ItemID.RUNE_LONGSWORD_6897),
EMPTY("", -1),
POSSIBLY_EMPTY("", ItemID.CAKE_OF_GUIDANCE),
UNKNOWN("Unknown", ItemID.CAKE_OF_GUIDANCE);
@Getter
private final int id;
@Getter
private final String name;
AlchemyItem(String name, int id)
{
this.id = id;
this.name = name;
}
public static AlchemyItem find(String item)
{
for (AlchemyItem alchemyItem : values())
{
if (item.toLowerCase().contains(alchemyItem.name.toLowerCase()))
{
return alchemyItem;
}
}
return null;
}
}

View File

@@ -0,0 +1,475 @@
/*
* Copyright (c) 2018, Jasper Ketelaar <Jasper0781@gmail.com>
* Copyright (c) 2018, Adam <Adam@sigterm.info>
* 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.mta.alchemy;
import com.google.common.eventbus.Subscribe;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.util.Arrays;
import java.util.Objects;
import javax.inject.Inject;
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 static net.runelite.api.ObjectID.CUPBOARD_23678;
import static net.runelite.api.ObjectID.CUPBOARD_23679;
import static net.runelite.api.ObjectID.CUPBOARD_23680;
import static net.runelite.api.ObjectID.CUPBOARD_23681;
import static net.runelite.api.ObjectID.CUPBOARD_23682;
import static net.runelite.api.ObjectID.CUPBOARD_23683;
import static net.runelite.api.ObjectID.CUPBOARD_23684;
import static net.runelite.api.ObjectID.CUPBOARD_23685;
import static net.runelite.api.ObjectID.CUPBOARD_23686;
import static net.runelite.api.ObjectID.CUPBOARD_23687;
import static net.runelite.api.ObjectID.CUPBOARD_23688;
import static net.runelite.api.ObjectID.CUPBOARD_23689;
import static net.runelite.api.ObjectID.CUPBOARD_23690;
import static net.runelite.api.ObjectID.CUPBOARD_23691;
import static net.runelite.api.ObjectID.CUPBOARD_23692;
import static net.runelite.api.ObjectID.CUPBOARD_23693;
import net.runelite.api.Perspective;
import net.runelite.api.Player;
import net.runelite.api.Point;
import net.runelite.api.coords.WorldPoint;
import net.runelite.api.events.ChatMessage;
import net.runelite.api.events.GameObjectSpawned;
import net.runelite.api.events.GameStateChanged;
import net.runelite.api.events.GameTick;
import net.runelite.api.widgets.Widget;
import net.runelite.api.widgets.WidgetID;
import net.runelite.api.widgets.WidgetInfo;
import net.runelite.api.widgets.WidgetItem;
import net.runelite.client.game.ItemManager;
import net.runelite.client.plugins.mta.MTAConfig;
import net.runelite.client.plugins.mta.MTAPlugin;
import net.runelite.client.plugins.mta.MTARoom;
import net.runelite.client.ui.overlay.infobox.InfoBoxManager;
@Slf4j
public class AlchemyRoom extends MTARoom
{
private static final int MTA_ALCH_REGION = 13462;
private static final int IMAGE_Z_OFFSET = 150;
private static final int NUM_CUPBOARDS = 8;
private static final int INFO_START = 5;
private static final int BEST_POINTS = 30;
private static final String YOU_FOUND = "You found:";
private static final String EMPTY = "The cupboard is empty.";
private final Cupboard[] cupboards = new Cupboard[NUM_CUPBOARDS];
private final MTAPlugin plugin;
private final Client client;
private final ItemManager itemManager;
private final InfoBoxManager infoBoxManager;
private AlchemyItem best;
private Cupboard suggestion;
@Inject
private AlchemyRoom(Client client, MTAConfig config, MTAPlugin plugin, ItemManager itemManager, InfoBoxManager infoBoxManager)
{
super(config);
this.client = client;
this.plugin = plugin;
this.itemManager = itemManager;
this.infoBoxManager = infoBoxManager;
}
@Subscribe
public void onGameTick(GameTick event)
{
if (!inside() || !config.alchemy())
{
return;
}
AlchemyItem bestItem = getBest();
if (best == null || best != bestItem)
{
if (best != null)
{
infoBoxManager.removeIf(e -> e instanceof AlchemyRoomTimer);
infoBoxManager.addInfoBox(new AlchemyRoomTimer(plugin));
}
log.debug("Item change to {}!", best);
best = bestItem;
// Reset items to unknown
Arrays.stream(cupboards)
.filter(Objects::nonNull)
.forEach(e -> e.alchemyItem = AlchemyItem.UNKNOWN);
}
Cupboard newSuggestion = getSuggestion();
if (suggestion == null || newSuggestion == null || suggestion.alchemyItem != newSuggestion.alchemyItem)
{
suggestion = newSuggestion;
}
}
@Subscribe
public void onGameObjectSpawned(GameObjectSpawned event)
{
if (!inside())
{
return;
}
GameObject spawn = event.getGameObject();
int cupboardId;
switch (spawn.getId())
{
// Closed and opened versions of each
case CUPBOARD_23678:
case CUPBOARD_23679:
cupboardId = 0;
break;
case CUPBOARD_23680:
case CUPBOARD_23681:
cupboardId = 1;
break;
case CUPBOARD_23682:
case CUPBOARD_23683:
cupboardId = 2;
break;
case CUPBOARD_23684:
case CUPBOARD_23685:
cupboardId = 3;
break;
case CUPBOARD_23686:
case CUPBOARD_23687:
cupboardId = 4;
break;
case CUPBOARD_23688:
case CUPBOARD_23689:
cupboardId = 5;
break;
case CUPBOARD_23690:
case CUPBOARD_23691:
cupboardId = 6;
break;
case CUPBOARD_23692:
case CUPBOARD_23693:
cupboardId = 7;
break;
default:
return;
}
Cupboard cupboard = cupboards[cupboardId];
if (cupboard != null)
{
cupboard.gameObject = spawn;
}
else
{
cupboard = new Cupboard();
cupboard.gameObject = spawn;
cupboard.alchemyItem = AlchemyItem.UNKNOWN;
cupboards[cupboardId] = cupboard;
}
}
@Subscribe
public void onGameStateChanged(GameStateChanged gameStateChanged)
{
if (gameStateChanged.getGameState() == GameState.LOGGED_IN)
{
if (!inside())
{
reset();
}
}
}
@Subscribe
public void onChatMessage(ChatMessage wrapper)
{
if (!inside() || !config.alchemy())
{
return;
}
String message = wrapper.getMessage();
if (wrapper.getType() == ChatMessageType.SERVER)
{
if (message.contains(YOU_FOUND))
{
String item = message.replace(YOU_FOUND, "").trim();
AlchemyItem alchemyItem = AlchemyItem.find(item);
Cupboard clicked = getClicked();
if (clicked.alchemyItem != alchemyItem)
{
fill(clicked, alchemyItem);
}
}
else if (message.equals(EMPTY))
{
Cupboard clicked = getClicked();
int idx = Arrays.asList(cupboards).indexOf(clicked);
for (int i = -2; i <= 2; ++i)
{
int j = (idx + i) % 8;
if (j < 0)
{
j = 8 + j;
}
Cupboard cupboard = cupboards[j];
if (cupboard != null && cupboard.alchemyItem == AlchemyItem.UNKNOWN)
{
cupboard.alchemyItem = AlchemyItem.POSSIBLY_EMPTY;
}
}
clicked.alchemyItem = AlchemyItem.EMPTY;
}
}
}
private void reset()
{
Arrays.fill(cupboards, null);
}
@Override
public boolean inside()
{
Player player = client.getLocalPlayer();
return player != null && player.getWorldLocation().getRegionID() == MTA_ALCH_REGION
&& player.getWorldLocation().getPlane() == 2;
}
private AlchemyItem getBest()
{
for (int i = 0; i < INFO_START; i++)
{
int index = i + INFO_START;
Widget textWidget = client.getWidget(WidgetID.MTA_ALCHEMY_GROUP_ID, index);
if (textWidget == null)
{
return null;
}
String item = textWidget.getText().replace(":", "");
Widget pointsWidget = client.getWidget(WidgetID.MTA_ALCHEMY_GROUP_ID, index + INFO_START);
int points = Integer.parseInt(pointsWidget.getText());
if (points == BEST_POINTS)
{
return AlchemyItem.find(item);
}
}
return null;
}
private Cupboard getClicked()
{
Cupboard nearest = null;
double distance = Double.MAX_VALUE;
WorldPoint mine = client.getLocalPlayer().getWorldLocation();
for (Cupboard cupboard : cupboards)
{
if (cupboard == null)
{
continue;
}
double objectDistance = cupboard.gameObject.getWorldLocation().distanceTo(mine);
if (nearest == null || objectDistance < distance)
{
nearest = cupboard;
distance = objectDistance;
}
}
return nearest;
}
private void fill(Cupboard cupboard, AlchemyItem alchemyItem)
{
int idx = Arrays.asList(cupboards).indexOf(cupboard);
assert idx != -1;
int itemIdx = alchemyItem.ordinal();
log.debug("Filling cupboard {} with {}", idx, alchemyItem);
for (int i = 0; i < NUM_CUPBOARDS; ++i)
{
int cupIdx = (idx + i) % NUM_CUPBOARDS;
int itemIndex = (itemIdx + i) % NUM_CUPBOARDS;
cupboards[cupIdx].alchemyItem = itemIndex <= 4 ? AlchemyItem.values()[itemIndex] : AlchemyItem.EMPTY;
}
}
@Override
public void under(Graphics2D graphics)
{
if (!getConfig().alchemy() || best == null || !inside())
{
return;
}
boolean found = false;
for (Cupboard cupboard : cupboards)
{
if (cupboard == null)
{
continue;
}
GameObject object = cupboard.gameObject;
AlchemyItem alchemyItem = cupboard.alchemyItem;
if (alchemyItem == AlchemyItem.EMPTY)
{
continue;
}
if (alchemyItem == best)
{
client.setHintArrow(object.getWorldLocation());
found = true;
}
BufferedImage image = itemManager.getImage(alchemyItem.getId());
Point canvasLoc = Perspective.getCanvasImageLocation(client, graphics, object.getLocalLocation(), image, IMAGE_Z_OFFSET);
if (canvasLoc != null)
{
graphics.drawImage(image, canvasLoc.getX(), canvasLoc.getY(), null);
}
}
if (!found && suggestion != null)
{
client.setHintArrow(suggestion.gameObject.getWorldLocation());
}
}
private Cupboard getSuggestion()
{
// check if a cupboard has the best item in it
if (best != null)
{
for (Cupboard cupboard : cupboards)
{
if (cupboard != null && cupboard.alchemyItem == best)
{
return cupboard;
}
}
}
// otherwise find the closest cupboard which can not be empty
Cupboard nearest = null;
int distance = -1;
WorldPoint mine = client.getLocalPlayer().getWorldLocation();
for (Cupboard cupboard : cupboards)
{
if (cupboard == null || cupboard.alchemyItem == AlchemyItem.EMPTY || cupboard.alchemyItem == AlchemyItem.POSSIBLY_EMPTY)
{
continue;
}
int objectDistance = (int) cupboard.gameObject.getWorldLocation().distanceTo(mine);
if (nearest == null || objectDistance < distance)
{
nearest = cupboard;
distance = objectDistance;
}
}
return nearest;
}
@Override
public void over(Graphics2D graphics)
{
if (!inside() || !config.alchemy() || best == null)
{
return;
}
Widget inventory = client.getWidget(WidgetInfo.INVENTORY);
if (inventory.isHidden())
{
return;
}
for (WidgetItem item : inventory.getWidgetItems())
{
if (item.getId() != best.getId())
{
continue;
}
drawItem(graphics, item, Color.GREEN);
}
}
private void drawItem(Graphics2D graphics, WidgetItem item, Color border)
{
Rectangle bounds = item.getCanvasBounds();
graphics.setColor(border);
graphics.draw(bounds);
}
}

View File

@@ -0,0 +1,68 @@
/*
* Copyright (c) 2018, Jasper Ketelaar <Jasper0781@gmail.com>
* 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.mta.alchemy;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.time.temporal.ChronoUnit;
import javax.imageio.ImageIO;
import lombok.extern.slf4j.Slf4j;
import net.runelite.client.plugins.Plugin;
import net.runelite.client.ui.overlay.infobox.Timer;
@Slf4j
public class AlchemyRoomTimer extends Timer
{
private static final int RESET_PERIOD = 42;
private static BufferedImage image;
public AlchemyRoomTimer(Plugin plugin)
{
super(RESET_PERIOD, ChronoUnit.SECONDS, getResetImage(), plugin);
this.setTooltip("Time until items swap");
}
private static BufferedImage getResetImage()
{
if (image != null)
{
return image;
}
try
{
synchronized (ImageIO.class)
{
image = ImageIO.read(AlchemyRoomTimer.class.getResourceAsStream("reset.png"));
}
}
catch (IOException ex)
{
log.warn(null, ex);
}
return image;
}
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright (c) 2018, Adam <Adam@sigterm.info>
* 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.mta.alchemy;
import net.runelite.api.GameObject;
class Cupboard
{
GameObject gameObject;
AlchemyItem alchemyItem;
}

View File

@@ -0,0 +1,152 @@
/*
* Copyright (c) 2018, Jasper Ketelaar <Jasper0781@gmail.com>
* 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.mta.enchantment;
import com.google.common.eventbus.Subscribe;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.Client;
import net.runelite.api.GameState;
import net.runelite.api.Item;
import net.runelite.api.ItemID;
import net.runelite.api.ItemLayer;
import net.runelite.api.Player;
import net.runelite.api.Tile;
import net.runelite.api.coords.WorldPoint;
import net.runelite.api.events.GameStateChanged;
import net.runelite.api.events.GameTick;
import net.runelite.api.events.ItemLayerChanged;
import net.runelite.client.plugins.mta.MTAConfig;
import net.runelite.client.plugins.mta.MTARoom;
@Slf4j
public class EnchantmentRoom extends MTARoom
{
private static final int MTA_ENCHANT_REGION = 13462;
private final Client client;
private final List<WorldPoint> dragonstones = new ArrayList<>();
@Inject
private EnchantmentRoom(MTAConfig config, Client client)
{
super(config);
this.client = client;
}
@Subscribe
public void onGameStateChanged(GameStateChanged gameStateChanged)
{
if (gameStateChanged.getGameState() == GameState.LOGGED_IN)
{
if (!inside())
{
dragonstones.clear();
}
}
}
@Subscribe
public void onGameTick(GameTick event)
{
if (!inside() || !config.enchantment())
{
return;
}
WorldPoint nearest = findNearestStone();
if (nearest != null)
{
client.setHintArrow(nearest);
}
}
private WorldPoint findNearestStone()
{
WorldPoint nearest = null;
double dist = Double.MAX_VALUE;
WorldPoint local = client.getLocalPlayer().getWorldLocation();
for (WorldPoint worldPoint : dragonstones)
{
double currDist = local.distanceTo(worldPoint);
if (nearest == null || currDist < dist)
{
dist = currDist;
nearest = worldPoint;
}
}
return nearest;
}
@Subscribe
public void onItemLayerChanged(ItemLayerChanged event)
{
if (!inside())
{
return;
}
Tile changed = event.getTile();
ItemLayer itemLayer = changed.getItemLayer();
WorldPoint worldPoint = changed.getWorldLocation();
List<Item> groundItems = changed.getGroundItems();
if (groundItems == null)
{
boolean removed = dragonstones.remove(worldPoint);
if (removed)
{
log.debug("Removed dragonstone at {}", worldPoint);
}
return;
}
for (Item item : changed.getGroundItems())
{
if (item.getId() == ItemID.DRAGONSTONE_6903)
{
log.debug("Adding dragonstone at {}", worldPoint);
dragonstones.add(worldPoint);
return;
}
}
boolean removed = dragonstones.remove(worldPoint);
if (removed)
{
log.debug("Removed dragonstone at {}", worldPoint);
}
}
@Override
public boolean inside()
{
Player player = client.getLocalPlayer();
return player != null && player.getWorldLocation().getRegionID() == MTA_ENCHANT_REGION
&& player.getWorldLocation().getPlane() == 0;
}
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright (c) 2018, Jasper Ketelaar <Jasper0781@gmail.com>
* 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.mta.graveyard;
import java.awt.Color;
import java.awt.image.BufferedImage;
import net.runelite.client.plugins.Plugin;
import net.runelite.client.ui.overlay.infobox.Counter;
public class GraveyardCounter extends Counter
{
private int count;
public GraveyardCounter(BufferedImage image, Plugin plugin)
{
super(image, plugin, "0");
}
public void setCount(int count)
{
this.count = count;
this.setText(String.valueOf(count));
}
@Override
public Color getTextColor()
{
if (count >= GraveyardRoom.MIN_SCORE)
{
return Color.GREEN;
}
else if (count == 0)
{
return Color.RED;
}
else
{
return Color.ORANGE;
}
}
}

View File

@@ -0,0 +1,150 @@
/*
* Copyright (c) 2018, Jasper Ketelaar <Jasper0781@gmail.com>
* 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.mta.graveyard;
import com.google.common.eventbus.Subscribe;
import java.awt.image.BufferedImage;
import javax.inject.Inject;
import net.runelite.api.Client;
import net.runelite.api.InventoryID;
import net.runelite.api.Item;
import net.runelite.api.ItemContainer;
import static net.runelite.api.ItemID.ANIMALS_BONES;
import static net.runelite.api.ItemID.ANIMALS_BONES_6905;
import static net.runelite.api.ItemID.ANIMALS_BONES_6906;
import static net.runelite.api.ItemID.ANIMALS_BONES_6907;
import net.runelite.api.Player;
import net.runelite.api.events.GameTick;
import net.runelite.api.events.ItemContainerChanged;
import net.runelite.client.game.ItemManager;
import net.runelite.client.plugins.mta.MTAConfig;
import net.runelite.client.plugins.mta.MTAPlugin;
import net.runelite.client.plugins.mta.MTARoom;
import net.runelite.client.ui.overlay.infobox.InfoBoxManager;
public class GraveyardRoom extends MTARoom
{
private static final int MTA_GRAVEYARD_REGION = 13462;
static final int MIN_SCORE = 16;
private final Client client;
private final MTAPlugin plugin;
private final ItemManager itemManager;
private final InfoBoxManager infoBoxManager;
private int score;
private GraveyardCounter counter;
@Inject
private GraveyardRoom(MTAConfig config, Client client, MTAPlugin plugin,
ItemManager itemManager, InfoBoxManager infoBoxManager)
{
super(config);
this.client = client;
this.plugin = plugin;
this.itemManager = itemManager;
this.infoBoxManager = infoBoxManager;
}
@Override
public boolean inside()
{
Player player = client.getLocalPlayer();
return player != null && player.getWorldLocation().getRegionID() == MTA_GRAVEYARD_REGION
&& player.getWorldLocation().getPlane() == 1;
}
@Subscribe
public void onGameTick(GameTick tick)
{
if (!inside() || !config.graveyard())
{
if (this.counter != null)
{
infoBoxManager.removeIf(e -> e instanceof GraveyardCounter);
this.counter = null;
}
}
}
@Subscribe
public void itemContainerChanged(ItemContainerChanged event)
{
if (!inside())
{
return;
}
ItemContainer container = event.getItemContainer();
if (container == client.getItemContainer(InventoryID.INVENTORY))
{
this.score = score(container.getItems());
if (counter == null)
{
BufferedImage image = itemManager.getImage(ANIMALS_BONES);
counter = new GraveyardCounter(image, plugin);
infoBoxManager.addInfoBox(counter);
}
counter.setCount(score);
}
}
private int score(Item[] items)
{
int score = 0;
if (items == null)
{
return score;
}
for (Item item : items)
{
score += getPoints(item.getId());
}
return score;
}
private int getPoints(int id)
{
switch (id)
{
case ANIMALS_BONES:
return 1;
case ANIMALS_BONES_6905:
return 2;
case ANIMALS_BONES_6906:
return 3;
case ANIMALS_BONES_6907:
return 4;
default:
return 0;
}
}
}

View File

@@ -0,0 +1,73 @@
/*
* Copyright (c) 2018, Jasper Ketelaar <Jasper0781@gmail.com>
* 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.mta.telekinetic;
import net.runelite.api.coords.LocalPoint;
public enum Maze
{
MAZE_1(100, new LocalPoint(6848, 3904)),
MAZE_2(124, new LocalPoint(4928, 6848)),
MAZE_3(129, new LocalPoint(7104, 5312)),
MAZE_4(53, new LocalPoint(6208, 4928)),
MAZE_5(108, new LocalPoint(5056, 5184)),
MAZE_6(121, new LocalPoint(3648, 5440)),
MAZE_7(71, new LocalPoint(6080, 5696)),
MAZE_8(98, new LocalPoint(5952, 7360)),
MAZE_9(87, new LocalPoint(5184, 6208)),
MAZE_10(91, new LocalPoint(5440, 9024));
private final int walls;
private final LocalPoint start;
Maze(int walls, LocalPoint start)
{
this.walls = walls;
this.start = start;
}
public static Maze fromWalls(int walls)
{
for (Maze maze : values())
{
if (maze.getWalls() == walls)
{
return maze;
}
}
return null;
}
public int getWalls()
{
return walls;
}
public LocalPoint getStart()
{
return start;
}
}

View File

@@ -0,0 +1,512 @@
/*
* Copyright (c) 2018, Jasper Ketelaar <Jasper0781@gmail.com>
* 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.mta.telekinetic;
import com.google.common.eventbus.Subscribe;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.Stack;
import javax.inject.Inject;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.Client;
import net.runelite.api.GameState;
import net.runelite.api.GroundObject;
import net.runelite.api.NPC;
import net.runelite.api.NpcID;
import net.runelite.api.NullObjectID;
import net.runelite.api.Perspective;
import net.runelite.api.Projectile;
import net.runelite.api.ProjectileID;
import net.runelite.api.WallObject;
import net.runelite.api.coords.Angle;
import net.runelite.api.coords.Direction;
import net.runelite.api.coords.LocalPoint;
import net.runelite.api.coords.WorldArea;
import net.runelite.api.coords.WorldPoint;
import net.runelite.api.events.GameTick;
import net.runelite.api.events.NpcDespawned;
import net.runelite.api.events.NpcSpawned;
import net.runelite.api.queries.GroundObjectQuery;
import net.runelite.api.queries.WallObjectQuery;
import net.runelite.api.widgets.WidgetID;
import net.runelite.client.plugins.mta.MTAConfig;
import net.runelite.client.plugins.mta.MTARoom;
@Slf4j
public class TelekineticRoom extends MTARoom
{
private static final int TELEKINETIC_WALL = NullObjectID.NULL_10755;
private static final int TELEKINETIC_FINISH = NullObjectID.NULL_23672;
private final Client client;
private Stack<Direction> moves = new Stack<>();
private LocalPoint destination;
private WorldPoint location;
private Rectangle bounds;
private NPC guardian;
private Maze maze;
@Inject
private TelekineticRoom(MTAConfig config, Client client)
{
super(config);
this.client = client;
}
@Subscribe
public void onGameTick(GameTick event)
{
if (!config.telekinetic()
|| !inside()
|| client.getGameState() != GameState.LOGGED_IN)
{
maze = null;
moves.clear();
return;
}
WallObjectQuery qry = new WallObjectQuery()
.idEquals(TELEKINETIC_WALL);
WallObject[] result = qry.result(client);
int length = result.length;
if (maze == null || length != maze.getWalls())
{
bounds = getBounds(result);
maze = Maze.fromWalls(length);
client.clearHintArrow();
}
else if (guardian != null)
{
WorldPoint current;
if (guardian.getId() == NpcID.MAZE_GUARDIAN_MOVING)
{
destination = getGuardianDestination();
current = WorldPoint.fromLocal(client, destination);
}
else
{
destination = null;
current = guardian.getWorldLocation();
}
//Prevent unnecessary updating when the guardian has not moved
if (current.equals(location))
{
return;
}
log.debug("Updating guarding location {} -> {}", location, current);
location = current;
if (location.equals(finish()))
{
client.clearHintArrow();
}
else
{
for (Projectile projectile : client.getProjectiles())
{
if (projectile.getId() == ProjectileID.TELEKINETIC_SPELL)
{
return;
}
}
log.debug("Rebuilding moves due to guardian move");
this.moves = build();
}
}
else
{
client.clearHintArrow();
moves.clear();
}
}
@Subscribe
public void onNpcSpawned(NpcSpawned event)
{
NPC npc = event.getNpc();
if (npc.getId() == NpcID.MAZE_GUARDIAN)
{
guardian = npc;
}
}
@Subscribe
public void onNpcDespawned(NpcDespawned event)
{
NPC npc = event.getNpc();
if (npc == guardian)
{
guardian = null;
}
}
@Override
public boolean inside()
{
return client.getWidget(WidgetID.MTA_TELEKINETIC_GROUP_ID, 0) != null;
}
@Override
public void under(Graphics2D graphics2D)
{
if (inside() && maze != null && guardian != null)
{
if (destination != null)
{
graphics2D.setColor(Color.ORANGE);
renderLocalPoint(graphics2D, destination);
}
if (!moves.isEmpty())
{
if (moves.peek() == getPosition())
{
graphics2D.setColor(Color.GREEN);
}
else
{
graphics2D.setColor(Color.RED);
}
Polygon tile = Perspective.getCanvasTilePoly(client, guardian.getLocalLocation());
if (tile != null)
{
graphics2D.drawPolygon(tile);
}
WorldPoint optimal = optimal();
if (optimal != null)
{
client.setHintArrow(optimal);
renderWorldPoint(graphics2D, optimal);
}
}
}
}
private WorldPoint optimal()
{
WorldPoint current = client.getLocalPlayer().getWorldLocation();
Direction next = moves.pop();
WorldArea areaNext = getIndicatorLine(next);
WorldPoint nearestNext = nearest(areaNext, current);
if (moves.isEmpty())
{
moves.push(next);
return nearestNext;
}
Direction after = moves.peek();
moves.push(next);
WorldArea areaAfter = getIndicatorLine(after);
WorldPoint nearestAfter = nearest(areaAfter, nearestNext);
return nearest(areaNext, nearestAfter);
}
private static int manhattan(WorldPoint point1, WorldPoint point2)
{
return Math.abs(point1.getX() - point2.getX()) + Math.abs(point2.getY() - point1.getY());
}
private WorldPoint nearest(WorldArea area, WorldPoint worldPoint)
{
int dist = Integer.MAX_VALUE;
WorldPoint nearest = null;
for (WorldPoint areaPoint : area.toWorldPointList())
{
int currDist = manhattan(areaPoint, worldPoint);
if (nearest == null || dist > currDist)
{
nearest = areaPoint;
dist = currDist;
}
}
return nearest;
}
private void renderWorldPoint(Graphics2D graphics, WorldPoint worldPoint)
{
renderLocalPoint(graphics, LocalPoint.fromWorld(client, worldPoint));
}
private void renderLocalPoint(Graphics2D graphics, LocalPoint local)
{
if (local != null)
{
Polygon canvasTilePoly = Perspective.getCanvasTilePoly(client, local);
if (canvasTilePoly != null)
{
graphics.drawPolygon(canvasTilePoly);
}
}
}
private Stack<Direction> build()
{
if (guardian.getId() == NpcID.MAZE_GUARDIAN_MOVING)
{
WorldPoint converted = WorldPoint.fromLocal(client, getGuardianDestination());
return build(converted);
}
else
{
return build(guardian.getWorldLocation());
}
}
private LocalPoint getGuardianDestination()
{
Angle angle = new Angle(guardian.getOrientation());
Direction facing = angle.getNearestDirection();
return neighbour(guardian.getLocalLocation(), facing);
}
private Stack<Direction> build(WorldPoint start)
{
LocalPoint finish = finish();
Queue<WorldPoint> visit = new LinkedList<>();
Set<WorldPoint> closed = new HashSet<>();
Map<WorldPoint, Integer> scores = new HashMap<>();
Map<WorldPoint, WorldPoint> edges = new HashMap<>();
scores.put(start, 0);
visit.add(start);
while (!visit.isEmpty())
{
WorldPoint next = visit.poll();
closed.add(next);
LocalPoint localNext = LocalPoint.fromWorld(client, next);
LocalPoint[] neighbours = neighbours(localNext);
for (LocalPoint neighbour : neighbours)
{
if (neighbour == null)
{
continue;
}
WorldPoint nghbWorld = WorldPoint.fromLocal(client, neighbour);
if (!nghbWorld.equals(next)
&& !closed.contains(nghbWorld))
{
int score = scores.get(next) + 1;
if (!scores.containsKey(nghbWorld) || scores.get(nghbWorld) > score)
{
scores.put(nghbWorld, score);
edges.put(nghbWorld, next);
visit.add(nghbWorld);
}
}
}
}
return build(edges, WorldPoint.fromLocal(client, finish));
}
private Stack<Direction> build(Map<WorldPoint, WorldPoint> edges, WorldPoint finish)
{
Stack<Direction> path = new Stack<>();
WorldPoint current = finish;
while (edges.containsKey(current))
{
WorldPoint next = edges.get(current);
if (next.getX() > current.getX())
{
path.add(Direction.WEST);
}
else if (next.getX() < current.getX())
{
path.add(Direction.EAST);
}
else if (next.getY() > current.getY())
{
path.add(Direction.SOUTH);
}
else
{
path.add(Direction.NORTH);
}
current = next;
}
return path;
}
private LocalPoint[] neighbours(LocalPoint point)
{
return new LocalPoint[]
{
neighbour(point, Direction.NORTH), neighbour(point, Direction.SOUTH),
neighbour(point, Direction.EAST), neighbour(point, Direction.WEST)
};
}
private LocalPoint neighbour(LocalPoint point, Direction direction)
{
WorldPoint worldPoint = WorldPoint.fromLocal(client, point);
WorldArea area = new WorldArea(worldPoint, 1, 1);
int dx, dy;
switch (direction)
{
case NORTH:
dx = 0;
dy = 1;
break;
case SOUTH:
dx = 0;
dy = -1;
break;
case EAST:
dx = 1;
dy = 0;
break;
case WEST:
dx = -1;
dy = 0;
break;
default:
throw new IllegalStateException();
}
while (area.canTravelInDirection(client, dx, dy))
{
worldPoint = area.toWorldPoint()
.dx(dx)
.dy(dy);
area = new WorldArea(worldPoint, 1, 1);
}
return LocalPoint.fromWorld(client, worldPoint);
}
private LocalPoint finish()
{
GroundObjectQuery qry = new GroundObjectQuery()
.idEquals(TELEKINETIC_FINISH);
GroundObject[] result = qry.result(client);
if (result.length > 0)
{
return result[0].getLocalLocation();
}
return null;
}
private Rectangle getBounds(WallObject[] walls)
{
int minX = Integer.MAX_VALUE;
int minY = Integer.MAX_VALUE;
int maxX = Integer.MIN_VALUE;
int maxY = Integer.MIN_VALUE;
for (WallObject wall : walls)
{
WorldPoint point = wall.getWorldLocation();
minX = Math.min(minX, point.getX());
minY = Math.min(minY, point.getY());
maxX = Math.max(maxX, point.getX());
maxY = Math.max(maxY, point.getY());
}
return new Rectangle(minX, minY, maxX - minX, maxY - minY);
}
private Direction getPosition()
{
WorldPoint mine = client.getLocalPlayer().getWorldLocation();
if (mine.getY() >= bounds.getMaxY() && mine.getX() < bounds.getMaxX() && mine.getX() > bounds.getX())
{
return Direction.NORTH;
}
else if (mine.getY() <= bounds.getY() && mine.getX() < bounds.getMaxX() && mine.getX() > bounds.getX())
{
return Direction.SOUTH;
}
else if (mine.getX() >= bounds.getMaxX() && mine.getY() < bounds.getMaxY() && mine.getY() > bounds.getY())
{
return Direction.EAST;
}
else if (mine.getX() <= bounds.getX() && mine.getY() < bounds.getMaxY() && mine.getY() > bounds.getY())
{
return Direction.WEST;
}
return null;
}
private WorldArea getIndicatorLine(Direction direction)
{
switch (direction)
{
case NORTH:
return new WorldArea(bounds.x + 1, (int) bounds.getMaxY(), bounds.width - 1, 1, 0);
case SOUTH:
return new WorldArea(bounds.x + 1, bounds.y, bounds.width - 1, 1, 0);
case WEST:
return new WorldArea(bounds.x, bounds.y + 1, 1, bounds.height - 1, 0);
case EAST:
return new WorldArea((int) bounds.getMaxX(), bounds.y + 1, 1, bounds.height - 1, 0);
}
return null;
}
}