Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ Ticks until next attack may be enabled over your player's head.

## Updates

## 1.2.11

* Support for Royal Titans attack cooldown speedup when killing elementals

## 1.2.8 - 1.2.10

* Fixes for Wyrmscraig (Hallowfell)
Expand Down
2 changes: 1 addition & 1 deletion runelite-plugin.properties
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
displayName=AttackTimer
author=ngraves95,Lexer747
build=standard
version=1.2.10
version=1.2.11
description=A plugin to countdown until your next attack
tags=pvm,timer,attack,combat,weapon
plugins=com.attacktimer.AttackTimerMetronomePlugin
369 changes: 169 additions & 200 deletions src/main/java/com/attacktimer/AttackTimerMetronomePlugin.java

Large diffs are not rendered by default.

11 changes: 6 additions & 5 deletions src/main/java/com/attacktimer/ClientUtils/Utils.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

/*
* Copyright (c) 2022, Nick Graves <https://github.com/ngraves95>
* Copyright (c) 2024, Lexer747 <https://github.com/Lexer747>
* Copyright (c) 2024-2026, Lexer747 <https://github.com/Lexer747>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
Expand Down Expand Up @@ -63,12 +63,13 @@ public static int getWeaponId(Client client)
}

// getLocation will return the current world point of the player accounting for instances.
public static WorldPoint getLocation(Client client)
//
// For computing tile based distances you probably don't want this and instead should use
// client.getLocalPlayer().getWorldLocation().
public static WorldPoint getLocalLocation(Client client)
{
WorldPoint location = client.getLocalPlayer().getWorldLocation();
final LocalPoint localPoint = client.getLocalPlayer().getLocalLocation();
location = WorldPoint.fromLocalInstance(client, localPoint);
return location;
return WorldPoint.fromLocalInstance(client, localPoint);
}

// returns ACCURATE for unknown weapons/styles
Expand Down
111 changes: 111 additions & 0 deletions src/main/java/com/attacktimer/Damage.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package com.attacktimer;

/*
* Copyright (c) 2026, Lexer747 <https://github.com/Lexer747>
* 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.
*/

import com.attacktimer.ClientUtils.Utils;
import com.attacktimer.VariableSpeed.State.TickCount;
import java.util.ArrayDeque;
import net.runelite.api.Skill;
import net.runelite.api.events.FakeXpDrop;
import net.runelite.api.events.StatChanged;

