discord: Fix action timeout, add in game time elapsed option (#12471)
Co-authored-by: Matthew C <66925241+Matthew-nop@users.noreply.github.com> Co-authored-by: Tomas Slusny <slusnucky@gmail.com> Co-authored-by: Jordan Atwood <jordan.atwood423@gmail.com>
This commit is contained in:
@@ -24,6 +24,7 @@
|
||||
*/
|
||||
package net.runelite.client.plugins.discord;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import net.runelite.client.config.Config;
|
||||
import net.runelite.client.config.ConfigGroup;
|
||||
import net.runelite.client.config.ConfigItem;
|
||||
@@ -32,11 +33,38 @@ import net.runelite.client.config.Units;
|
||||
@ConfigGroup("discord")
|
||||
public interface DiscordConfig extends Config
|
||||
{
|
||||
@AllArgsConstructor
|
||||
enum ElapsedTimeType
|
||||
{
|
||||
TOTAL("Total elapsed time"),
|
||||
ACTIVITY("Per activity"),
|
||||
HIDDEN("Hide elapsed time");
|
||||
|
||||
private final String value;
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "elapsedTime",
|
||||
name = "Elapsed Time",
|
||||
description = "Configures elapsed time shown.",
|
||||
position = 1
|
||||
)
|
||||
default ElapsedTimeType elapsedTimeType()
|
||||
{
|
||||
return ElapsedTimeType.ACTIVITY;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "actionTimeout",
|
||||
name = "Action timeout",
|
||||
description = "Configures after how long of not updating status will be reset (in minutes)",
|
||||
position = 1
|
||||
name = "Activity timeout",
|
||||
description = "Configures after how long of not updating activity will be reset (in minutes)",
|
||||
position = 2
|
||||
)
|
||||
@Units(Units.MINUTES)
|
||||
default int actionTimeout()
|
||||
@@ -44,17 +72,6 @@ public interface DiscordConfig extends Config
|
||||
return 5;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "hideElapsedTime",
|
||||
name = "Hide elapsed time",
|
||||
description = "Configures if the elapsed time of your activity should be hidden.",
|
||||
position = 2
|
||||
)
|
||||
default boolean hideElapsedTime()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@ConfigItem(
|
||||
keyName = "showSkillActivity",
|
||||
name = "Skilling",
|
||||
|
||||
@@ -42,8 +42,8 @@ import net.runelite.api.Varbits;
|
||||
enum DiscordGameEventType
|
||||
{
|
||||
|
||||
IN_GAME("In Game", -3),
|
||||
IN_MENU("In Menu", -3),
|
||||
IN_MENU("In Menu", -3, true, true, true, false, true),
|
||||
IN_GAME("In Game", -3, true, false, false, false, true),
|
||||
PLAYING_DEADMAN("Playing Deadman Mode", -3),
|
||||
PLAYING_PVP("Playing in a PVP world", -3),
|
||||
TRAINING_ATTACK(Skill.ATTACK),
|
||||
@@ -462,9 +462,32 @@ enum DiscordGameEventType
|
||||
private String details;
|
||||
|
||||
private int priority;
|
||||
|
||||
/**
|
||||
* Marks this event as root event, e.g event that should be used for total time tracking
|
||||
*/
|
||||
private boolean root;
|
||||
|
||||
/**
|
||||
* Determines if event should clear other clearable events when triggered
|
||||
*/
|
||||
private boolean shouldClear;
|
||||
|
||||
/**
|
||||
* Determines if event should be processed when it timeouts based on action timeout
|
||||
*/
|
||||
private boolean shouldTimeout;
|
||||
|
||||
/**
|
||||
* Determines if event start time should be reset when processed
|
||||
*/
|
||||
private boolean shouldRestart;
|
||||
|
||||
/**
|
||||
* Determines if event should be cleared when processed
|
||||
*/
|
||||
private boolean shouldBeCleared = true;
|
||||
|
||||
@Nullable
|
||||
private DiscordAreaType discordAreaType;
|
||||
|
||||
@@ -496,11 +519,20 @@ enum DiscordGameEventType
|
||||
this.shouldClear = true;
|
||||
}
|
||||
|
||||
DiscordGameEventType(String state, int priority)
|
||||
DiscordGameEventType(String state, int priority, boolean shouldClear, boolean shouldTimeout, boolean shouldRestart, boolean shouldBeCleared, boolean root)
|
||||
{
|
||||
this.state = state;
|
||||
this.priority = priority;
|
||||
this.shouldClear = true;
|
||||
this.shouldClear = shouldClear;
|
||||
this.shouldTimeout = shouldTimeout;
|
||||
this.shouldRestart = shouldRestart;
|
||||
this.shouldBeCleared = shouldBeCleared;
|
||||
this.root = root;
|
||||
}
|
||||
|
||||
DiscordGameEventType(String state, int priority)
|
||||
{
|
||||
this(state, priority, true, false, false, true, false);
|
||||
}
|
||||
|
||||
DiscordGameEventType(String areaName, DiscordAreaType areaType, Varbits varbits)
|
||||
|
||||
@@ -106,7 +106,7 @@ public class DiscordPlugin extends Plugin
|
||||
@Inject
|
||||
private OkHttpClient okHttpClient;
|
||||
|
||||
private Map<Skill, Integer> skillExp = new HashMap<>();
|
||||
private final Map<Skill, Integer> skillExp = new HashMap<>();
|
||||
private NavigationButton discordButton;
|
||||
private boolean loginFlag;
|
||||
|
||||
@@ -129,6 +129,7 @@ public class DiscordPlugin extends Plugin
|
||||
.build();
|
||||
|
||||
clientToolbar.addNavigation(discordButton);
|
||||
resetState();
|
||||
checkForGameStateUpdate();
|
||||
checkForAreaUpdate();
|
||||
|
||||
@@ -144,7 +145,7 @@ public class DiscordPlugin extends Plugin
|
||||
protected void shutDown() throws Exception
|
||||
{
|
||||
clientToolbar.removeNavigation(discordButton);
|
||||
discordState.reset();
|
||||
resetState();
|
||||
partyService.changeParty(null);
|
||||
wsClient.unregisterMessage(DiscordUserInfo.class);
|
||||
}
|
||||
@@ -155,6 +156,7 @@ public class DiscordPlugin extends Plugin
|
||||
switch (event.getGameState())
|
||||
{
|
||||
case LOGIN_SCREEN:
|
||||
resetState();
|
||||
checkForGameStateUpdate();
|
||||
return;
|
||||
case LOGGING_IN:
|
||||
@@ -164,9 +166,9 @@ public class DiscordPlugin extends Plugin
|
||||
if (loginFlag)
|
||||
{
|
||||
loginFlag = false;
|
||||
resetState();
|
||||
checkForGameStateUpdate();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -178,6 +180,7 @@ public class DiscordPlugin extends Plugin
|
||||
{
|
||||
if (event.getGroup().equalsIgnoreCase("discord"))
|
||||
{
|
||||
resetState();
|
||||
checkForGameStateUpdate();
|
||||
checkForAreaUpdate();
|
||||
}
|
||||
@@ -362,10 +365,13 @@ public class DiscordPlugin extends Plugin
|
||||
discordState.refresh();
|
||||
}
|
||||
|
||||
private void resetState()
|
||||
{
|
||||
discordState.reset();
|
||||
}
|
||||
|
||||
private void checkForGameStateUpdate()
|
||||
{
|
||||
// Game state update does also full reset of discord state
|
||||
discordState.reset();
|
||||
discordState.triggerEvent(client.getGameState() == GameState.LOGGED_IN
|
||||
? DiscordGameEventType.IN_GAME
|
||||
: DiscordGameEventType.IN_MENU);
|
||||
|
||||
@@ -32,6 +32,8 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.inject.Inject;
|
||||
import lombok.Data;
|
||||
import net.runelite.client.RuneLiteProperties;
|
||||
@@ -49,7 +51,7 @@ class DiscordState
|
||||
private static class EventWithTime
|
||||
{
|
||||
private final DiscordGameEventType type;
|
||||
private final Instant start;
|
||||
private Instant start;
|
||||
private Instant updated;
|
||||
}
|
||||
|
||||
@@ -57,7 +59,7 @@ class DiscordState
|
||||
private final List<EventWithTime> events = new ArrayList<>();
|
||||
private final DiscordService discordService;
|
||||
private final DiscordConfig config;
|
||||
private PartyService party;
|
||||
private final PartyService party;
|
||||
private DiscordPresence lastPresence;
|
||||
|
||||
@Inject
|
||||
@@ -115,7 +117,7 @@ class DiscordState
|
||||
void triggerEvent(final DiscordGameEventType eventType)
|
||||
{
|
||||
final Optional<EventWithTime> foundEvent = events.stream().filter(e -> e.type == eventType).findFirst();
|
||||
EventWithTime event;
|
||||
final EventWithTime event;
|
||||
|
||||
if (foundEvent.isPresent())
|
||||
{
|
||||
@@ -123,8 +125,8 @@ class DiscordState
|
||||
}
|
||||
else
|
||||
{
|
||||
event = new EventWithTime(eventType, Instant.now());
|
||||
|
||||
event = new EventWithTime(eventType);
|
||||
event.setStart(Instant.now());
|
||||
events.add(event);
|
||||
}
|
||||
|
||||
@@ -132,7 +134,12 @@ class DiscordState
|
||||
|
||||
if (event.getType().isShouldClear())
|
||||
{
|
||||
events.removeIf(e -> e.getType() != eventType && e.getType().isShouldClear());
|
||||
events.removeIf(e -> e.getType() != eventType && e.getType().isShouldBeCleared());
|
||||
}
|
||||
|
||||
if (event.getType().isShouldRestart())
|
||||
{
|
||||
event.setStart(Instant.now());
|
||||
}
|
||||
|
||||
events.sort((a, b) -> ComparisonChain.start()
|
||||
@@ -140,7 +147,18 @@ class DiscordState
|
||||
.compare(b.getUpdated(), a.getUpdated())
|
||||
.result());
|
||||
|
||||
event = events.get(0);
|
||||
updatePresenceWithLatestEvent();
|
||||
}
|
||||
|
||||
private void updatePresenceWithLatestEvent()
|
||||
{
|
||||
if (events.isEmpty())
|
||||
{
|
||||
reset();
|
||||
return;
|
||||
}
|
||||
|
||||
final EventWithTime event = events.get(0);
|
||||
|
||||
String imageKey = null;
|
||||
String state = null;
|
||||
@@ -176,11 +194,35 @@ class DiscordState
|
||||
.state(MoreObjects.firstNonNull(state, ""))
|
||||
.details(MoreObjects.firstNonNull(details, ""))
|
||||
.largeImageText(RuneLiteProperties.getTitle() + " v" + versionShortHand)
|
||||
.startTimestamp(config.hideElapsedTime() ? null : event.getStart())
|
||||
.smallImageKey(imageKey)
|
||||
.partyMax(PARTY_MAX)
|
||||
.partySize(party.getMembers().size());
|
||||
|
||||
final Instant startTime;
|
||||
switch (config.elapsedTimeType())
|
||||
{
|
||||
case HIDDEN:
|
||||
startTime = null;
|
||||
break;
|
||||
case TOTAL:
|
||||
// We are tracking total time spent instead of per activity time so try to find
|
||||
// root event as this indicates start of tracking and find last updated one
|
||||
// to determine correct state we are in
|
||||
startTime = events.stream()
|
||||
.filter(e -> e.getType().isRoot())
|
||||
.sorted((a, b) -> b.getUpdated().compareTo(a.getUpdated()))
|
||||
.map(EventWithTime::getStart)
|
||||
.findFirst()
|
||||
.orElse(event.getStart());
|
||||
break;
|
||||
case ACTIVITY:
|
||||
default:
|
||||
startTime = event.getStart();
|
||||
break;
|
||||
}
|
||||
|
||||
presenceBuilder.startTimestamp(startTime);
|
||||
|
||||
if (!party.isInParty() || party.isPartyOwner())
|
||||
{
|
||||
presenceBuilder.partyId(partyId.toString());
|
||||
@@ -209,19 +251,29 @@ class DiscordState
|
||||
|
||||
final Duration actionTimeout = Duration.ofMinutes(config.actionTimeout());
|
||||
final Instant now = Instant.now();
|
||||
final EventWithTime eventWithTime = events.get(0);
|
||||
final AtomicBoolean updatedAny = new AtomicBoolean();
|
||||
|
||||
events.removeIf(event -> event.getType().isShouldTimeout() && now.isAfter(event.getUpdated().plus(actionTimeout)));
|
||||
final boolean removedAny = events.removeAll(events.stream()
|
||||
// Find only events that should time out
|
||||
.filter(event -> event.getType().isShouldTimeout() && now.isAfter(event.getUpdated().plus(actionTimeout)))
|
||||
// Reset start times on timed events that should restart
|
||||
.peek(event ->
|
||||
{
|
||||
if (event.getType().isShouldRestart())
|
||||
{
|
||||
event.setStart(null);
|
||||
updatedAny.set(true);
|
||||
}
|
||||
})
|
||||
// Now filter out events that should restart as we do not want to remove them
|
||||
.filter(event -> !event.getType().isShouldRestart())
|
||||
.filter(event -> event.getType().isShouldBeCleared())
|
||||
.collect(Collectors.toList())
|
||||
);
|
||||
|
||||
assert DiscordGameEventType.IN_MENU.getState() != null;
|
||||
if (DiscordGameEventType.IN_MENU.getState().equals(eventWithTime.getType().getState()) && now.isAfter(eventWithTime.getStart().plus(actionTimeout)))
|
||||
if (removedAny || updatedAny.get())
|
||||
{
|
||||
final DiscordPresence presence = lastPresence
|
||||
.toBuilder()
|
||||
.startTimestamp(null)
|
||||
.build();
|
||||
lastPresence = presence;
|
||||
discordService.updatePresence(presence);
|
||||
updatePresenceWithLatestEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user