Merge remote-tracking branch 'runelite/master'

This commit is contained in:
Owain van Brakel
2022-03-30 12:03:22 +02:00
34 changed files with 1071 additions and 92 deletions

View File

@@ -0,0 +1,37 @@
/*
* Copyright (c) 2022, Adam <Adam@sigterm.info>
* 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 com.jagex.oldscape.pub;
public interface OAuthApi
{
boolean isOnLoginScreen();
void setOtlTokenRequester(OtlTokenRequester otlTokenRequester);
/**
* Gets a unique per-RuneScape-Account identifier or {@code -1} if the client has not logged in yet
*/
long getAccountHash();
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright (c) 2022, Adam <Adam@sigterm.info>
* 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 com.jagex.oldscape.pub;
import java.net.URL;
import java.util.concurrent.Future;
public interface OtlTokenRequester
{
Future<OtlTokenResponse> request(URL url);
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright (c) 2022, Adam <Adam@sigterm.info>
* 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 com.jagex.oldscape.pub;
public interface OtlTokenResponse
{
boolean isSuccess();
String getToken();
}

View File

@@ -24,6 +24,7 @@
*/ */
package net.runelite.api; package net.runelite.api;
import com.jagex.oldscape.pub.OAuthApi;
import java.awt.Canvas; import java.awt.Canvas;
import java.awt.Dimension; import java.awt.Dimension;
import java.math.BigInteger; import java.math.BigInteger;
@@ -52,7 +53,7 @@ import org.intellij.lang.annotations.MagicConstant;
/** /**
* Represents the RuneScape client. * Represents the RuneScape client.
*/ */
public interface Client extends GameEngine public interface Client extends OAuthApi, GameEngine
{ {
/** /**
* The injected client invokes these callbacks to send events to us * The injected client invokes these callbacks to send events to us
@@ -199,10 +200,13 @@ public interface Client extends GameEngine
void setWorldSelectOpen(boolean open); void setWorldSelectOpen(boolean open);
/** /**
* DEPRECATED. See getAccountHash instead.
* Gets the current logged in username. * Gets the current logged in username.
* *
* @return the logged in username * @return the logged in username
* @see OAuthApi#getAccountHash()
*/ */
@Deprecated
String getUsername(); String getUsername();
/** /**

View File

@@ -33,6 +33,7 @@ public final class ParamID
* Long name for NPCs used in the HP hud * Long name for NPCs used in the HP hud
*/ */
public static final int NPC_HP_NAME = 510; public static final int NPC_HP_NAME = 510;
public static final int QUEST_NAME = 610;
/** /**
* @see SettingID * @see SettingID
*/ */

View File

@@ -275,6 +275,12 @@ public final class ScriptID
@ScriptArguments(integer = 3) @ScriptArguments(integer = 3)
public static final int GE_ITEM_SEARCH = 752; public static final int GE_ITEM_SEARCH = 752;
/**
* On load listener for building the quest list interface
*/
@ScriptArguments(integer = 8)
public static final int QUESTLIST_INIT = 1350;
/** /**
* Called when the friends list is updated * Called when the friends list is updated
* <ul> * <ul>
@@ -428,4 +434,16 @@ public final class ScriptID
*/ */
@ScriptArguments(integer = 1) @ScriptArguments(integer = 1)
public static final int NOTIFICATION_DELAY = 3347; public static final int NOTIFICATION_DELAY = 3347;
/**
* Check if a quest should be filtered from the quest list
* <ul>
* <li> int (StructID) Quest struct </li>
* <li> int State filter </li>
* <li> int Requirement filter </li>
* <li> int Stats filter </li>
* </ul>
*/
@ScriptArguments(integer = 4)
public static final int QUEST_FILTER = 3238;
} }

View File

@@ -0,0 +1,29 @@
/*
* Copyright (c) 2022 Abex
* 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.api.events;
public class AccountHashChanged
{
}

View File

@@ -132,6 +132,7 @@ public final class WidgetID
public static final int SKOTIZO_GROUP_ID = 308; public static final int SKOTIZO_GROUP_ID = 308;
public static final int ENTERING_HOUSE_GROUP_ID = 71; public static final int ENTERING_HOUSE_GROUP_ID = 71;
public static final int FULLSCREEN_CONTAINER_TLI = 165; public static final int FULLSCREEN_CONTAINER_TLI = 165;
public static final int QUESTLIST_GROUP_ID = 399;
public static final int SKILLS_GROUP_ID = 320; public static final int SKILLS_GROUP_ID = 320;
public static final int MUSIC_GROUP_ID = 239; public static final int MUSIC_GROUP_ID = 239;
public static final int BARROWS_PUZZLE_GROUP_ID = 25; public static final int BARROWS_PUZZLE_GROUP_ID = 25;
@@ -889,6 +890,12 @@ public final class WidgetID
static final int CONTAINER = 2; static final int CONTAINER = 2;
} }
static class QuestList
{
static final int BOX = 0;
static final int CONTAINER = 2;
}
static class Music static class Music
{ {
static final int CONTAINER = 0; static final int CONTAINER = 0;

View File

@@ -521,6 +521,9 @@ public enum WidgetInfo
SKOTIZO_CONTAINER(WidgetID.SKOTIZO_GROUP_ID, WidgetID.Skotizo.CONTAINER), SKOTIZO_CONTAINER(WidgetID.SKOTIZO_GROUP_ID, WidgetID.Skotizo.CONTAINER),
QUESTLIST_BOX(WidgetID.QUESTLIST_GROUP_ID, WidgetID.QuestList.BOX),
QUESTLIST_CONTAINER(WidgetID.QUESTLIST_GROUP_ID, WidgetID.QuestList.CONTAINER),
SEED_VAULT_TITLE_CONTAINER(WidgetID.SEED_VAULT_GROUP_ID, WidgetID.SeedVault.TITLE_CONTAINER), SEED_VAULT_TITLE_CONTAINER(WidgetID.SEED_VAULT_GROUP_ID, WidgetID.SeedVault.TITLE_CONTAINER),
SEED_VAULT_ITEM_CONTAINER(WidgetID.SEED_VAULT_GROUP_ID, WidgetID.SeedVault.ITEM_CONTAINER), SEED_VAULT_ITEM_CONTAINER(WidgetID.SEED_VAULT_GROUP_ID, WidgetID.SeedVault.ITEM_CONTAINER),
SEED_VAULT_ITEM_TEXT(WidgetID.SEED_VAULT_GROUP_ID, WidgetID.SeedVault.ITEM_TEXT), SEED_VAULT_ITEM_TEXT(WidgetID.SEED_VAULT_GROUP_ID, WidgetID.SeedVault.ITEM_TEXT),

View File

@@ -27,9 +27,12 @@ package net.runelite.client;
import ch.qos.logback.classic.Level; import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger; import ch.qos.logback.classic.Logger;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
import com.google.inject.Guice; import com.google.inject.Guice;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.Injector; import com.google.inject.Injector;
import com.jagex.oldscape.pub.OAuthApi;
import com.jagex.oldscape.pub.OtlTokenResponse;
import com.openosrs.client.OpenOSRS; import com.openosrs.client.OpenOSRS;
import com.openosrs.client.game.PlayerManager; import com.openosrs.client.game.PlayerManager;
import com.openosrs.client.ui.OpenOSRSSplashScreen; import com.openosrs.client.ui.OpenOSRSSplashScreen;
@@ -50,6 +53,7 @@ import java.security.SecureRandom;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Stream; import java.util.stream.Stream;
@@ -92,6 +96,8 @@ import net.runelite.http.api.RuneLiteAPI;
import net.runelite.http.api.worlds.World; import net.runelite.http.api.worlds.World;
import net.runelite.http.api.worlds.WorldResult; import net.runelite.http.api.worlds.WorldResult;
import okhttp3.Cache; import okhttp3.Cache;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
import okhttp3.Request; import okhttp3.Request;
import okhttp3.Response; import okhttp3.Response;
@@ -120,6 +126,9 @@ public class RuneLite
@Inject @Inject
private net.runelite.client.plugins.PluginManager pluginManager; private net.runelite.client.plugins.PluginManager pluginManager;
@Inject
private OkHttpClient okHttpClient;
@Inject @Inject
private ExternalPluginManager externalPluginManager; private ExternalPluginManager externalPluginManager;
@@ -290,8 +299,8 @@ public class RuneLite
try try
{ {
final ClientLoader clientLoader = new ClientLoader(okHttpClient, options.valueOf(updateMode), (String) options.valueOf("jav_config"));
final RuntimeConfigLoader runtimeConfigLoader = new RuntimeConfigLoader(okHttpClient); final RuntimeConfigLoader runtimeConfigLoader = new RuntimeConfigLoader(okHttpClient);
final ClientLoader clientLoader = new ClientLoader(okHttpClient, options.valueOf(updateMode), runtimeConfigLoader, (String) options.valueOf("jav_config"));
new Thread(() -> new Thread(() ->
{ {
@@ -330,6 +339,7 @@ public class RuneLite
log.error("Failure during startup", e); log.error("Failure during startup", e);
SwingUtilities.invokeLater(() -> SwingUtilities.invokeLater(() ->
new FatalErrorDialog("OpenOSRS has encountered an unexpected error during startup.") new FatalErrorDialog("OpenOSRS has encountered an unexpected error during startup.")
.addHelpButtons()
.open()); .open());
} }
finally finally
@@ -371,6 +381,11 @@ public class RuneLite
} }
applet.start(); applet.start();
if (applet instanceof OAuthApi)
{
setupJxAuth((OAuthApi) applet);
}
} }
SplashScreen.stage(.57, null, "Loading configuration"); SplashScreen.stage(.57, null, "Loading configuration");
@@ -661,4 +676,83 @@ public class RuneLite
System.setProperty(key, value); System.setProperty(key, value);
} }
} }
private void setupJxAuth(OAuthApi oAuthApi)
{
String accessToken = System.getenv("JX_ACCESS_TOKEN");
if (Strings.isNullOrEmpty(accessToken))
{
return;
}
try
{
log.info("Initializing OTL token requester with access token");
oAuthApi.setOtlTokenRequester(url ->
{
CompletableFuture<OtlTokenResponse> f = new CompletableFuture<>();
okHttpClient.newCall(new Request.Builder()
.url(url)
.header("Authorization", "Bearer " + accessToken)
.get()
.build())
.enqueue(new Callback()
{
private void complete(boolean success, String token)
{
f.complete(new OtlTokenResponse()
{
@Override
public boolean isSuccess()
{
return success;
}
@Override
public String getToken()
{
return token;
}
});
}
@Override
public void onFailure(Call call, IOException e)
{
log.error("HTTP error while performing OTL request", e);
complete(false, null);
}
@Override
public void onResponse(Call call, Response response) throws IOException
{
if (response.code() != 200)
{
log.error("Non-OK response performing OTL request: {}", response.code());
complete(false, null);
response.close();
return;
}
if (response.body() == null)
{
log.error("OK response with empty body from OTL request");
complete(false, null);
response.close();
return;
}
log.debug("Successful OTL response");
complete(true, response.body().string());
response.close();
}
});
return f;
});
}
catch (LinkageError ex)
{
log.error("error setting up OTL requester", ex);
}
}
} }

View File

@@ -50,6 +50,7 @@ public class RuneLiteProperties
private static final String PLUGINHUB_VERSION = "runelite.pluginhub.version"; private static final String PLUGINHUB_VERSION = "runelite.pluginhub.version";
private static final String API_BASE = "runelite.api.base"; private static final String API_BASE = "runelite.api.base";
private static final String RUNELITE_CONFIG = "runelite.config"; private static final String RUNELITE_CONFIG = "runelite.config";
private static final String OSRS_TWITTER_LINK = "runelite.osrstwitter.link";
@Getter(AccessLevel.PACKAGE) @Getter(AccessLevel.PACKAGE)
private static final Properties properties = new Properties(); private static final Properties properties = new Properties();
@@ -148,4 +149,9 @@ public class RuneLiteProperties
{ {
return properties.getProperty(RUNELITE_CONFIG); return properties.getProperty(RUNELITE_CONFIG);
} }
public static String getOSRSTwitterLink()
{
return properties.getProperty(OSRS_TWITTER_LINK);
}
} }

View File

@@ -24,13 +24,46 @@
*/ */
package net.runelite.client; package net.runelite.client;
import com.google.common.base.Strings;
import java.util.Collections; import java.util.Collections;
import java.util.Map; import java.util.Map;
import javax.swing.SwingUtilities;
import lombok.Data; import lombok.Data;
import net.runelite.client.ui.FatalErrorDialog;
import net.runelite.client.util.LinkBrowser;
@Data @Data
public class RuntimeConfig public class RuntimeConfig
{ {
private Map<String, ?> props = Collections.emptyMap(); private Map<String, ?> props = Collections.emptyMap();
private Map<String, String> sysProps = Collections.emptyMap(); private Map<String, String> sysProps = Collections.emptyMap();
private String outageMessage;
private Map<String, String> outageLinks;
public boolean showOutageMessage()
{
if (Strings.isNullOrEmpty(getOutageMessage()))
{
return false;
}
SwingUtilities.invokeLater(() ->
{
FatalErrorDialog fed = new FatalErrorDialog(getOutageMessage());
if (getOutageLinks() != null)
{
for (Map.Entry<String, String> e : getOutageLinks().entrySet())
{
fed.addButton(e.getKey(), () -> LinkBrowser.browse(e.getValue()));
}
}
else
{
fed.addButton("OSRS Twitter", () -> LinkBrowser.browse(RuneLiteProperties.getOSRSTwitterLink()));
}
fed.open();
});
return true;
}
} }

View File

@@ -31,7 +31,10 @@ import java.nio.file.Files;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Supplier; import java.util.function.Supplier;
import javax.annotation.Nullable;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import net.runelite.http.api.RuneLiteAPI; import net.runelite.http.api.RuneLiteAPI;
import okhttp3.Call; import okhttp3.Call;
@@ -41,7 +44,7 @@ import okhttp3.Request;
import okhttp3.Response; import okhttp3.Response;
@Slf4j @Slf4j
class RuntimeConfigLoader implements Supplier<RuntimeConfig> public class RuntimeConfigLoader implements Supplier<RuntimeConfig>
{ {
private final OkHttpClient okHttpClient; private final OkHttpClient okHttpClient;
private final CompletableFuture<RuntimeConfig> configFuture; private final CompletableFuture<RuntimeConfig> configFuture;
@@ -66,6 +69,19 @@ class RuntimeConfigLoader implements Supplier<RuntimeConfig>
} }
} }
@Nullable
public RuntimeConfig tryGet()
{
try
{
return configFuture.get(0, TimeUnit.SECONDS);
}
catch (InterruptedException | ExecutionException | TimeoutException e)
{
return null;
}
}
private CompletableFuture<RuntimeConfig> fetch() private CompletableFuture<RuntimeConfig> fetch()
{ {
CompletableFuture<RuntimeConfig> future = new CompletableFuture<>(); CompletableFuture<RuntimeConfig> future = new CompletableFuture<>();

View File

@@ -121,7 +121,7 @@ public class ClientThread implements Executor
} }
catch (Throwable e) catch (Throwable e)
{ {
log.warn("Exception in invoke", e); log.error("Exception in invoke", e);
} }
if (remove) if (remove)
{ {

View File

@@ -88,6 +88,7 @@ import lombok.extern.slf4j.Slf4j;
import net.runelite.api.Client; import net.runelite.api.Client;
import net.runelite.api.Player; import net.runelite.api.Player;
import net.runelite.api.coords.WorldPoint; import net.runelite.api.coords.WorldPoint;
import net.runelite.api.events.AccountHashChanged;
import net.runelite.api.events.PlayerChanged; import net.runelite.api.events.PlayerChanged;
import net.runelite.api.events.UsernameChanged; import net.runelite.api.events.UsernameChanged;
import net.runelite.api.events.WorldChanged; import net.runelite.api.events.WorldChanged;
@@ -115,6 +116,7 @@ public class ConfigManager
private static final String RSPROFILE_TYPE = "type"; private static final String RSPROFILE_TYPE = "type";
private static final String RSPROFILE_LOGIN_HASH = "loginHash"; private static final String RSPROFILE_LOGIN_HASH = "loginHash";
private static final String RSPROFILE_LOGIN_SALT = "loginSalt"; private static final String RSPROFILE_LOGIN_SALT = "loginSalt";
private static final String RSPROFILE_ACCOUNT_HASH = "accountHash";
private static final DateFormat TIME_FORMAT = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss"); private static final DateFormat TIME_FORMAT = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss");
@@ -544,17 +546,17 @@ public class ConfigManager
displayName = p.getName(); displayName = p.getName();
} }
String username = client.getUsername(); RuneScapeProfile prof = findRSProfile(getRSProfiles(), RuneScapeProfileType.getCurrent(client), displayName, true);
if (Strings.isNullOrEmpty(username)) if (prof == null)
{ {
log.warn("trying to create profile without a set username"); log.warn("trying to create a profile while not logged in");
return; return;
} }
RuneScapeProfile prof = findRSProfile(getRSProfiles(), username, RuneScapeProfileType.getCurrent(client), displayName, true);
rsProfileKey = prof.getKey(); rsProfileKey = prof.getKey();
this.rsProfileKey = rsProfileKey; this.rsProfileKey = rsProfileKey;
log.debug("RS profile changed to {}", rsProfileKey);
eventBus.post(new RuneScapeProfileChanged()); eventBus.post(new RuneScapeProfileChanged());
} }
setConfiguration(groupName, rsProfileKey, key, value); setConfiguration(groupName, rsProfileKey, key, value);
@@ -790,6 +792,10 @@ public class ConfigManager
{ {
return Integer.parseInt(str); return Integer.parseInt(str);
} }
if (type == long.class || type == Long.class)
{
return Long.parseLong(str);
}
if (type == double.class || type == Double.class) if (type == double.class || type == Double.class)
{ {
return Double.parseDouble(str); return Double.parseDouble(str);
@@ -1129,10 +1135,12 @@ public class ConfigManager
return profileKeys.stream() return profileKeys.stream()
.map(key -> .map(key ->
{ {
Long accid = getConfiguration(RSPROFILE_GROUP, key, RSPROFILE_ACCOUNT_HASH, long.class);
RuneScapeProfile prof = new RuneScapeProfile( RuneScapeProfile prof = new RuneScapeProfile(
getConfiguration(RSPROFILE_GROUP, key, RSPROFILE_DISPLAY_NAME), getConfiguration(RSPROFILE_GROUP, key, RSPROFILE_DISPLAY_NAME),
getConfiguration(RSPROFILE_GROUP, key, RSPROFILE_TYPE, RuneScapeProfileType.class), getConfiguration(RSPROFILE_GROUP, key, RSPROFILE_TYPE, RuneScapeProfileType.class),
getConfiguration(RSPROFILE_GROUP, key, RSPROFILE_LOGIN_HASH, byte[].class), getConfiguration(RSPROFILE_GROUP, key, RSPROFILE_LOGIN_HASH, byte[].class),
accid == null ? RuneScapeProfile.ACCOUNT_HASH_INVALID : accid,
key key
); );
@@ -1141,26 +1149,54 @@ public class ConfigManager
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
private synchronized RuneScapeProfile findRSProfile(List<RuneScapeProfile> profiles, String username, RuneScapeProfileType type, String displayName, boolean create) private synchronized RuneScapeProfile findRSProfile(List<RuneScapeProfile> profiles, RuneScapeProfileType type, String displayName, boolean create)
{ {
byte[] salt = getConfiguration(RSPROFILE_GROUP, RSPROFILE_LOGIN_SALT, byte[].class); String username = client.getUsername();
if (salt == null) long accountHash = client.getAccountHash();
if (accountHash == RuneScapeProfile.ACCOUNT_HASH_INVALID && username == null)
{ {
salt = new byte[15]; return null;
new SecureRandom()
.nextBytes(salt);
log.info("creating new salt as there is no existing one {}", Base64.getUrlEncoder().encodeToString(salt));
setConfiguration(RSPROFILE_GROUP, RSPROFILE_LOGIN_SALT, salt);
} }
Hasher h = Hashing.sha512().newHasher(); final byte[] loginHash;
h.putBytes(salt); byte[] salt = null;
h.putString(username.toLowerCase(Locale.US), StandardCharsets.UTF_8); if (username != null)
byte[] loginHash = h.hash().asBytes(); {
salt = getConfiguration(RSPROFILE_GROUP, RSPROFILE_LOGIN_SALT, byte[].class);
if (salt == null)
{
salt = new byte[15];
new SecureRandom()
.nextBytes(salt);
log.info("creating new salt as there is no existing one {}", Base64.getUrlEncoder().encodeToString(salt));
setConfiguration(RSPROFILE_GROUP, RSPROFILE_LOGIN_SALT, salt);
}
Set<RuneScapeProfile> matches = profiles.stream() Hasher h = Hashing.sha512().newHasher();
.filter(p -> Arrays.equals(p.getLoginHash(), loginHash) && p.getType() == type) h.putBytes(salt);
h.putString(username.toLowerCase(Locale.US), StandardCharsets.UTF_8);
loginHash = h.hash().asBytes();
}
else
{
loginHash = null;
}
Set<RuneScapeProfile> matches = Collections.emptySet();
if (accountHash != RuneScapeProfile.ACCOUNT_HASH_INVALID)
{
matches = profiles.stream()
.filter(p -> p.getType() == type && accountHash == p.getAccountHash())
.collect(Collectors.toSet()); .collect(Collectors.toSet());
}
if (matches.isEmpty() && loginHash != null)
{
matches = profiles.stream()
.filter(p -> p.getType() == type && Arrays.equals(loginHash, p.getLoginHash()))
.collect(Collectors.toSet());
}
if (matches.size() > 1) if (matches.size() > 1)
{ {
@@ -1169,7 +1205,21 @@ public class ConfigManager
if (matches.size() >= 1) if (matches.size() >= 1)
{ {
return matches.iterator().next(); RuneScapeProfile profile = matches.iterator().next();
if (profile.getAccountHash() == RuneScapeProfile.ACCOUNT_HASH_INVALID && accountHash != RuneScapeProfile.ACCOUNT_HASH_INVALID)
{
int upgrades = 0;
for (RuneScapeProfile p : profiles)
{
if (p.getAccountHash() == RuneScapeProfile.ACCOUNT_HASH_INVALID && Arrays.equals(p.getLoginHash(), loginHash))
{
setConfiguration(RSPROFILE_GROUP, p.getKey(), RSPROFILE_ACCOUNT_HASH, accountHash);
upgrades++;
}
}
log.info("Attaching account id to {} profiles", upgrades);
}
return profile;
} }
if (!create) if (!create)
@@ -1179,22 +1229,41 @@ public class ConfigManager
// generate the new key deterministically so if you "create" the same profile on 2 different clients it doesn't duplicate // generate the new key deterministically so if you "create" the same profile on 2 different clients it doesn't duplicate
Set<String> keys = profiles.stream().map(RuneScapeProfile::getKey).collect(Collectors.toSet()); Set<String> keys = profiles.stream().map(RuneScapeProfile::getKey).collect(Collectors.toSet());
byte[] key = Arrays.copyOf(loginHash, 6); byte[] key = accountHash == RuneScapeProfile.ACCOUNT_HASH_INVALID
? Arrays.copyOf(loginHash, 6)
: new byte[]
{
(byte) accountHash,
(byte) (accountHash >> 8),
(byte) (accountHash >> 16),
(byte) (accountHash >> 24),
(byte) (accountHash >> 32),
(byte) (accountHash >> 40),
};
key[0] += type.ordinal(); key[0] += type.ordinal();
for (int i = 0; i < 0xFF; i++, key[1]++) for (int i = 0; i < 0xFF; i++, key[1]++)
{ {
String keyStr = RSPROFILE_GROUP + "." + Base64.getUrlEncoder().encodeToString(key); String keyStr = RSPROFILE_GROUP + "." + Base64.getUrlEncoder().encodeToString(key);
if (!keys.contains(keyStr)) if (!keys.contains(keyStr))
{ {
log.info("creating new profile {} for user {} ({}) salt {}", keyStr, username, type, Base64.getUrlEncoder().encodeToString(salt)); log.info("creating new profile {} for username {} account hash {} ({}) salt {}",
keyStr, username, accountHash, type,
salt == null ? "null" : Base64.getUrlEncoder().encodeToString(salt));
setConfiguration(RSPROFILE_GROUP, keyStr, RSPROFILE_LOGIN_HASH, loginHash); if (loginHash != null)
{
setConfiguration(RSPROFILE_GROUP, keyStr, RSPROFILE_LOGIN_HASH, loginHash);
}
if (accountHash != RuneScapeProfile.ACCOUNT_HASH_INVALID)
{
setConfiguration(RSPROFILE_GROUP, keyStr, RSPROFILE_ACCOUNT_HASH, accountHash);
}
setConfiguration(RSPROFILE_GROUP, keyStr, RSPROFILE_TYPE, type); setConfiguration(RSPROFILE_GROUP, keyStr, RSPROFILE_TYPE, type);
if (displayName != null) if (displayName != null)
{ {
setConfiguration(RSPROFILE_GROUP, keyStr, RSPROFILE_DISPLAY_NAME, displayName); setConfiguration(RSPROFILE_GROUP, keyStr, RSPROFILE_DISPLAY_NAME, displayName);
} }
return new RuneScapeProfile(displayName, type, loginHash, keyStr); return new RuneScapeProfile(displayName, type, loginHash, accountHash, keyStr);
} }
} }
throw new RuntimeException("too many rs profiles"); throw new RuntimeException("too many rs profiles");
@@ -1208,7 +1277,7 @@ public class ConfigManager
} }
List<RuneScapeProfile> profiles = getRSProfiles(); List<RuneScapeProfile> profiles = getRSProfiles();
RuneScapeProfile prof = findRSProfile(profiles, client.getUsername(), RuneScapeProfileType.getCurrent(client), null, false); RuneScapeProfile prof = findRSProfile(profiles, RuneScapeProfileType.getCurrent(client), null, false);
String key = prof == null ? null : prof.getKey(); String key = prof == null ? null : prof.getKey();
if (Objects.equals(key, rsProfileKey)) if (Objects.equals(key, rsProfileKey))
@@ -1217,6 +1286,7 @@ public class ConfigManager
} }
rsProfileKey = key; rsProfileKey = key;
log.debug("RS profile changed to {}", key);
eventBus.post(new RuneScapeProfileChanged()); eventBus.post(new RuneScapeProfileChanged());
} }
@@ -1226,6 +1296,12 @@ public class ConfigManager
updateRSProfile(); updateRSProfile();
} }
@Subscribe
private void onAccountHashChanged(AccountHashChanged ev)
{
updateRSProfile();
}
@Subscribe @Subscribe
private void onWorldChanged(WorldChanged ev) private void onWorldChanged(WorldChanged ev)
{ {

View File

@@ -33,9 +33,12 @@ import lombok.Data;
@Data @Data
public class RuneScapeProfile public class RuneScapeProfile
{ {
public static final int ACCOUNT_HASH_INVALID = -1;
private final String displayName; private final String displayName;
private final RuneScapeProfileType type; private final RuneScapeProfileType type;
private final byte[] loginHash; private final byte[] loginHash;
private final long accountHash;
/** /**
* Profile key used to save configs for this profile to the config store. This will * Profile key used to save configs for this profile to the config store. This will

View File

@@ -110,10 +110,8 @@ public class ChatCommandsPlugin extends Plugin
private static final String TEAM_SIZES = "(?:\\d+(?:\\+|-\\d+)? players|Solo)"; private static final String TEAM_SIZES = "(?:\\d+(?:\\+|-\\d+)? players|Solo)";
private static final Pattern RAIDS_PB_PATTERN = Pattern.compile("<col=ef20ff>Congratulations - your raid is complete!</col><br>Team size: <col=ff0000>" + TEAM_SIZES + "</col> Duration:</col> <col=ff0000>(?<pb>[0-9:]+(?:\\.[0-9]+)?)</col> \\(new personal best\\)</col>"); private static final Pattern RAIDS_PB_PATTERN = Pattern.compile("<col=ef20ff>Congratulations - your raid is complete!</col><br>Team size: <col=ff0000>" + TEAM_SIZES + "</col> Duration:</col> <col=ff0000>(?<pb>[0-9:]+(?:\\.[0-9]+)?)</col> \\(new personal best\\)</col>");
private static final Pattern RAIDS_DURATION_PATTERN = Pattern.compile("<col=ef20ff>Congratulations - your raid is complete!</col><br>Team size: <col=ff0000>" + TEAM_SIZES + "</col> Duration:</col> <col=ff0000>[0-9:.]+</col> Personal best: </col><col=ff0000>(?<pb>[0-9:]+(?:\\.[0-9]+)?)</col>"); private static final Pattern RAIDS_DURATION_PATTERN = Pattern.compile("<col=ef20ff>Congratulations - your raid is complete!</col><br>Team size: <col=ff0000>" + TEAM_SIZES + "</col> Duration:</col> <col=ff0000>[0-9:.]+</col> Personal best: </col><col=ff0000>(?<pb>[0-9:]+(?:\\.[0-9]+)?)</col>");
private static final Pattern TOB_WAVE_PB_PATTERN = Pattern.compile("Theatre of Blood wave completion time: <col=ff0000>(?<pb>[0-9:]+(?:\\.[0-9]+)?)</col> \\(new personal best\\)"); private static final Pattern KILL_DURATION_PATTERN = Pattern.compile("(?i)(?:(?:Fight |Lap |Challenge |Corrupted challenge )?duration:|Subdued in|(?<!total )completion time:) <col=[0-9a-f]{6}>[0-9:.]+</col>\\. Personal best: (?:<col=ff0000>)?(?<pb>[0-9:]+(?:\\.[0-9]+)?)");
private static final Pattern TOB_WAVE_DURATION_PATTERN = Pattern.compile("Theatre of Blood wave completion time: <col=ff0000>[0-9:.]+</col>\\. Personal best: (?<pb>[0-9:]+(?:\\.[0-9]+)?)"); private static final Pattern NEW_PB_PATTERN = Pattern.compile("(?i)(?:(?:Fight |Lap |Challenge |Corrupted challenge )?duration:|Subdued in|(?<!total )completion time:) <col=[0-9a-f]{6}>(?<pb>[0-9:]+(?:\\.[0-9]+)?)</col> \\(new personal best\\)");
private static final Pattern KILL_DURATION_PATTERN = Pattern.compile("(?i)(?:(?:Fight |Lap |Challenge |Corrupted challenge )?duration:|Subdued in) <col=[0-9a-f]{6}>[0-9:.]+</col>\\. Personal best: (?:<col=ff0000>)?(?<pb>[0-9:]+(?:\\.[0-9]+)?)");
private static final Pattern NEW_PB_PATTERN = Pattern.compile("(?i)(?:(?:Fight |Lap |Challenge |Corrupted challenge )?duration:|Subdued in) <col=[0-9a-f]{6}>(?<pb>[0-9:]+(?:\\.[0-9]+)?)</col> \\(new personal best\\)");
private static final Pattern DUEL_ARENA_WINS_PATTERN = Pattern.compile("You (were defeated|won)! You have(?: now)? won ([\\d,]+|one) duels?"); private static final Pattern DUEL_ARENA_WINS_PATTERN = Pattern.compile("You (were defeated|won)! You have(?: now)? won ([\\d,]+|one) duels?");
private static final Pattern DUEL_ARENA_LOSSES_PATTERN = Pattern.compile("You have(?: now)? lost ([\\d,]+|one) duels?"); private static final Pattern DUEL_ARENA_LOSSES_PATTERN = Pattern.compile("You have(?: now)? lost ([\\d,]+|one) duels?");
private static final Pattern ADVENTURE_LOG_TITLE_PATTERN = Pattern.compile("The Exploits of (.+)"); private static final Pattern ADVENTURE_LOG_TITLE_PATTERN = Pattern.compile("The Exploits of (.+)");
@@ -123,6 +121,7 @@ public class ChatCommandsPlugin extends Plugin
private static final Pattern HS_KC_FLOOR_PATTERN = Pattern.compile("You have completed Floor (\\d) of the Hallowed Sepulchre! Total completions: <col=ff0000>([0-9,]+)</col>\\."); private static final Pattern HS_KC_FLOOR_PATTERN = Pattern.compile("You have completed Floor (\\d) of the Hallowed Sepulchre! Total completions: <col=ff0000>([0-9,]+)</col>\\.");
private static final Pattern HS_KC_GHC_PATTERN = Pattern.compile("You have opened the Grand Hallowed Coffin <col=ff0000>([0-9,]+)</col> times?!"); private static final Pattern HS_KC_GHC_PATTERN = Pattern.compile("You have opened the Grand Hallowed Coffin <col=ff0000>([0-9,]+)</col> times?!");
private static final Pattern COLLECTION_LOG_ITEM_PATTERN = Pattern.compile("New item added to your collection log: (.*)"); private static final Pattern COLLECTION_LOG_ITEM_PATTERN = Pattern.compile("New item added to your collection log: (.*)");
private static final Pattern GUARDIANS_OF_THE_RIFT_PATTERN = Pattern.compile("Amount of Rifts you have closed: <col=ff0000>([0-9,]+)</col>.");
private static final String TOTAL_LEVEL_COMMAND_STRING = "!total"; private static final String TOTAL_LEVEL_COMMAND_STRING = "!total";
private static final String PRICE_COMMAND_STRING = "!price"; private static final String PRICE_COMMAND_STRING = "!price";
@@ -469,18 +468,6 @@ public class ChatCommandsPlugin extends Plugin
matchPb(matcher); matchPb(matcher);
} }
matcher = TOB_WAVE_PB_PATTERN.matcher(message);
if (matcher.find())
{
matchPb(matcher);
}
matcher = TOB_WAVE_DURATION_PATTERN.matcher(message);
if (matcher.find())
{
matchPb(matcher);
}
matcher = HS_PB_PATTERN.matcher(message); matcher = HS_PB_PATTERN.matcher(message);
if (matcher.find()) if (matcher.find())
{ {
@@ -538,6 +525,13 @@ public class ChatCommandsPlugin extends Plugin
} }
} }
} }
matcher = GUARDIANS_OF_THE_RIFT_PATTERN.matcher(message);
if (matcher.find())
{
int kc = Integer.parseInt(matcher.group(1));
setKc("Guardians of the Rift", kc);
}
} }
@VisibleForTesting @VisibleForTesting
@@ -1970,6 +1964,7 @@ public class ChatCommandsPlugin extends Plugin
case "tob hm": case "tob hm":
case "tob hard mode": case "tob hard mode":
case "tob hard": case "tob hard":
case "hmt":
return "Theatre of Blood Hard Mode"; return "Theatre of Blood Hard Mode";
// agility course // agility course
@@ -2152,6 +2147,11 @@ public class ChatCommandsPlugin extends Plugin
case "jad 6": case "jad 6":
return "TzHaar-Ket-Rak's Sixth Challenge"; return "TzHaar-Ket-Rak's Sixth Challenge";
// Guardians of the Rift
case "gotr":
case "runetodt":
return "Guardians of the Rift";
default: default:
return WordUtils.capitalize(boss); return WordUtils.capitalize(boss);
} }

View File

@@ -312,6 +312,7 @@ enum DiscordGameEventType
MG_TZHAAR_FIGHT_PITS("Tzhaar Fight Pits", DiscordAreaType.MINIGAMES, 9552), MG_TZHAAR_FIGHT_PITS("Tzhaar Fight Pits", DiscordAreaType.MINIGAMES, 9552),
MG_VARROCK_RAT_PITS("Varrock Rat Pits", DiscordAreaType.MINIGAMES, 11599), MG_VARROCK_RAT_PITS("Varrock Rat Pits", DiscordAreaType.MINIGAMES, 11599),
MG_VOLCANIC_MINE("Volcanic Mine", DiscordAreaType.MINIGAMES, 15263, 15262), MG_VOLCANIC_MINE("Volcanic Mine", DiscordAreaType.MINIGAMES, 15263, 15262),
MG_GUARDIANS_OF_THE_RIFT("Guardians of the Rift", DiscordAreaType.MINIGAMES, 14484),
// Raids // Raids
RAIDS_CHAMBERS_OF_XERIC("Chambers of Xeric", DiscordAreaType.RAIDS, 12889, 13136, 13137, 13138, 13139, 13140, 13141, 13145, 13393, 13394, 13395, 13396, 13397, 13401), RAIDS_CHAMBERS_OF_XERIC("Chambers of Xeric", DiscordAreaType.RAIDS, 12889, 13136, 13137, 13138, 13139, 13140, 13141, 13145, 13393, 13394, 13395, 13396, 13397, 13401),

View File

@@ -185,7 +185,7 @@ public class GrandExchangePlugin extends Plugin
private boolean wasFuzzySearch; private boolean wasFuzzySearch;
private String machineUuid; private String machineUuid;
private String lastUsername; private long lastAccount;
private int tradeSeq; private int tradeSeq;
/** /**
@@ -298,7 +298,8 @@ public class GrandExchangePlugin extends Plugin
clientToolbar.removeNavigation(button); clientToolbar.removeNavigation(button);
mouseManager.unregisterMouseListener(inputListener); mouseManager.unregisterMouseListener(inputListener);
keyManager.unregisterKeyListener(inputListener); keyManager.unregisterKeyListener(inputListener);
lastUsername = machineUuid = null; machineUuid = null;
lastAccount = -1L;
tradeSeq = 0; tradeSeq = 0;
} }
@@ -893,13 +894,13 @@ public class GrandExchangePlugin extends Plugin
private String getMachineUuid() private String getMachineUuid()
{ {
String username = client.getUsername(); long accountHash = client.getAccountHash();
if (lastUsername == username) if (lastAccount == accountHash)
{ {
return machineUuid; return machineUuid;
} }
lastUsername = username; lastAccount = accountHash;
try try
{ {
@@ -922,7 +923,7 @@ public class GrandExchangePlugin extends Plugin
hasher.putBytes(hardwareAddress); hasher.putBytes(hardwareAddress);
} }
} }
hasher.putUnencodedChars(username); hasher.putLong(accountHash);
machineUuid = hasher.hash().toString(); machineUuid = hasher.hash().toString();
tradeSeq = 0; tradeSeq = 0;
return machineUuid; return machineUuid;

View File

@@ -263,6 +263,12 @@ public class LootTrackerPlugin extends Plugin
private static final String TEMPOROSS_LOOT_STRING = "You found some loot: "; private static final String TEMPOROSS_LOOT_STRING = "You found some loot: ";
private static final int TEMPOROSS_REGION = 12588; private static final int TEMPOROSS_REGION = 12588;
// Guardians of the Rift
private static final String GUARDIANS_OF_THE_RIFT_EVENT = "Guardians of the Rift";
private static final String INTRICATE_POUCH_EVENT = "Intricate pouch";
private static final String GUARDIANS_OF_THE_RIFT_LOOT_STRING = "You found some loot: ";
private static final int GUARDIANS_OF_THE_RIFT_REGION = 14484;
// Mahogany Homes // Mahogany Homes
private static final String MAHOGANY_CRATE_EVENT = "Supply crate (Mahogany Homes)"; private static final String MAHOGANY_CRATE_EVENT = "Supply crate (Mahogany Homes)";
@@ -480,6 +486,13 @@ public class LootTrackerPlugin extends Plugin
clientThread.invokeLater(() -> clientThread.invokeLater(() ->
{ {
// convertToLootTrackerRecord requires item compositions to be available to get the item name,
// so it can't be run while the client is starting
if (client.getGameState().getState() < GameState.LOGIN_SCREEN.getState())
{
return false;
}
// convertToLootTrackerRecord must be called on client thread // convertToLootTrackerRecord must be called on client thread
List<LootTrackerRecord> records = loots.stream() List<LootTrackerRecord> records = loots.stream()
.map(this::convertToLootTrackerRecord) .map(this::convertToLootTrackerRecord)
@@ -489,6 +502,8 @@ public class LootTrackerPlugin extends Plugin
panel.clearRecords(); panel.clearRecords();
panel.addRecords(records); panel.addRecords(records);
}); });
return true;
}); });
}); });
} }
@@ -887,6 +902,12 @@ public class LootTrackerPlugin extends Plugin
return; return;
} }
if (regionID == GUARDIANS_OF_THE_RIFT_REGION && message.startsWith(GUARDIANS_OF_THE_RIFT_LOOT_STRING))
{
onInvChange(collectInvItems(LootRecordType.EVENT, GUARDIANS_OF_THE_RIFT_EVENT, client.getBoostedSkillLevel(Skill.RUNECRAFT)));
return;
}
if (message.equals(IMPLING_CATCH_MESSAGE)) if (message.equals(IMPLING_CATCH_MESSAGE))
{ {
onInvChange(collectInvItems(LootRecordType.EVENT, client.getLocalPlayer().getInteracting().getName())); onInvChange(collectInvItems(LootRecordType.EVENT, client.getLocalPlayer().getInteracting().getName()));
@@ -977,6 +998,9 @@ public class LootTrackerPlugin extends Plugin
case ItemID.CASKET_25590: case ItemID.CASKET_25590:
onInvChange(collectInvAndGroundItems(LootRecordType.EVENT, TEMPOROSS_CASKET_EVENT)); onInvChange(collectInvAndGroundItems(LootRecordType.EVENT, TEMPOROSS_CASKET_EVENT));
break; break;
case ItemID.INTRICATE_POUCH:
onInvChange(collectInvAndGroundItems(LootRecordType.EVENT, INTRICATE_POUCH_EVENT));
break;
case ItemID.SIMPLE_LOCKBOX_25647: case ItemID.SIMPLE_LOCKBOX_25647:
case ItemID.ELABORATE_LOCKBOX_25649: case ItemID.ELABORATE_LOCKBOX_25649:
case ItemID.ORNATE_LOCKBOX_25651: case ItemID.ORNATE_LOCKBOX_25651:

View File

@@ -826,4 +826,34 @@ public interface MenuEntrySwapperConfig extends Config
{ {
return false; return false;
} }
@ConfigItem(
keyName = "swapDepositPool",
name = "Deposit Pool - Only Runes",
description = "Swap Deposit with Deposit Runes on the Deposit Pool in Guardians of the Rift.",
section = objectSection
)
default boolean swapDepositPool()
{
return false;
}
enum UnchargedCellsMode
{
TAKE,
TAKE_1,
TAKE_5,
TAKE_10
}
@ConfigItem(
keyName = "swapUnchargedCells",
name = "Uncharged Cells",
description = "Swap the take option for Uncharged Cells in Guardians of the Rift.",
section = objectSection
)
default UnchargedCellsMode swapUnchargedCells()
{
return UnchargedCellsMode.TAKE;
}
} }

View File

@@ -71,6 +71,7 @@ import static net.runelite.client.plugins.menuentryswapper.MenuEntrySwapperConfi
import static net.runelite.client.plugins.menuentryswapper.MenuEntrySwapperConfig.KaramjaGlovesMode; import static net.runelite.client.plugins.menuentryswapper.MenuEntrySwapperConfig.KaramjaGlovesMode;
import static net.runelite.client.plugins.menuentryswapper.MenuEntrySwapperConfig.MorytaniaLegsMode; import static net.runelite.client.plugins.menuentryswapper.MenuEntrySwapperConfig.MorytaniaLegsMode;
import static net.runelite.client.plugins.menuentryswapper.MenuEntrySwapperConfig.RadasBlessingMode; import static net.runelite.client.plugins.menuentryswapper.MenuEntrySwapperConfig.RadasBlessingMode;
import static net.runelite.client.plugins.menuentryswapper.MenuEntrySwapperConfig.UnchargedCellsMode;
import net.runelite.client.util.Text; import net.runelite.client.util.Text;
@PluginDescriptor( @PluginDescriptor(
@@ -419,6 +420,12 @@ public class MenuEntrySwapperPlugin extends Plugin
swap("climb", "climb-up", () -> (shiftModifier() ? config.swapStairsShiftClick() : config.swapStairsLeftClick()) == MenuEntrySwapperConfig.StairsMode.CLIMB_UP); swap("climb", "climb-up", () -> (shiftModifier() ? config.swapStairsShiftClick() : config.swapStairsLeftClick()) == MenuEntrySwapperConfig.StairsMode.CLIMB_UP);
swap("climb", "climb-down", () -> (shiftModifier() ? config.swapStairsShiftClick() : config.swapStairsLeftClick()) == MenuEntrySwapperConfig.StairsMode.CLIMB_DOWN); swap("climb", "climb-down", () -> (shiftModifier() ? config.swapStairsShiftClick() : config.swapStairsLeftClick()) == MenuEntrySwapperConfig.StairsMode.CLIMB_DOWN);
swap("deposit", "deposit-runes", config::swapDepositPool);
swap("take", "uncharged cells", "take-1", () -> config.swapUnchargedCells() == UnchargedCellsMode.TAKE_1);
swap("take", "uncharged cells", "take-5", () -> config.swapUnchargedCells() == UnchargedCellsMode.TAKE_5);
swap("take", "uncharged cells", "take-10", () -> config.swapUnchargedCells() == UnchargedCellsMode.TAKE_10);
} }
public Swap swap(String option, String swappedOption, Supplier<Boolean> enabled) public Swap swap(String option, String swappedOption, Supplier<Boolean> enabled)

View File

@@ -42,7 +42,7 @@ class NpcAggroAreaNotWorkingOverlay extends OverlayPanel
this.plugin = plugin; this.plugin = plugin;
panelComponent.getChildren().add(LineComponent.builder() panelComponent.getChildren().add(LineComponent.builder()
.left("Unaggressive NPC timers will start working when you teleport far away or enter a dungeon.") .left("Unaggressive NPC timers require calibration. Teleport far away or enter a dungeon, then run until this overlay disappears.")
.build()); .build());
setPriority(OverlayPriority.LOW); setPriority(OverlayPriority.LOW);
@@ -53,7 +53,7 @@ class NpcAggroAreaNotWorkingOverlay extends OverlayPanel
@Override @Override
public Dimension render(Graphics2D graphics) public Dimension render(Graphics2D graphics)
{ {
if (!plugin.isActive() || plugin.getSafeCenters()[1] != null) if (plugin.getSafeCenters()[1] != null)
{ {
return null; return null;
} }

View File

@@ -0,0 +1,218 @@
/*
* Copyright (c) 2019 Spudjb <https://github.com/spudjb>
* Copyright (c) 2022 Adam <Adam@sigterm.info>
* 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.questlist;
import com.google.common.base.Strings;
import javax.inject.Inject;
import net.runelite.api.Client;
import net.runelite.api.ParamID;
import net.runelite.api.ScriptID;
import net.runelite.api.SoundEffectID;
import net.runelite.api.SpriteID;
import net.runelite.api.VarClientInt;
import net.runelite.api.Varbits;
import net.runelite.api.events.ScriptCallbackEvent;
import net.runelite.api.events.ScriptPostFired;
import net.runelite.api.events.VarClientIntChanged;
import net.runelite.api.events.VarbitChanged;
import net.runelite.api.widgets.JavaScriptCallback;
import net.runelite.api.widgets.Widget;
import net.runelite.api.widgets.WidgetInfo;
import net.runelite.api.widgets.WidgetPositionMode;
import net.runelite.api.widgets.WidgetType;
import net.runelite.client.callback.ClientThread;
import net.runelite.client.eventbus.Subscribe;
import net.runelite.client.game.chatbox.ChatboxPanelManager;
import net.runelite.client.game.chatbox.ChatboxTextInput;
import net.runelite.client.plugins.Plugin;
import net.runelite.client.plugins.PluginDescriptor;
@PluginDescriptor(
name = "Quest List",
description = "Adds a search filter to the quest list"
)
public class QuestListPlugin extends Plugin
{
private static final String MENU_OPEN = "Open";
private static final String MENU_CLOSE = "Close";
private static final String MENU_SEARCH = "Search";
@Inject
private Client client;
@Inject
private ChatboxPanelManager chatboxPanelManager;
@Inject
private ClientThread clientThread;
private ChatboxTextInput searchInput;
private Widget questSearchButton;
@Override
protected void startUp()
{
clientThread.invoke(this::addQuestButtons);
}
@Override
protected void shutDown()
{
Widget header = client.getWidget(WidgetInfo.QUESTLIST_BOX);
if (header != null)
{
header.deleteAllChildren();
}
}
@Subscribe
public void onScriptPostFired(ScriptPostFired event)
{
if (event.getScriptId() == ScriptID.QUESTLIST_INIT)
{
addQuestButtons();
}
}
private void addQuestButtons()
{
Widget header = client.getWidget(WidgetInfo.QUESTLIST_BOX);
if (header != null)
{
header.deleteAllChildren();
questSearchButton = header.createChild(-1, WidgetType.GRAPHIC);
questSearchButton.setSpriteId(SpriteID.GE_SEARCH);
questSearchButton.setOriginalWidth(18);
questSearchButton.setOriginalHeight(17);
questSearchButton.setXPositionMode(WidgetPositionMode.ABSOLUTE_RIGHT);
questSearchButton.setOriginalX(5);
questSearchButton.setOriginalY(0);
questSearchButton.setHasListener(true);
questSearchButton.setAction(1, MENU_OPEN);
questSearchButton.setOnOpListener((JavaScriptCallback) e -> openSearch());
questSearchButton.setName(MENU_SEARCH);
questSearchButton.revalidate();
}
}
@Subscribe
public void onVarbitChanged(VarbitChanged varbitChanged)
{
if (isChatboxOpen() && !isOnQuestTab())
{
chatboxPanelManager.close();
}
}
@Subscribe
public void onVarClientIntChanged(VarClientIntChanged varClientIntChanged)
{
if (varClientIntChanged.getIndex() == VarClientInt.INVENTORY_TAB.getIndex())
{
if (isChatboxOpen() && !isOnQuestTab())
{
chatboxPanelManager.close();
}
}
}
@Subscribe
public void onScriptCallbackEvent(ScriptCallbackEvent scriptCallbackEvent)
{
if (!"questFilter".equals(scriptCallbackEvent.getEventName()) || !isChatboxOpen())
{
return;
}
final String filter = searchInput.getValue();
if (Strings.isNullOrEmpty(filter))
{
return;
}
final int[] intStack = client.getIntStack();
final int intStackSize = client.getIntStackSize();
final int questStruct = intStack[intStackSize - 1];
final String questName = client.getStructComposition(questStruct)
.getStringValue(ParamID.QUEST_NAME);
intStack[intStackSize - 2] = questName.toLowerCase().contains(filter.toLowerCase()) ? 0 : 1;
}
private boolean isOnQuestTab()
{
return client.getVar(Varbits.QUEST_TAB) == 0 && client.getVar(VarClientInt.INVENTORY_TAB) == 2;
}
private boolean isChatboxOpen()
{
return searchInput != null && chatboxPanelManager.getCurrentInput() == searchInput;
}
private void closeSearch()
{
chatboxPanelManager.close();
redrawQuests();
client.playSoundEffect(SoundEffectID.UI_BOOP);
}
private void openSearch()
{
client.playSoundEffect(SoundEffectID.UI_BOOP);
questSearchButton.setAction(1, MENU_CLOSE);
questSearchButton.setOnOpListener((JavaScriptCallback) e -> closeSearch());
searchInput = chatboxPanelManager.openTextInput("Search quest list")
.onChanged(s -> redrawQuests())
.onDone(s -> false)
.onClose(() ->
{
redrawQuests();
questSearchButton.setOnOpListener((JavaScriptCallback) e -> openSearch());
questSearchButton.setAction(1, MENU_OPEN);
})
.build();
}
private void redrawQuests()
{
Widget w = client.getWidget(WidgetInfo.QUESTLIST_CONTAINER);
if (w == null)
{
return;
}
Object[] onVarTransmitListener = w.getOnVarTransmitListener();
if (onVarTransmitListener == null)
{
return;
}
clientThread.invokeLater(() ->
client.runScript(onVarTransmitListener));
}
}

View File

@@ -34,7 +34,6 @@ import java.awt.image.BufferedImage;
import java.time.temporal.ChronoUnit; import java.time.temporal.ChronoUnit;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.List; import java.util.List;
import java.util.Objects;
import javax.inject.Inject; import javax.inject.Inject;
import lombok.AccessLevel; import lombok.AccessLevel;
import lombok.Setter; import lombok.Setter;
@@ -123,7 +122,7 @@ public class XpTrackerPlugin extends Plugin
@VisibleForTesting @VisibleForTesting
private XpPanel xpPanel; private XpPanel xpPanel;
private XpWorldType lastWorldType; private XpWorldType lastWorldType;
private String lastUsername; private long lastAccount;
private long lastTickMillis = 0; private long lastTickMillis = 0;
private boolean fetchXp; // fetch lastXp for the online xp tracker private boolean fetchXp; // fetch lastXp for the online xp tracker
private long lastXp = 0; private long lastXp = 0;
@@ -162,6 +161,7 @@ public class XpTrackerPlugin extends Plugin
// Initialize the tracker & last xp if already logged in // Initialize the tracker & last xp if already logged in
fetchXp = true; fetchXp = true;
initializeTracker = true; initializeTracker = true;
lastAccount = -1L;
} }
@Override @Override
@@ -182,15 +182,15 @@ public class XpTrackerPlugin extends Plugin
// Check that the username changed or the world type changed. // Check that the username changed or the world type changed.
XpWorldType type = worldSetToType(client.getWorldType()); XpWorldType type = worldSetToType(client.getWorldType());
if (!Objects.equals(client.getUsername(), lastUsername) || lastWorldType != type) if (client.getAccountHash() != lastAccount || lastWorldType != type)
{ {
// Reset // Reset
log.debug("World change: {} -> {}, {} -> {}", log.debug("World change: {} -> {}, {} -> {}",
lastUsername, client.getUsername(), lastAccount, client.getAccountHash(),
firstNonNull(lastWorldType, "<unknown>"), firstNonNull(lastWorldType, "<unknown>"),
firstNonNull(type, "<unknown>")); firstNonNull(type, "<unknown>"));
lastUsername = client.getUsername(); lastAccount = client.getAccountHash();
// xp is not available until after login is finished, so fetch it on the next gametick // xp is not available until after login is finished, so fetch it on the next gametick
fetchXp = true; fetchXp = true;
lastWorldType = type; lastWorldType = type;

View File

@@ -29,7 +29,6 @@ package net.runelite.client.plugins.xpupdater;
import com.google.inject.Provides; import com.google.inject.Provides;
import java.io.IOException; import java.io.IOException;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.Objects;
import javax.inject.Inject; import javax.inject.Inject;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import net.runelite.api.Client; import net.runelite.api.Client;
@@ -74,7 +73,7 @@ public class XpUpdaterPlugin extends Plugin
@Inject @Inject
private OkHttpClient okHttpClient; private OkHttpClient okHttpClient;
private String lastUsername; private long lastAccount;
private boolean fetchXp; private boolean fetchXp;
private long lastXp; private long lastXp;
@@ -88,6 +87,7 @@ public class XpUpdaterPlugin extends Plugin
protected void startUp() protected void startUp()
{ {
fetchXp = true; fetchXp = true;
lastAccount = -1L;
} }
@Subscribe @Subscribe
@@ -96,9 +96,9 @@ public class XpUpdaterPlugin extends Plugin
GameState state = gameStateChanged.getGameState(); GameState state = gameStateChanged.getGameState();
if (state == GameState.LOGGED_IN) if (state == GameState.LOGGED_IN)
{ {
if (!Objects.equals(client.getUsername(), lastUsername)) if (lastAccount != client.getAccountHash())
{ {
lastUsername = client.getUsername(); lastAccount = client.getAccountHash();
fetchXp = true; fetchXp = true;
} }
} }
@@ -114,8 +114,8 @@ public class XpUpdaterPlugin extends Plugin
// Don't submit update unless xp threshold is reached // Don't submit update unless xp threshold is reached
if (Math.abs(totalXp - lastXp) > XP_THRESHOLD) if (Math.abs(totalXp - lastXp) > XP_THRESHOLD)
{ {
log.debug("Submitting update for {}", local.getName()); log.debug("Submitting update for {} accountHash {}", local.getName(), lastAccount);
update(local.getName()); update(lastAccount, local.getName());
lastXp = totalXp; lastXp = totalXp;
} }
} }
@@ -131,12 +131,12 @@ public class XpUpdaterPlugin extends Plugin
} }
} }
private void update(String username) private void update(long accountHash, String username)
{ {
EnumSet<WorldType> worldTypes = client.getWorldType(); EnumSet<WorldType> worldTypes = client.getWorldType();
username = username.replace(" ", "_"); username = username.replace(" ", "_");
updateCml(username, worldTypes); updateCml(username, worldTypes);
updateTempleosrs(username, worldTypes); updateTempleosrs(accountHash, username, worldTypes);
updateWom(username, worldTypes); updateWom(username, worldTypes);
} }
@@ -165,7 +165,7 @@ public class XpUpdaterPlugin extends Plugin
} }
} }
private void updateTempleosrs(String username, EnumSet<WorldType> worldTypes) private void updateTempleosrs(long accountHash, String username, EnumSet<WorldType> worldTypes)
{ {
if (config.templeosrs() if (config.templeosrs()
&& !worldTypes.contains(WorldType.SEASONAL) && !worldTypes.contains(WorldType.SEASONAL)
@@ -178,6 +178,7 @@ public class XpUpdaterPlugin extends Plugin
.addPathSegment("php") .addPathSegment("php")
.addPathSegment("add_datapoint.php") .addPathSegment("add_datapoint.php")
.addQueryParameter("player", username) .addQueryParameter("player", username)
.addQueryParameter("accountHash", Long.toString(accountHash))
.build(); .build();
Request request = new Request.Builder() Request request = new Request.Builder()

View File

@@ -60,6 +60,8 @@ import lombok.extern.slf4j.Slf4j;
import net.runelite.api.Client; import net.runelite.api.Client;
import net.runelite.client.RuneLite; import net.runelite.client.RuneLite;
import net.runelite.client.RuneLiteProperties; import net.runelite.client.RuneLiteProperties;
import net.runelite.client.RuntimeConfig;
import net.runelite.client.RuntimeConfigLoader;
import static net.runelite.client.rs.ClientUpdateCheckMode.AUTO; import static net.runelite.client.rs.ClientUpdateCheckMode.AUTO;
import static net.runelite.client.rs.ClientUpdateCheckMode.NONE; import static net.runelite.client.rs.ClientUpdateCheckMode.NONE;
import static net.runelite.client.rs.ClientUpdateCheckMode.VANILLA; import static net.runelite.client.rs.ClientUpdateCheckMode.VANILLA;
@@ -74,7 +76,7 @@ import okhttp3.Request;
import okhttp3.Response; import okhttp3.Response;
@Slf4j @Slf4j
@SuppressWarnings("deprecation") @SuppressWarnings({"deprecation", "removal"})
public class ClientLoader implements Supplier<Applet> public class ClientLoader implements Supplier<Applet>
{ {
private static final String INJECTED_CLIENT_NAME = "/injected-client.oprs"; private static final String INJECTED_CLIENT_NAME = "/injected-client.oprs";
@@ -87,16 +89,18 @@ public class ClientLoader implements Supplier<Applet>
private final ClientConfigLoader clientConfigLoader; private final ClientConfigLoader clientConfigLoader;
private ClientUpdateCheckMode updateCheckMode; private ClientUpdateCheckMode updateCheckMode;
private final WorldSupplier worldSupplier; private final WorldSupplier worldSupplier;
private final RuntimeConfigLoader runtimeConfigLoader;
private final String javConfigUrl; private final String javConfigUrl;
private Object client; private Object client;
public ClientLoader(OkHttpClient okHttpClient, ClientUpdateCheckMode updateCheckMode, String javConfigUrl) public ClientLoader(OkHttpClient okHttpClient, ClientUpdateCheckMode updateCheckMode, RuntimeConfigLoader runtimeConfigLoader, String javConfigUrl)
{ {
this.okHttpClient = okHttpClient; this.okHttpClient = okHttpClient;
this.clientConfigLoader = new ClientConfigLoader(okHttpClient); this.clientConfigLoader = new ClientConfigLoader(okHttpClient);
this.updateCheckMode = updateCheckMode; this.updateCheckMode = updateCheckMode;
this.worldSupplier = new WorldSupplier(okHttpClient); this.worldSupplier = new WorldSupplier(okHttpClient);
this.runtimeConfigLoader = runtimeConfigLoader;
this.javConfigUrl = javConfigUrl; this.javConfigUrl = javConfigUrl;
} }
@@ -183,12 +187,19 @@ public class ClientLoader implements Supplier<Applet>
return rs; return rs;
} }
catch (OutageException e)
{
return e;
}
catch (IOException | ClassNotFoundException | InstantiationException | IllegalAccessException catch (IOException | ClassNotFoundException | InstantiationException | IllegalAccessException
| VerificationException | SecurityException e) | VerificationException | SecurityException e)
{ {
log.error("Error loading RS!", e); log.error("Error loading RS!", e);
SwingUtilities.invokeLater(() -> FatalErrorDialog.showNetErrorWindow("loading the client", e)); if (!checkOutages())
{
SwingUtilities.invokeLater(() -> FatalErrorDialog.showNetErrorWindow("loading the client", e));
}
return e; return e;
} }
} }
@@ -213,6 +224,10 @@ public class ClientLoader implements Supplier<Applet>
catch (IOException e) catch (IOException e)
{ {
log.info("Failed to get jav_config from host \"{}\" ({})", url.host(), e.getMessage()); log.info("Failed to get jav_config from host \"{}\" ({})", url.host(), e.getMessage());
if (checkOutages())
{
throw new OutageException(e);
}
if (!javConfigUrl.equals(RuneLiteProperties.getJavConfig())) if (!javConfigUrl.equals(RuneLiteProperties.getJavConfig()))
{ {
@@ -301,6 +316,11 @@ public class ClientLoader implements Supplier<Applet>
} }
vanilla.position(0); vanilla.position(0);
if (!vanillaCacheIsInvalid && "false".equals(System.getProperty("runelite.updateVanilla")))
{
return;
}
// Start downloading the vanilla client // Start downloading the vanilla client
HttpUrl url; HttpUrl url;
if (config.isFallback()) if (config.isFallback())
@@ -412,6 +432,10 @@ public class ClientLoader implements Supplier<Applet>
catch (IOException e) catch (IOException e)
{ {
log.warn("Failed to download gamepack from \"{}\"", url, e); log.warn("Failed to download gamepack from \"{}\"", url, e);
if (checkOutages())
{
throw new OutageException(e);
}
// With fallback config do 1 attempt (there are no additional urls to try) // With fallback config do 1 attempt (there are no additional urls to try)
if (!javConfigUrl.equals(RuneLiteProperties.getJavConfig()) || config.isFallback() || attempt >= NUM_ATTEMPTS) if (!javConfigUrl.equals(RuneLiteProperties.getJavConfig()) || config.isFallback() || attempt >= NUM_ATTEMPTS)
@@ -438,6 +462,7 @@ public class ClientLoader implements Supplier<Applet>
SwingUtilities.invokeLater(() -> SwingUtilities.invokeLater(() ->
new FatalErrorDialog("The client-patch is missing from the classpath. If you are building " + new FatalErrorDialog("The client-patch is missing from the classpath. If you are building " +
"the client you need to re-run maven") "the client you need to re-run maven")
.addHelpButtons()
.addBuildingGuide() .addBuildingGuide()
.open()); .open());
throw new NullPointerException(); throw new NullPointerException();
@@ -605,7 +630,7 @@ public class ClientLoader implements Supplier<Applet>
Class<?> clientClass = classLoader.loadClass(initialClass); Class<?> clientClass = classLoader.loadClass(initialClass);
Applet rs = (Applet) clientClass.newInstance(); Applet rs = (Applet) clientClass.newInstance();
rs.setStub(new RSAppletStub(config)); rs.setStub(new RSAppletStub(config, runtimeConfigLoader));
if (rs instanceof Client) if (rs instanceof Client)
{ {
@@ -658,4 +683,22 @@ public class ClientLoader implements Supplier<Applet>
verifyJarEntry(je, chains); verifyJarEntry(je, chains);
} }
} }
private static class OutageException extends RuntimeException
{
private OutageException(Throwable cause)
{
super(cause);
}
}
private boolean checkOutages()
{
RuntimeConfig rtc = runtimeConfigLoader.tryGet();
if (rtc != null)
{
return rtc.showOutageMessage();
}
return false;
}
} }

View File

@@ -25,16 +25,28 @@
*/ */
package net.runelite.client.rs; package net.runelite.client.rs;
import java.applet.Applet;
import java.applet.AppletContext; import java.applet.AppletContext;
import java.applet.AppletStub; import java.applet.AppletStub;
import java.applet.AudioClip;
import java.awt.Image;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.util.Enumeration;
import java.util.Iterator;
import javax.swing.SwingUtilities;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import net.runelite.client.RuntimeConfig;
import net.runelite.client.RuntimeConfigLoader;
import net.runelite.client.ui.FatalErrorDialog;
@RequiredArgsConstructor @RequiredArgsConstructor
class RSAppletStub implements AppletStub class RSAppletStub implements AppletStub
{ {
private final RSConfig config; private final RSConfig config;
private final RuntimeConfigLoader runtimeConfigLoader;
@Override @Override
public boolean isActive() public boolean isActive()
@@ -70,7 +82,89 @@ class RSAppletStub implements AppletStub
@Override @Override
public AppletContext getAppletContext() public AppletContext getAppletContext()
{ {
return null; return new AppletContext()
{
@Override
public AudioClip getAudioClip(URL url)
{
return null;
}
@Override
public Image getImage(URL url)
{
return null;
}
@Override
public Applet getApplet(String name)
{
return null;
}
@Override
public Enumeration<Applet> getApplets()
{
return null;
}
@Override
public void showDocument(URL url)
{
if (url.getPath().startsWith("/error_game_"))
{
try
{
RuntimeConfig rtc = runtimeConfigLoader.get();
if (rtc.showOutageMessage())
{
return;
}
}
catch (Exception e)
{
}
String code = url.getPath()
.replace("/", "")
.replace(".ws", "");
SwingUtilities.invokeLater(() ->
new FatalErrorDialog("OldSchool RuneScape has crashed with the message: " + code + "")
.setTitle("OpenOSRS", "OldSchool RuneScape has crashed")
.addHelpButtons()
.open());
}
}
@Override
public void showDocument(URL url, String target)
{
showDocument(url);
}
@Override
public void showStatus(String status)
{
}
@Override
public void setStream(String key, InputStream stream) throws IOException
{
}
@Override
public InputStream getStream(String key)
{
return null;
}
@Override
public Iterator<String> getStreamKeys()
{
return null;
}
};
} }
@Override @Override

View File

@@ -61,6 +61,7 @@ public class FatalErrorDialog extends JDialog
private final JPanel rightColumn = new JPanel(); private final JPanel rightColumn = new JPanel();
private final Font font = new Font(Font.DIALOG, Font.PLAIN, 12); private final Font font = new Font(Font.DIALOG, Font.PLAIN, 12);
private final JLabel title;
public FatalErrorDialog(String message) public FatalErrorDialog(String message)
{ {
@@ -114,7 +115,7 @@ public class FatalErrorDialog extends JDialog
leftPane.setBackground(ColorScheme.DARKER_GRAY_COLOR); leftPane.setBackground(ColorScheme.DARKER_GRAY_COLOR);
leftPane.setLayout(new BorderLayout()); leftPane.setLayout(new BorderLayout());
JLabel title = new JLabel("There was a fatal error starting OpenOSRS"); title = new JLabel("There was a fatal error starting OpenOSRS");
title.setForeground(Color.WHITE); title.setForeground(Color.WHITE);
title.setFont(font.deriveFont(16.f)); title.setFont(font.deriveFont(16.f));
title.setBorder(new EmptyBorder(10, 10, 10, 10)); title.setBorder(new EmptyBorder(10, 10, 10, 10));
@@ -137,13 +138,6 @@ public class FatalErrorDialog extends JDialog
rightColumn.setBackground(ColorScheme.DARK_GRAY_COLOR); rightColumn.setBackground(ColorScheme.DARK_GRAY_COLOR);
rightColumn.setMaximumSize(new Dimension(200, Integer.MAX_VALUE)); rightColumn.setMaximumSize(new Dimension(200, Integer.MAX_VALUE));
addButton("Open logs folder", () ->
{
LinkBrowser.open(RuneLite.LOGS_DIR.toString());
});
addButton("Get help on Discord", () -> LinkBrowser.browse(RuneLiteProperties.getDiscordInvite()));
addButton("Troubleshooting steps", () -> LinkBrowser.browse(RuneLiteProperties.getTroubleshootingLink()));
pane.add(rightColumn, BorderLayout.EAST); pane.add(rightColumn, BorderLayout.EAST);
} }
@@ -193,6 +187,20 @@ public class FatalErrorDialog extends JDialog
return this; return this;
} }
public FatalErrorDialog setTitle(String windowTitle, String header)
{
super.setTitle(windowTitle);
title.setText(header);
return this;
}
public FatalErrorDialog addHelpButtons()
{
return addButton("Open logs folder", () -> LinkBrowser.open(RuneLite.LOGS_DIR.toString()))
.addButton("Get help on Discord", () -> LinkBrowser.browse(RuneLiteProperties.getDiscordInvite()))
.addButton("Troubleshooting steps", () -> LinkBrowser.browse(RuneLiteProperties.getTroubleshootingLink()));
}
public FatalErrorDialog addBuildingGuide() public FatalErrorDialog addBuildingGuide()
{ {
return addButton("Building guide", () -> LinkBrowser.browse(RuneLiteProperties.getBuildingLink())); return addButton("Building guide", () -> LinkBrowser.browse(RuneLiteProperties.getBuildingLink()));
@@ -205,6 +213,7 @@ public class FatalErrorDialog extends JDialog
new FatalErrorDialog("OpenOSRS was unable to verify the security of its connection to the internet while " + new FatalErrorDialog("OpenOSRS was unable to verify the security of its connection to the internet while " +
action + ". You may have a misbehaving antivirus, internet service provider, a proxy, or an incomplete" + action + ". You may have a misbehaving antivirus, internet service provider, a proxy, or an incomplete" +
" java installation.") " java installation.")
.addHelpButtons()
.open(); .open();
return; return;
} }
@@ -213,6 +222,7 @@ public class FatalErrorDialog extends JDialog
{ {
new FatalErrorDialog("OpenOSRS is unable to connect to a required server while " + action + ". " + new FatalErrorDialog("OpenOSRS is unable to connect to a required server while " + action + ". " +
"Please check your internet connection") "Please check your internet connection")
.addHelpButtons()
.open(); .open();
return; return;
} }
@@ -222,11 +232,14 @@ public class FatalErrorDialog extends JDialog
new FatalErrorDialog("OpenOSRS is unable to resolve the address of a required server while " + action + ". " + new FatalErrorDialog("OpenOSRS is unable to resolve the address of a required server while " + action + ". " +
"Your DNS resolver may be misconfigured, pointing to an inaccurate resolver, or your internet connection may " + "Your DNS resolver may be misconfigured, pointing to an inaccurate resolver, or your internet connection may " +
"be down. ") "be down. ")
.addHelpButtons()
.addButton("Change your DNS resolver", () -> LinkBrowser.browse(RuneLiteProperties.getDNSChangeLink())) .addButton("Change your DNS resolver", () -> LinkBrowser.browse(RuneLiteProperties.getDNSChangeLink()))
.open(); .open();
return; return;
} }
new FatalErrorDialog("OpenOSRS encountered a fatal error while " + action + ".").open(); new FatalErrorDialog("RuneLite encountered a fatal error while " + action + ".")
.addHelpButtons()
.open();
} }
} }