/**
* Damage is a helper store that from the HP exp alone can compute the predicted damage of an attack.
*
* It works by storing the queue of hp exp drops then computing the delta of those drops then the normal
* damage formula (hpExp * 3/4 == damage).
*
* TODO npc exp modifiers
*
* TODO global modifiers
*/
public class Damage
{
private static final double MODIFIER = 1;
private static final double GLOBAL_MODIFIER = 1;

private ArrayDeque<Integer> hpExpEarned = new ArrayDeque<Integer>();
private ArrayDeque<Integer> hpExpEarnedTickCount = new ArrayDeque<Integer>();

public boolean onXpDrop(StatChanged event, TickCount tc)
{
if (event.getSkill() != Skill.HITPOINTS)
{
return false;
}
hpExpEarnedTickCount.addLast(tc.get());
hpExpEarned.addLast(event.getXp());
return true;
}

public boolean onXpDrop(FakeXpDrop event, TickCount tc)
{
if (event.getSkill() != Skill.HITPOINTS)
{
return false;
}
hpExpEarnedTickCount.addLast(tc.get());
hpExpEarnedTickCount.addLast(tc.get());
// Fake exp doesn't have a delta like real xp
hpExpEarned.addLast(0);
hpExpEarned.addLast(event.getXp());
return true;
}

/**
* compute determines from the previous hp exp drops how much damage the player has dealt on this tick.
* @param tc the tick count state
* @return the amount of damage dealt this tick
*/
public int compute(TickCount tc)
{
if (hpExpEarnedTickCount.isEmpty())
{
return -1;
}
final var lastTc = hpExpEarnedTickCount.getLast();
if (!tc.isWithinNTicks(lastTc, 1))
{
// In this case the last exp tick wasn't this tick in which case we hit a 0.
return 0;
}
// https://oldschool.runescape.wiki/w/Combat#Experience_gain
final var xp = (double) Utils.getLastDelta(hpExpEarned);
return (int) Math.round(xp * (3.0d / 4.0d) * MODIFIER * GLOBAL_MODIFIER);
}

public void cleanup()
{
while (hpExpEarnedTickCount.size() > 5)
{
hpExpEarnedTickCount.removeFirst();
}
while (hpExpEarned.size() > 5)
{
hpExpEarned.removeFirst();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,16 @@

import com.attacktimer.AnimationData;
import com.attacktimer.AttackProcedure;
import com.attacktimer.Spellbook;
import net.runelite.api.Client;

public class BloodMoonSet implements IVariableSpeed
{
private static final int BLOOD_MOON_SET_ANIM_ID = 2792;

public int apply(final Client client, final AnimationData curAnimation, final AttackProcedure atkType,
final int damageDealt, final int lastSpecDelta, final int baseSpeed, final int curSpeed)
final Spellbook spellbook, final int damageDealt, final int lastSpecDelta, final int baseSpeed,
final int curSpeed)
{
if (client.getLocalPlayer().hasSpotAnim(BLOOD_MOON_SET_ANIM_ID))
{
Expand Down
4 changes: 3 additions & 1 deletion src/main/java/com/attacktimer/VariableSpeed/EyeOfAyak.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,14 @@

import com.attacktimer.AnimationData;
import com.attacktimer.AttackProcedure;
import com.attacktimer.Spellbook;
import net.runelite.api.Client;

public class EyeOfAyak implements IVariableSpeed
{
public int apply(final Client client, final AnimationData curAnimation, final AttackProcedure atkType,
final int damageDealt, final int lastSpecDelta, final int baseSpeed, final int curSpeed)
final Spellbook spellbook, final int damageDealt, final int lastSpecDelta, final int baseSpeed,
final int curSpeed)
{
// https://oldschool.runescape.wiki/w/Eye_of_ayak#Charged
// https://oldschool.runescape.wiki/w/Eye_of_ayak#Special_attack
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

import com.attacktimer.AnimationData;
import com.attacktimer.AttackProcedure;
import com.attacktimer.Spellbook;
import com.attacktimer.VariableSpeed.State.IStateTracker;
import net.runelite.api.Client;

Expand Down Expand Up @@ -56,6 +57,7 @@ public int apply(
final Client client,
final AnimationData curAnimation,
final AttackProcedure atkType,
final Spellbook spellbook,
final int damageDealt,
final int lastSpecDelta,
final int baseSpeed,
Expand Down
21 changes: 8 additions & 13 deletions src/main/java/com/attacktimer/VariableSpeed/PurgingStaffSpec.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import com.attacktimer.AnimationData;
import com.attacktimer.AttackProcedure;
import com.attacktimer.ClientUtils.Utils;
import com.attacktimer.Spellbook;
import com.attacktimer.VariableSpeed.State.Yama;
import net.runelite.api.Client;
import net.runelite.api.NPC;
Expand All @@ -47,34 +48,28 @@ public class PurgingStaffSpec implements IVariableSpeed

// https://oldschool.runescape.wiki/w/Purging_staff#Special_attack
public int apply(final Client client, final AnimationData curAnimation, final AttackProcedure atkType,
final int damageDealt, final int lastSpecDelta, final int baseSpeed, final int curSpeed)
final Spellbook spellbook, final int damageDealt, final int lastSpecDelta, final int baseSpeed,
final int curSpeed)
{
// For now the plugin only works for yama
if (!this.yama.inYamaRegion)
{
return curSpeed;
}
var target = Utils.getTargetNPC(client);
var flare = Yama.isEitherVoidFlare(target, lastTarget);
final var target = Utils.getTargetNPC(client);
final var flare = Yama.isEitherVoidFlare(target, lastTarget);
lastTarget = target;
if (flare == null)
{
return curSpeed;
}
if (yama == null)
if (flare == null || yama == null)
{
return curSpeed;
}

yama.dealVoidFlareDamage(flare, damageDealt);
if (lastSpecDelta != -250)
if (lastSpecDelta != -250 || Utils.getWeaponId(client) != PURGING_STAFF_ID || spellbook != Spellbook.ARCEUUS)
{
// not using the spec
return curSpeed;
}
if (Utils.getWeaponId(client) != PURGING_STAFF_ID)
{
// not using a purging staff
// not on the arceuss spellbook
return curSpeed;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,15 @@
import com.attacktimer.AttackProcedure;
import com.attacktimer.AttackStyle;
import com.attacktimer.ClientUtils.Utils;
import com.attacktimer.Spellbook;
import net.runelite.api.Client;
import net.runelite.api.gameval.VarPlayerID;

public class RapidAttackStyle implements IVariableSpeed
{
public int apply(final Client client, final AnimationData curAnimation, final AttackProcedure atkType,
final int damageDealt, final int lastSpecDelta, final int baseSpeed, final int curSpeed)
final Spellbook spellbook, final int damageDealt, final int lastSpecDelta, final int baseSpeed,
final int curSpeed)
{
// index 1 == rapid
final boolean isRapid = client.getVarpValue(VarPlayerID.COM_MODE) == 1;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,14 @@

import com.attacktimer.AnimationData;
import com.attacktimer.AttackProcedure;
import com.attacktimer.Spellbook;
import net.runelite.api.Client;

public class RedKerisSpec implements IVariableSpeed
{
public int apply(final Client client, final AnimationData curAnimation, final AttackProcedure atkType,
final int damageDealt, final int lastSpecDelta, final int baseSpeed, final int curSpeed)
final Spellbook spellbook, final int damageDealt, final int lastSpecDelta, final int baseSpeed,
final int curSpeed)
{
// https://oldschool.runescape.wiki/w/Keris_partisan_of_corruption#Special_attack
if (lastSpecDelta != -750 || curAnimation != AnimationData.MELEE_RED_KERIS_SPEC)
Expand Down
Loading
Loading