nextattack
This commit is contained in:
@@ -24,7 +24,11 @@
|
||||
*/
|
||||
package net.runelite.client.game;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
@@ -42,12 +46,14 @@ public class NPCManager
|
||||
{
|
||||
private final OkHttpClient okHttpClient;
|
||||
private Map<Integer, NpcInfo> npcMap = Collections.emptyMap();
|
||||
private ImmutableMap<Integer, NPCStats> statsMap;
|
||||
|
||||
@Inject
|
||||
private NPCManager(OkHttpClient okHttpClient, ScheduledExecutorService scheduledExecutorService)
|
||||
{
|
||||
this.okHttpClient = okHttpClient;
|
||||
scheduledExecutorService.execute(this::loadNpcs);
|
||||
loadStats();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -74,4 +80,45 @@ public class NPCManager
|
||||
log.warn("error loading npc stats", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void loadStats()
|
||||
{
|
||||
try (JsonReader reader = new JsonReader(new InputStreamReader(NPCManager.class.getResourceAsStream("/npc_stats.json"), StandardCharsets.UTF_8)))
|
||||
{
|
||||
ImmutableMap.Builder<Integer, NPCStats> builder = ImmutableMap.builderWithExpectedSize(2821);
|
||||
reader.beginObject();
|
||||
|
||||
while (reader.hasNext())
|
||||
{
|
||||
builder.put(
|
||||
Integer.parseInt(reader.nextName()),
|
||||
NPCStats.NPC_STATS_TYPE_ADAPTER.read(reader)
|
||||
);
|
||||
}
|
||||
|
||||
reader.endObject();
|
||||
statsMap = builder.build();
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the attack speed for target NPC ID.
|
||||
*
|
||||
* @param npcId NPC id
|
||||
* @return attack speed in game ticks for NPC ID.
|
||||
*/
|
||||
public int getAttackSpeed(final int npcId)
|
||||
{
|
||||
final NPCStats s = statsMap.get(npcId);
|
||||
if (s == null || s.getAttackSpeed() == -1)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
return s.getAttackSpeed();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,203 @@
|
||||
/*
|
||||
* Copyright (c) 2019, TheStonedTurtle <https://github.com/TheStonedTurtle>
|
||||
* 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.game;
|
||||
|
||||
import com.google.gson.TypeAdapter;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
import java.io.IOException;
|
||||
import lombok.Builder;
|
||||
import lombok.Value;
|
||||
|
||||
@Value
|
||||
@Builder(builderClassName = "Builder")
|
||||
public class NPCStats
|
||||
{
|
||||
private final String name;
|
||||
|
||||
private final int hitpoints;
|
||||
private final int combatLevel;
|
||||
private final int slayerLevel;
|
||||
private final int attackSpeed;
|
||||
|
||||
private final int attackLevel;
|
||||
private final int strengthLevel;
|
||||
private final int defenceLevel;
|
||||
private final int rangeLevel;
|
||||
private final int magicLevel;
|
||||
|
||||
private final int stab;
|
||||
private final int slash;
|
||||
private final int crush;
|
||||
private final int range;
|
||||
private final int magic;
|
||||
|
||||
private final int stabDef;
|
||||
private final int slashDef;
|
||||
private final int crushDef;
|
||||
private final int rangeDef;
|
||||
private final int magicDef;
|
||||
|
||||
private final int bonusAttack;
|
||||
private final int bonusStrength;
|
||||
private final int bonusRangeStrength;
|
||||
private final int bonusMagicDamage;
|
||||
|
||||
private final boolean poisonImmune;
|
||||
private final boolean venomImmune;
|
||||
|
||||
private final boolean dragon;
|
||||
private final boolean demon;
|
||||
private final boolean undead;
|
||||
|
||||
/**
|
||||
* Based off the formula found here: http://services.runescape.com/m=forum/c=PLuJ4cy6gtA/forums.ws?317,318,712,65587452,209,337584542#209
|
||||
*
|
||||
* @return bonus XP modifier
|
||||
*/
|
||||
public double calculateXpModifier()
|
||||
{
|
||||
final double averageLevel = Math.floor((attackLevel + strengthLevel + defenceLevel + hitpoints) / 4);
|
||||
final double averageDefBonus = Math.floor((stabDef + slashDef + crushDef) / 3);
|
||||
|
||||
return (1 + Math.floor(averageLevel * (averageDefBonus + bonusStrength + bonusAttack) / 5120) / 40);
|
||||
}
|
||||
|
||||
// Because this class is here we can't add the TypeAdapter to gson (easily)
|
||||
// doesn't mean we can't use one to do it a bit quicker
|
||||
public static final TypeAdapter<NPCStats> NPC_STATS_TYPE_ADAPTER = new TypeAdapter<>()
|
||||
{
|
||||
@Override
|
||||
public void write(JsonWriter out, NPCStats value)
|
||||
{
|
||||
throw new UnsupportedOperationException("Not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public NPCStats read(JsonReader in) throws IOException
|
||||
{
|
||||
in.beginObject();
|
||||
NPCStats.Builder builder = NPCStats.builder();
|
||||
|
||||
// Name is the only one that's guaranteed
|
||||
in.skipValue();
|
||||
builder.name(in.nextString());
|
||||
|
||||
while (in.hasNext())
|
||||
{
|
||||
switch (in.nextName())
|
||||
{
|
||||
case "hitpoints":
|
||||
builder.hitpoints(in.nextInt());
|
||||
break;
|
||||
case "combatLevel":
|
||||
builder.combatLevel(in.nextInt());
|
||||
break;
|
||||
case "slayerLevel":
|
||||
builder.slayerLevel(in.nextInt());
|
||||
break;
|
||||
case "attackSpeed":
|
||||
builder.attackSpeed(in.nextInt());
|
||||
break;
|
||||
case "attackLevel":
|
||||
builder.attackLevel(in.nextInt());
|
||||
break;
|
||||
case "strengthLevel":
|
||||
builder.strengthLevel(in.nextInt());
|
||||
break;
|
||||
case "defenceLevel":
|
||||
builder.defenceLevel(in.nextInt());
|
||||
break;
|
||||
case "rangeLevel":
|
||||
builder.rangeLevel(in.nextInt());
|
||||
break;
|
||||
case "magicLevel":
|
||||
builder.magicLevel(in.nextInt());
|
||||
break;
|
||||
case "stab":
|
||||
builder.stab(in.nextInt());
|
||||
break;
|
||||
case "slash":
|
||||
builder.slash(in.nextInt());
|
||||
break;
|
||||
case "crush":
|
||||
builder.crush(in.nextInt());
|
||||
break;
|
||||
case "range":
|
||||
builder.range(in.nextInt());
|
||||
break;
|
||||
case "magic":
|
||||
builder.magic(in.nextInt());
|
||||
break;
|
||||
case "stabDef":
|
||||
builder.stabDef(in.nextInt());
|
||||
break;
|
||||
case "slashDef":
|
||||
builder.slashDef(in.nextInt());
|
||||
break;
|
||||
case "crushDef":
|
||||
builder.crushDef(in.nextInt());
|
||||
break;
|
||||
case "rangeDef":
|
||||
builder.rangeDef(in.nextInt());
|
||||
break;
|
||||
case "magicDef":
|
||||
builder.magicDef(in.nextInt());
|
||||
break;
|
||||
case "bonusAttack":
|
||||
builder.bonusAttack(in.nextInt());
|
||||
break;
|
||||
case "bonusStrength":
|
||||
builder.bonusStrength(in.nextInt());
|
||||
break;
|
||||
case "bonusRangeStrength":
|
||||
builder.bonusRangeStrength(in.nextInt());
|
||||
break;
|
||||
case "bonusMagicDamage":
|
||||
builder.bonusMagicDamage(in.nextInt());
|
||||
break;
|
||||
case "poisonImmune":
|
||||
builder.poisonImmune(in.nextBoolean());
|
||||
break;
|
||||
case "venomImmune":
|
||||
builder.venomImmune(in.nextBoolean());
|
||||
break;
|
||||
case "dragon":
|
||||
builder.dragon(in.nextBoolean());
|
||||
break;
|
||||
case "demon":
|
||||
builder.demon(in.nextBoolean());
|
||||
break;
|
||||
case "undead":
|
||||
builder.undead(in.nextBoolean());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
in.endObject();
|
||||
return builder.build();
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -165,10 +165,16 @@ class PluginListPanel extends PluginPanel
|
||||
setLayout(new BorderLayout());
|
||||
setBackground(ColorScheme.DARK_GRAY_COLOR);
|
||||
|
||||
JButton externalPluginOPRSButton = new JButton("OpenOSRS Hub");
|
||||
externalPluginOPRSButton.setBorder(new EmptyBorder(5, 5, 5, 5));
|
||||
externalPluginOPRSButton.setLayout(new BorderLayout(0, BORDER_OFFSET));
|
||||
externalPluginOPRSButton.addActionListener(l -> muxer.pushState(pluginHubPanelProvider.get()));
|
||||
|
||||
JPanel topPanel = new JPanel();
|
||||
topPanel.setBorder(new EmptyBorder(10, 10, 10, 10));
|
||||
topPanel.setLayout(new BorderLayout(0, BORDER_OFFSET));
|
||||
topPanel.add(searchBar, BorderLayout.CENTER);
|
||||
topPanel.add(externalPluginOPRSButton, BorderLayout.SOUTH);
|
||||
add(topPanel, BorderLayout.NORTH);
|
||||
|
||||
mainPanel = new FixedWidthPanel();
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright (c) 2019, GeChallengeM <https://github.com/GeChallengeM>
|
||||
* 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.nextattack;
|
||||
|
||||
import java.awt.Color;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import net.runelite.api.Actor;
|
||||
import net.runelite.api.NPC;
|
||||
import net.runelite.api.coords.WorldArea;
|
||||
|
||||
@Getter(AccessLevel.PACKAGE)
|
||||
class MemorizedNPC
|
||||
{
|
||||
private NPC npc;
|
||||
private int npcIndex;
|
||||
private String npcName;
|
||||
private int attackSpeed;
|
||||
@Setter(AccessLevel.PACKAGE)
|
||||
private int combatTimerEnd;
|
||||
@Setter(AccessLevel.PACKAGE)
|
||||
private int timeLeft;
|
||||
@Setter(AccessLevel.PACKAGE)
|
||||
private int flinchTimerEnd;
|
||||
@Setter(AccessLevel.PACKAGE)
|
||||
private Status status;
|
||||
@Setter(AccessLevel.PACKAGE)
|
||||
private WorldArea lastnpcarea;
|
||||
@Setter(AccessLevel.PACKAGE)
|
||||
private Actor lastinteracted;
|
||||
|
||||
MemorizedNPC(final NPC npc, final int attackSpeed, final WorldArea worldArea)
|
||||
{
|
||||
this.npc = npc;
|
||||
this.npcIndex = npc.getIndex();
|
||||
this.npcName = npc.getName();
|
||||
this.attackSpeed = attackSpeed;
|
||||
this.combatTimerEnd = -1;
|
||||
this.flinchTimerEnd = -1;
|
||||
this.timeLeft = 0;
|
||||
this.status = Status.OUT_OF_COMBAT;
|
||||
this.lastnpcarea = worldArea;
|
||||
this.lastinteracted = null;
|
||||
}
|
||||
|
||||
@Getter(AccessLevel.PACKAGE)
|
||||
@AllArgsConstructor
|
||||
enum Status
|
||||
{
|
||||
FLINCHING("Flinching", Color.GREEN),
|
||||
IN_COMBAT_DELAY("In Combat Delay", Color.ORANGE),
|
||||
IN_COMBAT("In Combat", Color.RED),
|
||||
OUT_OF_COMBAT("Out of Combat", Color.BLUE);
|
||||
|
||||
private String name;
|
||||
private Color color;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright (c) 2019, GeChallengeM <https://github.com/GeChallengeM>
|
||||
* 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.nextattack;
|
||||
|
||||
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 net.runelite.client.config.Range;
|
||||
|
||||
@ConfigGroup("nextattack")
|
||||
public interface NextAttackConfig extends Config
|
||||
{
|
||||
@ConfigSection(
|
||||
name = "Attack range",
|
||||
description = "",
|
||||
position = 1
|
||||
)
|
||||
String rangeTitle = "Attack range";
|
||||
|
||||
@Range(
|
||||
min = 1
|
||||
)
|
||||
@ConfigItem(
|
||||
keyName = "AttackRange",
|
||||
name = "NPC attack range",
|
||||
description = "The attack range of the NPC.",
|
||||
position = 2,
|
||||
section = rangeTitle
|
||||
)
|
||||
default int getRange()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
@ConfigSection(
|
||||
name = "Attack speed",
|
||||
description = "",
|
||||
position = 3
|
||||
)
|
||||
String speedTitle = "Attack speed";
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "CustomAttSpeedEnabled",
|
||||
name = "Use custom speed",
|
||||
description = "Use this if the timer is wrong.",
|
||||
position = 4,
|
||||
section = speedTitle
|
||||
)
|
||||
default boolean isCustomAttSpeed()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Range(
|
||||
min = 1
|
||||
)
|
||||
@ConfigItem(
|
||||
keyName = "CustomAttSpeed",
|
||||
name = "Custom NPC att speed",
|
||||
description = "The attack speed of the NPC (amount of ticks between their attacks).",
|
||||
position = 5,
|
||||
section = speedTitle
|
||||
)
|
||||
default int getCustomAttSpeed()
|
||||
{
|
||||
return 4;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright (c) 2018, GeChallengeM <https://github.com/GeChallengeM>
|
||||
* 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.nextattack;
|
||||
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Graphics2D;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import net.runelite.api.Client;
|
||||
import net.runelite.api.Point;
|
||||
import net.runelite.client.ui.overlay.Overlay;
|
||||
import net.runelite.client.ui.overlay.OverlayLayer;
|
||||
import net.runelite.client.ui.overlay.OverlayPosition;
|
||||
import net.runelite.client.ui.overlay.OverlayUtil;
|
||||
|
||||
@Singleton
|
||||
public class NextAttackOverlay extends Overlay
|
||||
{
|
||||
private final Client client;
|
||||
private final NextAttackPlugin plugin;
|
||||
|
||||
@Inject
|
||||
NextAttackOverlay(final Client client, final NextAttackPlugin plugin)
|
||||
{
|
||||
this.client = client;
|
||||
this.plugin = plugin;
|
||||
setPosition(OverlayPosition.DYNAMIC);
|
||||
setLayer(OverlayLayer.ABOVE_SCENE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dimension render(Graphics2D graphics)
|
||||
{
|
||||
for (MemorizedNPC npc : plugin.getMemorizedNPCs())
|
||||
{
|
||||
if (npc.getNpc().getInteracting() == client.getLocalPlayer() || client.getLocalPlayer().getInteracting() == npc.getNpc())
|
||||
{
|
||||
switch (npc.getStatus())
|
||||
{
|
||||
case FLINCHING:
|
||||
npc.setTimeLeft(Math.max(0, npc.getFlinchTimerEnd() - client.getTickCount()));
|
||||
break;
|
||||
case IN_COMBAT_DELAY:
|
||||
npc.setTimeLeft(Math.max(0, npc.getCombatTimerEnd() - client.getTickCount() - 7));
|
||||
break;
|
||||
case IN_COMBAT:
|
||||
npc.setTimeLeft(Math.max(0, npc.getCombatTimerEnd() - client.getTickCount()));
|
||||
break;
|
||||
case OUT_OF_COMBAT:
|
||||
default:
|
||||
npc.setTimeLeft(0);
|
||||
break;
|
||||
}
|
||||
|
||||
Point textLocation = npc.getNpc().getCanvasTextLocation(graphics, Integer.toString(npc.getTimeLeft()), npc.getNpc().getLogicalHeight() + 40);
|
||||
|
||||
if (textLocation != null)
|
||||
{
|
||||
OverlayUtil.renderTextLocation(graphics, textLocation, Integer.toString(npc.getTimeLeft()), npc.getStatus().getColor());
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,266 @@
|
||||
/*
|
||||
* Copyright (c) 2019, GeChallengeM <https://github.com/GeChallengeM>
|
||||
* 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.nextattack;
|
||||
|
||||
import com.google.inject.Provides;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import javax.inject.Inject;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import net.runelite.api.Client;
|
||||
import net.runelite.api.GameState;
|
||||
import net.runelite.api.GraphicID;
|
||||
import net.runelite.api.Hitsplat;
|
||||
import net.runelite.api.NPC;
|
||||
import net.runelite.api.coords.WorldArea;
|
||||
import net.runelite.api.events.GameStateChanged;
|
||||
import net.runelite.api.events.GameTick;
|
||||
import net.runelite.api.events.GraphicChanged;
|
||||
import net.runelite.api.events.HitsplatApplied;
|
||||
import net.runelite.api.events.NpcDespawned;
|
||||
import net.runelite.api.events.NpcSpawned;
|
||||
import net.runelite.client.config.ConfigManager;
|
||||
import net.runelite.client.eventbus.Subscribe;
|
||||
import net.runelite.client.game.NPCManager;
|
||||
import net.runelite.client.plugins.Plugin;
|
||||
import net.runelite.client.plugins.PluginDescriptor;
|
||||
import net.runelite.client.ui.overlay.OverlayManager;
|
||||
|
||||
@PluginDescriptor(
|
||||
name = "Next Attack Timer",
|
||||
enabledByDefault = false,
|
||||
description = "Adds a timer on NPC's for their projected next attack",
|
||||
tags = {"openosrs", "flinch", "npc"}
|
||||
)
|
||||
public class NextAttackPlugin extends Plugin
|
||||
{
|
||||
@Inject
|
||||
private Client client;
|
||||
|
||||
@Inject
|
||||
private OverlayManager overlayManager;
|
||||
|
||||
@Inject
|
||||
private NPCManager npcManager;
|
||||
|
||||
@Inject
|
||||
private NextAttackConfig config;
|
||||
|
||||
@Inject
|
||||
private NextAttackOverlay npcStatusOverlay;
|
||||
|
||||
@Getter(AccessLevel.PACKAGE)
|
||||
private final Set<MemorizedNPC> memorizedNPCs = new HashSet<>();
|
||||
|
||||
private WorldArea lastPlayerLocation;
|
||||
|
||||
@Provides
|
||||
NextAttackConfig provideConfig(ConfigManager configManager)
|
||||
{
|
||||
return configManager.getConfig(NextAttackConfig.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startUp()
|
||||
{
|
||||
overlayManager.add(npcStatusOverlay);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void shutDown()
|
||||
{
|
||||
overlayManager.remove(npcStatusOverlay);
|
||||
memorizedNPCs.clear();
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
private void onNpcSpawned(NpcSpawned npcSpawned)
|
||||
{
|
||||
final NPC npc = npcSpawned.getNpc();
|
||||
final String npcName = npc.getName();
|
||||
|
||||
if (npcName == null || !Arrays.asList(npc.getComposition().getActions()).contains("Attack"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
int AttackSpeed = npcManager.getAttackSpeed(npc.getId());
|
||||
if (AttackSpeed == 0)
|
||||
{
|
||||
AttackSpeed = 4;
|
||||
}
|
||||
memorizedNPCs.add(new MemorizedNPC(npc, AttackSpeed, npc.getWorldArea()));
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
private void onNpcDespawned(NpcDespawned npcDespawned)
|
||||
{
|
||||
final NPC npc = npcDespawned.getNpc();
|
||||
memorizedNPCs.removeIf(c -> c.getNpc() == npc);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
private void onGameStateChanged(GameStateChanged event)
|
||||
{
|
||||
if (event.getGameState() == GameState.LOGIN_SCREEN ||
|
||||
event.getGameState() == GameState.HOPPING)
|
||||
{
|
||||
memorizedNPCs.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
private void onHitsplatApplied(HitsplatApplied event)
|
||||
{
|
||||
if (event.getActor().getInteracting() != client.getLocalPlayer())
|
||||
{
|
||||
return;
|
||||
}
|
||||
final Hitsplat hitsplat = event.getHitsplat();
|
||||
if ((hitsplat.getHitsplatType() == Hitsplat.HitsplatType.DAMAGE_ME || hitsplat.getHitsplatType() == Hitsplat.HitsplatType.BLOCK_ME) && event.getActor() instanceof NPC)
|
||||
{
|
||||
for (MemorizedNPC mn : memorizedNPCs)
|
||||
{
|
||||
if (mn.getNpcIndex() != ((NPC) event.getActor()).getIndex())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (mn.getStatus() == MemorizedNPC.Status.OUT_OF_COMBAT || (mn.getStatus() == MemorizedNPC.Status.IN_COMBAT && mn.getCombatTimerEnd() - client.getTickCount() < 1) || mn.getLastinteracted() == null)
|
||||
{
|
||||
mn.setStatus(MemorizedNPC.Status.FLINCHING);
|
||||
mn.setCombatTimerEnd(-1);
|
||||
if (config.isCustomAttSpeed())
|
||||
{
|
||||
mn.setFlinchTimerEnd(client.getTickCount() + config.getCustomAttSpeed() / 2 + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
mn.setFlinchTimerEnd(client.getTickCount() + mn.getAttackSpeed() / 2 + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
private void onGraphicChanged(GraphicChanged event)
|
||||
{
|
||||
if ((event.getActor().getGraphic() == GraphicID.SPLASH) && event.getActor() instanceof NPC)
|
||||
{
|
||||
for (MemorizedNPC mn : memorizedNPCs)
|
||||
{
|
||||
if (mn.getNpcIndex() != ((NPC) event.getActor()).getIndex())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (mn.getStatus() == MemorizedNPC.Status.OUT_OF_COMBAT || (mn.getStatus() == MemorizedNPC.Status.IN_COMBAT && mn.getCombatTimerEnd() - client.getTickCount() < 2) || event.getActor().getInteracting() == null)
|
||||
{
|
||||
mn.setStatus(MemorizedNPC.Status.FLINCHING);
|
||||
mn.setCombatTimerEnd(-1);
|
||||
if (config.isCustomAttSpeed())
|
||||
{
|
||||
mn.setFlinchTimerEnd(client.getTickCount() + config.getCustomAttSpeed() / 2 + 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
mn.setFlinchTimerEnd(client.getTickCount() + mn.getAttackSpeed() / 2 + 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void checkStatus()
|
||||
{
|
||||
if (lastPlayerLocation == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
for (MemorizedNPC npc : memorizedNPCs)
|
||||
{
|
||||
final double CombatTime = npc.getCombatTimerEnd() - client.getTickCount();
|
||||
final double FlinchTime = npc.getFlinchTimerEnd() - client.getTickCount();
|
||||
if (npc.getNpc().getWorldArea() == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (npc.getNpc().getInteracting() == client.getLocalPlayer())
|
||||
{
|
||||
//Checks: will the NPC attack this tick?
|
||||
if (((npc.getNpc().getWorldArea().canMelee(client, lastPlayerLocation) && config.getRange() == 1) //Separate mechanics for meleerange-only NPC's because they have extra collisiondata checks (fences etc.) and can't attack diagonally
|
||||
|| (lastPlayerLocation.hasLineOfSightTo(client, npc.getNpc().getWorldArea()) && npc.getNpc().getWorldArea().distanceTo(lastPlayerLocation) <= config.getRange() && config.getRange() > 1))
|
||||
&& ((npc.getStatus() != MemorizedNPC.Status.FLINCHING && CombatTime < 9) || (npc.getStatus() == MemorizedNPC.Status.FLINCHING && FlinchTime < 2))
|
||||
&& npc.getNpc().getAnimation() != -1 //Failsafe, attacking NPC's always have an animation.
|
||||
&& !(npc.getLastnpcarea().distanceTo(lastPlayerLocation) == 0 && npc.getLastnpcarea() != npc.getNpc().getWorldArea())) //Weird mechanic: NPC's can't attack on the tick they do a random move
|
||||
{
|
||||
npc.setStatus(MemorizedNPC.Status.IN_COMBAT_DELAY);
|
||||
npc.setLastnpcarea(npc.getNpc().getWorldArea());
|
||||
npc.setLastinteracted(npc.getNpc().getInteracting());
|
||||
if (config.isCustomAttSpeed())
|
||||
{
|
||||
npc.setCombatTimerEnd(client.getTickCount() + config.getCustomAttSpeed() + 8);
|
||||
}
|
||||
else
|
||||
{
|
||||
npc.setCombatTimerEnd(client.getTickCount() + npc.getAttackSpeed() + 8);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
switch (npc.getStatus())
|
||||
{
|
||||
case IN_COMBAT:
|
||||
if (CombatTime < 2)
|
||||
{
|
||||
npc.setStatus(MemorizedNPC.Status.OUT_OF_COMBAT);
|
||||
}
|
||||
break;
|
||||
case IN_COMBAT_DELAY:
|
||||
if (CombatTime < 9)
|
||||
{
|
||||
npc.setStatus(MemorizedNPC.Status.IN_COMBAT);
|
||||
}
|
||||
break;
|
||||
case FLINCHING:
|
||||
if (FlinchTime < 2)
|
||||
{
|
||||
npc.setStatus(MemorizedNPC.Status.IN_COMBAT);
|
||||
npc.setCombatTimerEnd(client.getTickCount() + 8);
|
||||
}
|
||||
}
|
||||
npc.setLastnpcarea(npc.getNpc().getWorldArea());
|
||||
npc.setLastinteracted(npc.getNpc().getInteracting());
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
private void onGameTick(GameTick event)
|
||||
{
|
||||
checkStatus();
|
||||
lastPlayerLocation = client.getLocalPlayer().getWorldArea();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user