View File

@@ -18,4 +18,5 @@ runelite.api.base=https://api.runelite.net/runelite-@project.version@
runelite.session=https://session.openosrs.dev runelite.session=https://session.openosrs.dev
runelite.static.base=https://static.runelite.net runelite.static.base=https://static.runelite.net
runelite.ws=https://api.runelite.net/ws runelite.ws=https://api.runelite.net/ws
runelite.config=https://static.runelite.net/config.json runelite.config=https://static.runelite.net/config.json
runelite.osrstwitter.link=https://twitter.com/OldSchoolRS

View File

@@ -0,0 +1 @@
3FA5FFC8DB18A42971CED41A9BC7CEA407A0EC98061D56B2822F66CD910E4BAF

View File

@@ -0,0 +1,129 @@
.id 3238
.int_stack_count 4
.string_stack_count 0
.int_var_count 5 ; +1 for filter result
.string_var_count 0
iconst -1 ; set to 1 to hide, 0 to show
iload 0 ; quest struct
sconst "questFilter"
runelite_callback
pop_int ; quest struct
istore 4 ; save result
; compare with -1
iload 4 ; load result
iconst -1
if_icmpeq continue
; return value
iload 4
return
continue:
iload 0
iconst 611
struct_param
iconst 1
if_icmpeq LABEL6
jump LABEL12
LABEL6:
invoke 4025
iconst 1
if_icmpeq LABEL10
jump LABEL12
LABEL10:
iconst 1
return
LABEL12:
iload 0
iconst 611
struct_param
iconst 0
if_icmpeq LABEL18
jump LABEL24
LABEL18:
get_varbit 13774
iconst 1
if_icmpeq LABEL22
jump LABEL24
LABEL22:
iconst 1
return
LABEL24:
iload 1
iconst 0
if_icmpeq LABEL28
jump LABEL34
LABEL28:
get_varbit 13775
iconst 1
if_icmpeq LABEL32
jump LABEL34
LABEL32:
iconst 1
return
LABEL34:
iload 1
iconst 1
if_icmpeq LABEL38
jump LABEL44
LABEL38:
get_varbit 13776
iconst 1
if_icmpeq LABEL42
jump LABEL44
LABEL42:
iconst 1
return
LABEL44:
iload 1
iconst 2
if_icmpeq LABEL48
jump LABEL54
LABEL48:
get_varbit 13777
iconst 1
if_icmpeq LABEL52
jump LABEL54
LABEL52:
iconst 1
return
LABEL54:
iload 1
iconst 1
if_icmpeq LABEL58
jump LABEL68
LABEL58:
get_varbit 13778
iconst 2
if_icmpeq LABEL62
jump LABEL68
LABEL62:
iload 2
iconst 0
if_icmpeq LABEL66
jump LABEL68
LABEL66:
iconst 1
return
LABEL68:
iload 1
iconst 1
if_icmpeq LABEL72
jump LABEL82
LABEL72:
get_varbit 13779
iconst 2
if_icmpeq LABEL76
jump LABEL82
LABEL76:
iload 3
iconst 0
if_icmpeq LABEL80
jump LABEL82
LABEL80:
iconst 1
return
LABEL82:
iconst 0
return
iconst -1
return

