diff --git a/runelite-plugin.properties b/runelite-plugin.properties index 3057d42..bc5a347 100644 --- a/runelite-plugin.properties +++ b/runelite-plugin.properties @@ -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 \ No newline at end of file diff --git a/src/main/java/com/attacktimer/AttackTimerMetronomeConfig.java b/src/main/java/com/attacktimer/AttackTimerMetronomeConfig.java index 3d7112a..4d2153a 100644 --- a/src/main/java/com/attacktimer/AttackTimerMetronomeConfig.java +++ b/src/main/java/com/attacktimer/AttackTimerMetronomeConfig.java @@ -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", @@ -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", @@ -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", @@ -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", @@ -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", @@ -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", @@ -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", @@ -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", @@ -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", @@ -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", @@ -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", @@ -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", @@ -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", @@ -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", @@ -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.", @@ -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 diff --git a/src/main/java/com/attacktimer/AttackTimerMetronomePlugin.java b/src/main/java/com/attacktimer/AttackTimerMetronomePlugin.java index 41f4945..6e1fcc1 100644 --- a/src/main/java/com/attacktimer/AttackTimerMetronomePlugin.java +++ b/src/main/java/com/attacktimer/AttackTimerMetronomePlugin.java @@ -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; @@ -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", @@ -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(); } } @@ -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(); } } @@ -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; @@ -541,6 +546,7 @@ public void onGameTick(GameTick tick) case NOT_ATTACKING: if (isAttacking) { + logStateTrace("onGameTick"); performAttack(); // Sets state to DELAYED_FIRST_TICK. } else @@ -557,6 +563,7 @@ public void onGameTick(GameTick tick) { // Eligible for a new attack if (isAttacking) { + logStateTrace("onGameTick"); performAttack(); } else @@ -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 @@ -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 = ", "; @@ -655,9 +679,9 @@ 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. @@ -665,4 +689,17 @@ public void checkForLateWeaponSwaps() } } + /** + * 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; + } + }