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
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.0
version=1.2.1
description=A plugin to countdown until your next attack
tags=pvm,timer,attack,combat,weapon
plugins=com.attacktimer.AttackTimerMetronomePlugin
53 changes: 36 additions & 17 deletions src/main/java/com/attacktimer/AttackTimerMetronomeConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,12 @@ default boolean enableMetronome()
@ConfigSection(
name = "Attack Cooldown Tick Settings",
description = "Change attack tick cooldown settings",
position = 1
position = 10
)
final String TICK_NUMBER_SETTINGS = "Attack Cooldown Tick Settings";

@ConfigItem(
position = 1,
position = 10,
keyName = "showTick",
name = "Show Attack Cooldown Ticks",
description = "Shows number of ticks until next attack",
Expand All @@ -71,7 +71,7 @@ default boolean showTick()


@ConfigItem(
position = 2,
position = 20,
keyName = "disableFontScaling",
name = "Disable Font Size Scaling (Metronome Tick Only)",
description = "Disables font size scaling for metronome tick number",
Expand All @@ -83,7 +83,7 @@ default boolean disableFontScaling()
}

@ConfigItem(
position = 3,
position = 30,
keyName = "fontSize",
name = "Font Size (Overhead Tick Only)",
description = "Change the font size of the overhead attack cooldown ticks",
Expand All @@ -96,7 +96,7 @@ default int fontSize()
}

@ConfigItem(
position = 4,
position = 40,
keyName = "countColor",
name = "Tick Number Color",
description = "Configures the color of tick number",
Expand All @@ -108,7 +108,7 @@ default Color NumberColor()
}

@ConfigItem(
position = 5,
position = 50,
keyName = "lastColor",
name = "Last Tick Color",
description = "Configures the color of tick number when it says 1",
Expand All @@ -120,7 +120,7 @@ default Color LastColor()
}

@ConfigItem(
position = 6,
position = 60,
keyName = "fontType",
name = "Font Type",
description = "Change the font of the tick number",
Expand All @@ -132,7 +132,7 @@ default FontTypes fontType()
}

@ConfigItem(
position = 7,
position = 70,
keyName = "ticksPosition",
name = "Ticks Position",
description = "Position of the tick number respective to the player",
Expand All @@ -144,7 +144,7 @@ default TicksPosition ticksPosition()
}

@ConfigItem(
position = 8,
position = 80,
keyName = "tickHeightOffset",
name = "Height Offset",
description = "Height offset for minor adjustments of the tick number",
Expand All @@ -157,7 +157,7 @@ default int heightTickOffset()
}

@ConfigItem(
position = 9,
position = 90,
keyName = "useZeroBasedTickCount",
name = "Zero-based Tick Count",
description = "Count ticks to 0 instead of 1",
Expand All @@ -171,12 +171,12 @@ default boolean useZeroBasedTickCount()
@ConfigSection(
name = "Attack Bar",
description = "Change the colors and number of colors to cycle through",
position = 2
position = 20
)
final String ATTACK_BAR_SETTINGS = "Attack Cooldown Bar Settings";

@ConfigItem(
position = 1,
position = 10,
keyName = "attackBar",
name = "Show Attack Bar",
description = "Show the attack bar",
Expand All @@ -188,7 +188,7 @@ default boolean showBar()
}

@ConfigItem(
position = 2,
position = 20,
keyName = "attackBarHeightOffset",
name = "Height Offset",
description = "Height offset for the bar from top of player model",
Expand All @@ -201,7 +201,7 @@ default int heightOffset()
}

@ConfigItem(
position = 3,
position = 30,
keyName = "attackBarEmpties",
name = "Empties Before Attack",
description = "Controls whether the attack bar will fully empty before a new attack can occur",
Expand All @@ -213,7 +213,7 @@ default boolean barEmpties()
}

@ConfigItem(
position = 4,
position = 40,
keyName = "attackBarFills",
name = "Fills Before Attack",
description = "Controls whether the attack bar will fill completely after an attack",
Expand All @@ -225,7 +225,7 @@ default boolean barFills()
}

@ConfigItem(
position = 5,
position = 50,
keyName = "attackBarDirection",
name = "Attack Bar Fills or Drains",
description = "Controls whether the attack bar will fill or drain as a cooldown",
Expand All @@ -237,7 +237,7 @@ default boolean barDirection()
}

@ConfigItem(
position = 6,
position = 60,
keyName = "attackBarStyle",
name = "Attack Bar Style",
description = "Auto matches HD/SD from Interface Styles plugin. Standard forces the basic bar. High Detail forces the HD bar.",
Expand All @@ -248,6 +248,25 @@ default AttackBarStyle barStyle()
return AttackBarStyle.AUTO;
}

@ConfigSection(
name = "Attack Timer Debug Settings",
description = "Debug",
position = 30
)
final String ATTACK_TIMER_DEBUG_SETTINGS = "Attack Timer Debug Settings";

@ConfigItem(
position = 10,
keyName = "debugLogs",
name = "Enable Logging",
description = "Turns on state machine logging for bugs",
section = ATTACK_TIMER_DEBUG_SETTINGS
)
default boolean debugLogs()
{
return false;
}

@Getter
@AllArgsConstructor
enum TicksPosition
Expand Down
49 changes: 43 additions & 6 deletions src/main/java/com/attacktimer/AttackTimerMetronomePlugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
import java.util.Set;
import java.util.regex.Pattern;
import javax.inject.Inject;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.Actor;
import net.runelite.api.Client;
import net.runelite.api.NPC;
Expand All @@ -70,6 +71,7 @@
import net.runelite.client.plugins.PluginDescriptor;
import net.runelite.client.ui.overlay.OverlayManager;

@Slf4j
@PluginDescriptor(
name = "Attack Timer Metronome",
description = "Shows a visual cue on an overlay every game tick to help timing based activities",
Expand Down Expand Up @@ -225,9 +227,10 @@ protected void onFakeXpDrop(FakeXpDrop event)
return;
}
combatExpEarned.get(event.getSkill()).addLast(event.getXp());
if (attackState == AttackState.DELAYED_FIRST_TICK)
if (inPreAttackWindow())
{
// We recompute attack speed here incase the hitsplat mattered (e.g. purging staff)
logStateTrace("onFakeXpDrop");
performAttack();
}
}
Expand All @@ -240,9 +243,10 @@ protected void onStatChanged(StatChanged event)
return;
}
combatExpEarned.get(event.getSkill()).addLast(event.getXp());
if (attackState == AttackState.DELAYED_FIRST_TICK)
if (inPreAttackWindow())
{
// We recompute attack speed here incase the hitsplat mattered (e.g. purging staff)
logStateTrace("onStatChanged");
performAttack();
}
}
Expand Down Expand Up @@ -509,6 +513,7 @@ public void onInteractingChanged(InteractingChanged interactingChanged)
// an instant attack. If its queued, don't trigger the cooldown yet.
if (isPlayerAttacking())
{
logStateTrace("onInteractingChanged");
performAttack();
}
break;
Expand Down Expand Up @@ -541,6 +546,7 @@ public void onGameTick(GameTick tick)
case NOT_ATTACKING:
if (isAttacking)
{
logStateTrace("onGameTick");
performAttack(); // Sets state to DELAYED_FIRST_TICK.
}
else
Expand All @@ -557,6 +563,7 @@ public void onGameTick(GameTick tick)
{ // Eligible for a new attack
if (isAttacking)
{
logStateTrace("onGameTick");
performAttack();
}
else
Expand Down Expand Up @@ -613,6 +620,23 @@ protected void shutDown() throws Exception

@VisibleForTesting
public void writeState(ByteArrayDataOutput outChannel)
{
StringBuilder sb = getState();
byte[] bytes = sb.toString().getBytes(StandardCharsets.UTF_8);
outChannel.write(bytes);
}

public void logStateTrace(String trace)
{
if (!config.debugLogs())
{
return;
}
StringBuilder sb = getState();
log.debug("["+trace+"]: "+sb.toString());
}

private StringBuilder getState()
{
StringBuilder sb = new StringBuilder();
// @formatter:off
Expand All @@ -626,9 +650,9 @@ public void writeState(ByteArrayDataOutput outChannel)
sb.append("soundEffectTick: "); sb.append(this.soundEffectTick);sb.append(SEPARATOR);
sb.append("soundEffectId: "); sb.append(this.soundEffectId);sb.append("\n");
// @formatter:on
byte[] bytes = sb.toString().getBytes(StandardCharsets.UTF_8);
outChannel.write(bytes);
return sb;
}

private static final String SEPARATOR = ", ";


Expand All @@ -655,14 +679,27 @@ public void checkForLateWeaponSwaps()

// This windowing safe guards of from late swaps inside a tick, if we have already rendered the tick
// then we shouldn't perform another attack.
final boolean preAttackWindow = attackState == AttackState.DELAYED_FIRST_TICK && renderedState != attackState;
if (preAttackWindow && weaponMisMatch)
if (inPreAttackWindow() && weaponMisMatch)
{
logStateTrace("checkForLateWeaponSwaps");
// "Perform an attack" this is overwrites the last attack since we now know the user swapped
// "Something" this tick, the equipped weapon detection will pick up specific weapon swaps. Even
// swapping more than 1 weapon inside a single tick.
performAttack();
}
}

/**
* inPreAttackWindow returns true if and only if the plugin has computed an attack speed and
* determined we are attacking an NPC, but the timer has not been rendered yet. Hence there is time
* still to adjust the speed if new data would change the result.
*
* @return true if an attack is detected and the plugin has not yet rendered the timer for the
* current attack, false in every other case.
*/
private boolean inPreAttackWindow()
{
return attackState == AttackState.DELAYED_FIRST_TICK && renderedState != attackState;
}

}
Loading