View File

@@ -165,11 +165,16 @@ public class ChatCommandsPluginTest
public void testTheatreOfBlood() public void testTheatreOfBlood()
{ {
ChatMessage chatMessage = new ChatMessage(null, GAMEMESSAGE, "", ChatMessage chatMessage = new ChatMessage(null, GAMEMESSAGE, "",
"Wave 'The Final Challenge' (Normal Mode) complete!<br>Duration: <col=ff0000>2:42.0</col><br>Theatre of Blood wave completion time: <col=ff0000>17:00.20</col> (new personal best)", null, 0); "Wave 'The Final Challenge' (Normal Mode) complete!<br>" +
"Duration: <col=ff0000>2:42.0</col><br>" +
"Theatre of Blood completion time: <col=ff0000>17:00.20</col> (new personal best)", null, 0);
chatCommandsPlugin.onChatMessage(chatMessage); chatCommandsPlugin.onChatMessage(chatMessage);
ChatMessage chatMessageEvent = new ChatMessage(null, GAMEMESSAGE, "", "Your completed Theatre of Blood count is: <col=ff0000>73</col>.", null, 0); chatMessage = new ChatMessage(null, GAMEMESSAGE, "", "Theatre of Blood total completion time: <col=ff0000>24:40.20</col>. Personal best: 20:45.00", null, 0);
chatCommandsPlugin.onChatMessage(chatMessageEvent); chatCommandsPlugin.onChatMessage(chatMessage);
chatMessage = new ChatMessage(null, GAMEMESSAGE, "", "Your completed Theatre of Blood count is: <col=ff0000>73</col>.", null, 0);
chatCommandsPlugin.onChatMessage(chatMessage);
verify(configManager).setRSProfileConfiguration("killcount", "theatre of blood", 73); verify(configManager).setRSProfileConfiguration("killcount", "theatre of blood", 73);
verify(configManager).setRSProfileConfiguration("personalbest", "theatre of blood", 17 * 60 + .2); verify(configManager).setRSProfileConfiguration("personalbest", "theatre of blood", 17 * 60 + .2);
@@ -179,11 +184,16 @@ public class ChatCommandsPluginTest
public void testTheatreOfBloodNoPb() public void testTheatreOfBloodNoPb()
{ {
ChatMessage chatMessage = new ChatMessage(null, GAMEMESSAGE, "", ChatMessage chatMessage = new ChatMessage(null, GAMEMESSAGE, "",
"Wave 'The Final Challenge' (Normal Mode) complete!<br>Duration: <col=ff0000>2:42</col><br>Theatre of Blood wave completion time: <col=ff0000>17:00</col>. Personal best: 13:52.80", null, 0); "Wave 'The Final Challenge' (Normal Mode) complete!<br>" +
"Duration: <col=ff0000>2:42</col><br>" +
"Theatre of Blood completion time: <col=ff0000>17:00</col>. Personal best: 13:52.80", null, 0);
chatCommandsPlugin.onChatMessage(chatMessage); chatCommandsPlugin.onChatMessage(chatMessage);
ChatMessage chatMessageEvent = new ChatMessage(null, GAMEMESSAGE, "", "Your completed Theatre of Blood count is: <col=ff0000>73</col>.", null, 0); chatMessage = new ChatMessage(null, GAMEMESSAGE, "", "Theatre of Blood total completion time: <col=ff0000>24:40.20</col>. Personal best: 20:45.00", null, 0);
chatCommandsPlugin.onChatMessage(chatMessageEvent); chatCommandsPlugin.onChatMessage(chatMessage);
chatMessage = new ChatMessage(null, GAMEMESSAGE, "", "Your completed Theatre of Blood count is: <col=ff0000>73</col>.", null, 0);
chatCommandsPlugin.onChatMessage(chatMessage);
verify(configManager).setRSProfileConfiguration("killcount", "theatre of blood", 73); verify(configManager).setRSProfileConfiguration("killcount", "theatre of blood", 73);
verify(configManager).setRSProfileConfiguration("personalbest", "theatre of blood", 13 * 60 + 52.8); verify(configManager).setRSProfileConfiguration("personalbest", "theatre of blood", 13 * 60 + 52.8);
@@ -193,11 +203,16 @@ public class ChatCommandsPluginTest
public void testTheatreOfBloodEntryMode() public void testTheatreOfBloodEntryMode()
{ {
ChatMessage chatMessage = new ChatMessage(null, GAMEMESSAGE, "", ChatMessage chatMessage = new ChatMessage(null, GAMEMESSAGE, "",
"Wave 'The Final Challenge' (Entry Mode) complete!<br>Duration: <col=ff0000>2:42</col><br>Theatre of Blood wave completion time: <col=ff0000>17:00</col> (new personal best)", null, 0); "Wave 'The Final Challenge' (Entry Mode) complete!<br>" +
"Duration: <col=ff0000>2:42</col><br>" +
"Theatre of Blood completion time: <col=ff0000>17:00</col> (new personal best)", null, 0);
chatCommandsPlugin.onChatMessage(chatMessage); chatCommandsPlugin.onChatMessage(chatMessage);
ChatMessage chatMessageEvent = new ChatMessage(null, GAMEMESSAGE, "", "Your completed Theatre of Blood: Entry Mode count is: <col=ff0000>73</col>.", null, 0); chatMessage = new ChatMessage(null, GAMEMESSAGE, "", "Theatre of Blood total completion time: <col=ff0000>24:40.20</col>. Personal best: 20:45.00", null, 0);
chatCommandsPlugin.onChatMessage(chatMessageEvent); chatCommandsPlugin.onChatMessage(chatMessage);
chatMessage = new ChatMessage(null, GAMEMESSAGE, "", "Your completed Theatre of Blood: Entry Mode count is: <col=ff0000>73</col>.", null, 0);
chatCommandsPlugin.onChatMessage(chatMessage);
verify(configManager).setRSProfileConfiguration("killcount", "theatre of blood entry mode", 73); verify(configManager).setRSProfileConfiguration("killcount", "theatre of blood entry mode", 73);
verify(configManager).setRSProfileConfiguration("personalbest", "theatre of blood entry mode", 17 * 60.); verify(configManager).setRSProfileConfiguration("personalbest", "theatre of blood entry mode", 17 * 60.);
@@ -1074,4 +1089,13 @@ public class ChatCommandsPluginTest
verify(configManager).setRSProfileConfiguration("personalbest", "tempoross", 234.0); verify(configManager).setRSProfileConfiguration("personalbest", "tempoross", 234.0);
verify(configManager).setRSProfileConfiguration("personalbest", "chambers of xeric", 1360.0); // the lowest time verify(configManager).setRSProfileConfiguration("personalbest", "chambers of xeric", 1360.0); // the lowest time
} }
@Test
public void testGuardiansOfTheRift()
{
ChatMessage chatMessage = new ChatMessage(null, GAMEMESSAGE, "", "Amount of Rifts you have closed: <col=ff0000>7</col>.", null, 0);
chatCommandsPlugin.onChatMessage(chatMessage);
verify(configManager).setRSProfileConfiguration("killcount", "guardians of the rift", 7);
}
} }

View File

@@ -95,7 +95,7 @@ public class FarmingTrackerTest
@Test(expected = IllegalStateException.class) @Test(expected = IllegalStateException.class)
public void testEmptyNotification() public void testEmptyNotification()
{ {
RuneScapeProfile runeScapeProfile = new RuneScapeProfile("Adam", RuneScapeProfileType.STANDARD, null, null); RuneScapeProfile runeScapeProfile = new RuneScapeProfile("Adam", RuneScapeProfileType.STANDARD, null, -1, null);
PatchPrediction patchPrediction = new PatchPrediction(Produce.EMPTY_COMPOST_BIN, CropState.EMPTY, 0L, 0, 0); PatchPrediction patchPrediction = new PatchPrediction(Produce.EMPTY_COMPOST_BIN, CropState.EMPTY, 0L, 0, 0);
FarmingRegion region = new FarmingRegion("Ardougne", 10548, false, FarmingRegion region = new FarmingRegion("Ardougne", 10548, false,
@@ -113,7 +113,7 @@ public class FarmingTrackerTest
@Test @Test
public void testHarvestableNotification() public void testHarvestableNotification()
{ {
RuneScapeProfile runeScapeProfile = new RuneScapeProfile("Adam", RuneScapeProfileType.STANDARD, null, null); RuneScapeProfile runeScapeProfile = new RuneScapeProfile("Adam", RuneScapeProfileType.STANDARD, null, -1, null);
PatchPrediction patchPrediction = new PatchPrediction(Produce.RANARR, CropState.HARVESTABLE, 0L, 0, 0); PatchPrediction patchPrediction = new PatchPrediction(Produce.RANARR, CropState.HARVESTABLE, 0L, 0, 0);
FarmingRegion region = new FarmingRegion("Ardougne", 10548, false, FarmingRegion region = new FarmingRegion("Ardougne", 10548, false,