diff --git a/cache-client/pom.xml b/cache-client/pom.xml index f4381ce9ae..9ad16f216c 100644 --- a/cache-client/pom.xml +++ b/cache-client/pom.xml @@ -29,7 +29,7 @@ net.runelite runelite-parent - 1.5.12-SNAPSHOT + 1.5.14-SNAPSHOT cache-client diff --git a/cache-updater/pom.xml b/cache-updater/pom.xml index 63450786f6..f6d5cc5ffa 100644 --- a/cache-updater/pom.xml +++ b/cache-updater/pom.xml @@ -28,7 +28,7 @@ net.runelite runelite-parent - 1.5.12-SNAPSHOT + 1.5.14-SNAPSHOT Cache Updater diff --git a/cache/pom.xml b/cache/pom.xml index 947dd7b667..072a7e4767 100644 --- a/cache/pom.xml +++ b/cache/pom.xml @@ -29,7 +29,7 @@ net.runelite runelite-parent - 1.5.12-SNAPSHOT + 1.5.14-SNAPSHOT cache diff --git a/http-api/pom.xml b/http-api/pom.xml index 5ab155acba..910cacdbd6 100644 --- a/http-api/pom.xml +++ b/http-api/pom.xml @@ -28,7 +28,7 @@ net.runelite runelite-parent - 1.5.12-SNAPSHOT + 1.5.14-SNAPSHOT Web API diff --git a/http-api/src/main/java/net/runelite/http/api/ge/GrandExchangeClient.java b/http-api/src/main/java/net/runelite/http/api/ge/GrandExchangeClient.java new file mode 100644 index 0000000000..a1b97bc390 --- /dev/null +++ b/http-api/src/main/java/net/runelite/http/api/ge/GrandExchangeClient.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2019, Adam + * 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.http.api.ge; + +import com.google.gson.Gson; +import java.io.IOException; +import java.util.UUID; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import net.runelite.http.api.RuneLiteAPI; +import okhttp3.Call; +import okhttp3.Callback; +import okhttp3.HttpUrl; +import okhttp3.MediaType; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; + +@Slf4j +@AllArgsConstructor +public class GrandExchangeClient +{ + private static final MediaType JSON = MediaType.parse("application/json"); + private static final Gson GSON = RuneLiteAPI.GSON; + + private final UUID uuid; + + public void submit(GrandExchangeTrade grandExchangeTrade) + { + final HttpUrl url = RuneLiteAPI.getApiBase().newBuilder() + .addPathSegment("ge") + .build(); + + Request request = new Request.Builder() + .header(RuneLiteAPI.RUNELITE_AUTH, uuid.toString()) + .post(RequestBody.create(JSON, GSON.toJson(grandExchangeTrade))) + .url(url) + .build(); + + RuneLiteAPI.CLIENT.newCall(request).enqueue(new Callback() + { + @Override + public void onFailure(Call call, IOException e) + { + log.debug("unable to submit trade", e); + } + + @Override + public void onResponse(Call call, Response response) + { + log.debug("Submitted trade"); + response.close(); + } + }); + } +} diff --git a/http-api/src/main/java/net/runelite/http/api/ge/GrandExchangeTrade.java b/http-api/src/main/java/net/runelite/http/api/ge/GrandExchangeTrade.java new file mode 100644 index 0000000000..b5d0012b16 --- /dev/null +++ b/http-api/src/main/java/net/runelite/http/api/ge/GrandExchangeTrade.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2019, Adam + * 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.http.api.ge; + +import java.time.Instant; +import lombok.Data; + +@Data +public class GrandExchangeTrade +{ + private boolean buy; + private int itemId; + private int quantity; + private int price; + private Instant time; +} diff --git a/http-api/src/main/java/net/runelite/http/api/loottracker/LootRecord.java b/http-api/src/main/java/net/runelite/http/api/loottracker/LootRecord.java index fb43a1605a..fc945220f1 100644 --- a/http-api/src/main/java/net/runelite/http/api/loottracker/LootRecord.java +++ b/http-api/src/main/java/net/runelite/http/api/loottracker/LootRecord.java @@ -24,6 +24,7 @@ */ package net.runelite.http.api.loottracker; +import java.time.Instant; import java.util.Collection; import lombok.AllArgsConstructor; import lombok.Data; @@ -37,4 +38,5 @@ public class LootRecord private String eventId; private LootRecordType type; private Collection drops; + private Instant time; } diff --git a/http-api/src/main/java/net/runelite/http/api/osbuddy/GrandExchangeClient.java b/http-api/src/main/java/net/runelite/http/api/osbuddy/OSBGrandExchangeClient.java similarity index 92% rename from http-api/src/main/java/net/runelite/http/api/osbuddy/GrandExchangeClient.java rename to http-api/src/main/java/net/runelite/http/api/osbuddy/OSBGrandExchangeClient.java index d80a739d29..ffdfad2035 100644 --- a/http-api/src/main/java/net/runelite/http/api/osbuddy/GrandExchangeClient.java +++ b/http-api/src/main/java/net/runelite/http/api/osbuddy/OSBGrandExchangeClient.java @@ -35,9 +35,9 @@ import okhttp3.Request; import okhttp3.Response; @Slf4j -public class GrandExchangeClient +public class OSBGrandExchangeClient { - public GrandExchangeResult lookupItem(int itemId) throws IOException + public OSBGrandExchangeResult lookupItem(int itemId) throws IOException { final HttpUrl url = RuneLiteAPI.getApiBase().newBuilder() .addPathSegment("osb") @@ -59,7 +59,7 @@ public class GrandExchangeClient } final InputStream in = response.body().byteStream(); - return RuneLiteAPI.GSON.fromJson(new InputStreamReader(in), GrandExchangeResult.class); + return RuneLiteAPI.GSON.fromJson(new InputStreamReader(in), OSBGrandExchangeResult.class); } catch (JsonParseException ex) { diff --git a/http-api/src/main/java/net/runelite/http/api/osbuddy/GrandExchangeResult.java b/http-api/src/main/java/net/runelite/http/api/osbuddy/OSBGrandExchangeResult.java similarity index 97% rename from http-api/src/main/java/net/runelite/http/api/osbuddy/GrandExchangeResult.java rename to http-api/src/main/java/net/runelite/http/api/osbuddy/OSBGrandExchangeResult.java index 993ee0d567..13e5f5e8e4 100644 --- a/http-api/src/main/java/net/runelite/http/api/osbuddy/GrandExchangeResult.java +++ b/http-api/src/main/java/net/runelite/http/api/osbuddy/OSBGrandExchangeResult.java @@ -28,7 +28,7 @@ import java.time.Instant; import lombok.Data; @Data -public class GrandExchangeResult +public class OSBGrandExchangeResult { private int item_id; private int buy_average; diff --git a/http-api/src/main/java/net/runelite/http/api/ws/messages/party/Join.java b/http-api/src/main/java/net/runelite/http/api/ws/messages/party/Join.java index 1c4cf2f73a..21aed0f653 100644 --- a/http-api/src/main/java/net/runelite/http/api/ws/messages/party/Join.java +++ b/http-api/src/main/java/net/runelite/http/api/ws/messages/party/Join.java @@ -25,10 +25,12 @@ package net.runelite.http.api.ws.messages.party; import java.util.UUID; +import lombok.EqualsAndHashCode; import lombok.Value; import net.runelite.http.api.ws.WebsocketMessage; @Value +@EqualsAndHashCode(callSuper = true) public class Join extends WebsocketMessage { private final UUID partyId; diff --git a/http-api/src/main/java/net/runelite/http/api/ws/messages/party/UserJoin.java b/http-api/src/main/java/net/runelite/http/api/ws/messages/party/UserJoin.java index 05d27b6790..81abe76827 100644 --- a/http-api/src/main/java/net/runelite/http/api/ws/messages/party/UserJoin.java +++ b/http-api/src/main/java/net/runelite/http/api/ws/messages/party/UserJoin.java @@ -25,10 +25,12 @@ package net.runelite.http.api.ws.messages.party; import java.util.UUID; +import lombok.EqualsAndHashCode; import lombok.Value; import net.runelite.http.api.ws.WebsocketMessage; @Value +@EqualsAndHashCode(callSuper = true) public class UserJoin extends WebsocketMessage { private final UUID memberId; diff --git a/http-api/src/main/java/net/runelite/http/api/ws/messages/party/UserPart.java b/http-api/src/main/java/net/runelite/http/api/ws/messages/party/UserPart.java index e3efbe4a9c..e80c6002bd 100644 --- a/http-api/src/main/java/net/runelite/http/api/ws/messages/party/UserPart.java +++ b/http-api/src/main/java/net/runelite/http/api/ws/messages/party/UserPart.java @@ -25,10 +25,12 @@ package net.runelite.http.api.ws.messages.party; import java.util.UUID; +import lombok.EqualsAndHashCode; import lombok.Value; import net.runelite.http.api.ws.WebsocketMessage; @Value +@EqualsAndHashCode(callSuper = true) public class UserPart extends WebsocketMessage { private final UUID memberId; diff --git a/http-api/src/main/java/net/runelite/http/api/ws/messages/party/UserSync.java b/http-api/src/main/java/net/runelite/http/api/ws/messages/party/UserSync.java index eca9844845..c95038c9fa 100644 --- a/http-api/src/main/java/net/runelite/http/api/ws/messages/party/UserSync.java +++ b/http-api/src/main/java/net/runelite/http/api/ws/messages/party/UserSync.java @@ -24,9 +24,11 @@ */ package net.runelite.http.api.ws.messages.party; +import lombok.EqualsAndHashCode; import lombok.Value; @Value +@EqualsAndHashCode(callSuper = true) public class UserSync extends PartyMemberMessage { } diff --git a/http-service/pom.xml b/http-service/pom.xml index 081195944c..d1217f0ced 100644 --- a/http-service/pom.xml +++ b/http-service/pom.xml @@ -28,7 +28,7 @@ net.runelite runelite-parent - 1.5.12-SNAPSHOT + 1.5.14-SNAPSHOT Web Service @@ -55,6 +55,10 @@ spring-boot-devtools true + + org.springframework + spring-jdbc + org.mapstruct @@ -130,6 +134,11 @@ 3.7.0 test + + com.h2database + h2 + test + diff --git a/http-service/src/main/java/net/runelite/http/service/SpringBootWebApplication.java b/http-service/src/main/java/net/runelite/http/service/SpringBootWebApplication.java index 562d93445a..bfdac0a254 100644 --- a/http-service/src/main/java/net/runelite/http/service/SpringBootWebApplication.java +++ b/http-service/src/main/java/net/runelite/http/service/SpringBootWebApplication.java @@ -25,13 +25,11 @@ package net.runelite.http.service; import ch.qos.logback.classic.LoggerContext; +import com.google.common.base.Strings; import java.io.IOException; import java.time.Instant; import java.util.HashMap; import java.util.Map; -import javax.naming.Context; -import javax.naming.InitialContext; -import javax.naming.NamingException; import javax.servlet.ServletContext; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; @@ -44,11 +42,17 @@ import okhttp3.Cache; import okhttp3.OkHttpClient; import org.slf4j.ILoggerFactory; import org.slf4j.impl.StaticLoggerBinder; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.web.support.SpringBootServletInitializer; import org.springframework.context.annotation.Bean; +import org.springframework.jdbc.datasource.lookup.JndiDataSourceLookup; import org.springframework.scheduling.annotation.EnableScheduling; import org.sql2o.Sql2o; import org.sql2o.converters.Converter; @@ -56,6 +60,7 @@ import org.sql2o.quirks.NoQuirks; @SpringBootApplication @EnableScheduling +@EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class}) @Slf4j public class SpringBootWebApplication extends SpringBootServletInitializer { @@ -96,35 +101,80 @@ public class SpringBootWebApplication extends SpringBootServletInitializer }; } - private Context getContext() throws NamingException + @ConfigurationProperties(prefix = "datasource.runelite") + @Bean("dataSourceRuneLite") + public DataSourceProperties dataSourceProperties() { - Context initCtx = new InitialContext(); - return (Context) initCtx.lookup("java:comp/env"); + return new DataSourceProperties(); + } + + @ConfigurationProperties(prefix = "datasource.runelite-cache") + @Bean("dataSourceRuneLiteCache") + public DataSourceProperties dataSourcePropertiesCache() + { + return new DataSourceProperties(); + } + + @ConfigurationProperties(prefix = "datasource.runelite-tracker") + @Bean("dataSourceRuneLiteTracker") + public DataSourceProperties dataSourcePropertiesTracker() + { + return new DataSourceProperties(); + } + + @Bean(value = "runelite", destroyMethod = "") + public DataSource runeliteDataSource(@Qualifier("dataSourceRuneLite") DataSourceProperties dataSourceProperties) + { + return getDataSource(dataSourceProperties); + } + + @Bean(value = "runelite-cache", destroyMethod = "") + public DataSource runeliteCache2DataSource(@Qualifier("dataSourceRuneLiteCache") DataSourceProperties dataSourceProperties) + { + return getDataSource(dataSourceProperties); + } + + @Bean(value = "runelite-tracker", destroyMethod = "") + public DataSource runeliteTrackerDataSource(@Qualifier("dataSourceRuneLiteTracker") DataSourceProperties dataSourceProperties) + { + return getDataSource(dataSourceProperties); } @Bean("Runelite SQL2O") - Sql2o sql2o() throws NamingException + public Sql2o sql2o(@Qualifier("runelite") DataSource dataSource) { - DataSource dataSource = (DataSource) getContext().lookup("jdbc/runelite"); - Map converters = new HashMap<>(); - converters.put(Instant.class, new InstantConverter()); - return new Sql2o(dataSource, new NoQuirks(converters)); + return createSql2oFromDataSource(dataSource); } @Bean("Runelite Cache SQL2O") - Sql2o cacheSql2o() throws NamingException + public Sql2o cacheSql2o(@Qualifier("runelite-cache") DataSource dataSource) { - DataSource dataSource = (DataSource) getContext().lookup("jdbc/runelite-cache2"); - Map converters = new HashMap<>(); - converters.put(Instant.class, new InstantConverter()); - return new Sql2o(dataSource, new NoQuirks(converters)); + return createSql2oFromDataSource(dataSource); } @Bean("Runelite XP Tracker SQL2O") - Sql2o trackerSql2o() throws NamingException + public Sql2o trackerSql2o(@Qualifier("runelite-tracker") DataSource dataSource) { - DataSource dataSource = (DataSource) getContext().lookup("jdbc/runelite-tracker"); - Map converters = new HashMap<>(); + return createSql2oFromDataSource(dataSource); + } + + private static DataSource getDataSource(DataSourceProperties dataSourceProperties) + { + if (!Strings.isNullOrEmpty(dataSourceProperties.getJndiName())) + { + // Use JNDI provided datasource, which is already configured with pooling + JndiDataSourceLookup dataSourceLookup = new JndiDataSourceLookup(); + return dataSourceLookup.getDataSource(dataSourceProperties.getJndiName()); + } + else + { + return dataSourceProperties.initializeDataSourceBuilder().build(); + } + } + + private static Sql2o createSql2oFromDataSource(final DataSource dataSource) + { + final Map converters = new HashMap<>(); converters.put(Instant.class, new InstantConverter()); return new Sql2o(dataSource, new NoQuirks(converters)); } diff --git a/http-service/src/main/java/net/runelite/http/service/SpringContentNegotiationConfigurer.java b/http-service/src/main/java/net/runelite/http/service/SpringWebMvcConfigurer.java similarity index 67% rename from http-service/src/main/java/net/runelite/http/service/SpringContentNegotiationConfigurer.java rename to http-service/src/main/java/net/runelite/http/service/SpringWebMvcConfigurer.java index ed055d0bdf..704e4f9cb2 100644 --- a/http-service/src/main/java/net/runelite/http/service/SpringContentNegotiationConfigurer.java +++ b/http-service/src/main/java/net/runelite/http/service/SpringWebMvcConfigurer.java @@ -24,22 +24,42 @@ */ package net.runelite.http.service; +import java.util.List; +import net.runelite.http.api.RuneLiteAPI; import org.springframework.context.annotation.Configuration; import org.springframework.http.MediaType; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.json.GsonHttpMessageConverter; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; -/** - * Configure .js as application/json to trick Cloudflare into caching json responses - */ @Configuration @EnableWebMvc -public class SpringContentNegotiationConfigurer extends WebMvcConfigurerAdapter +public class SpringWebMvcConfigurer extends WebMvcConfigurerAdapter { + /** + * Configure .js as application/json to trick Cloudflare into caching json responses + */ @Override public void configureContentNegotiation(ContentNegotiationConfigurer configurer) { configurer.mediaType("js", MediaType.APPLICATION_JSON); } + + /** + * Use GSON instead of Jackson for JSON serialization + * @param converters + */ + @Override + public void extendMessageConverters(List> converters) + { + // Could not figure out a better way to force GSON + converters.removeIf(MappingJackson2HttpMessageConverter.class::isInstance); + + GsonHttpMessageConverter gsonHttpMessageConverter = new GsonHttpMessageConverter(); + gsonHttpMessageConverter.setGson(RuneLiteAPI.GSON); + converters.add(gsonHttpMessageConverter); + } } diff --git a/http-service/src/main/java/net/runelite/http/service/account/AccountService.java b/http-service/src/main/java/net/runelite/http/service/account/AccountService.java index 3ef3a98659..c78a25b9ff 100644 --- a/http-service/src/main/java/net/runelite/http/service/account/AccountService.java +++ b/http-service/src/main/java/net/runelite/http/service/account/AccountService.java @@ -45,8 +45,6 @@ import net.runelite.http.api.ws.messages.LoginResponse; import net.runelite.http.service.account.beans.SessionEntry; import net.runelite.http.service.account.beans.UserEntry; import net.runelite.http.service.util.redis.RedisPool; -import net.runelite.http.service.ws.SessionManager; -import net.runelite.http.service.ws.WSService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -241,12 +239,6 @@ public class AccountService LoginResponse response = new LoginResponse(); response.setUsername(username); - WSService service = SessionManager.findSession(uuid); - if (service != null) - { - service.send(response); - } - try (Jedis jedis = jedisPool.getResource()) { jedis.publish("session." + uuid, websocketGson.toJson(response, WebsocketMessage.class)); @@ -276,10 +268,4 @@ public class AccountService { auth.handle(request, response); } - - @RequestMapping("/wscount") - public int wscount() - { - return SessionManager.getCount(); - } } diff --git a/http-service/src/main/java/net/runelite/http/service/cache/CacheService.java b/http-service/src/main/java/net/runelite/http/service/cache/CacheService.java index 882e90c460..e0fca4ac21 100644 --- a/http-service/src/main/java/net/runelite/http/service/cache/CacheService.java +++ b/http-service/src/main/java/net/runelite/http/service/cache/CacheService.java @@ -41,6 +41,7 @@ import java.io.InputStream; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import lombok.extern.slf4j.Slf4j; import net.runelite.cache.ConfigType; @@ -233,6 +234,11 @@ public class CacheService public List getItems() throws IOException { CacheEntry cache = findMostRecent(); + if (cache == null) + { + return Collections.emptyList(); + } + IndexEntry indexEntry = findIndexForCache(cache, IndexType.CONFIGS.getNumber()); ArchiveEntry archiveEntry = findArchiveForIndex(indexEntry, ConfigType.ITEM.getId()); ArchiveFiles archiveFiles = getArchiveFiles(archiveEntry); diff --git a/http-service/src/main/java/net/runelite/http/service/config/ConfigController.java b/http-service/src/main/java/net/runelite/http/service/config/ConfigController.java new file mode 100644 index 0000000000..966d49cda2 --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/config/ConfigController.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2019, Adam + * 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.http.service.config; + +import java.io.IOException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import net.runelite.http.api.config.Configuration; +import net.runelite.http.service.account.AuthFilter; +import net.runelite.http.service.account.beans.SessionEntry; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import static org.springframework.web.bind.annotation.RequestMethod.DELETE; +import static org.springframework.web.bind.annotation.RequestMethod.PUT; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/config") +public class ConfigController +{ + private final ConfigService configService; + private final AuthFilter authFilter; + + @Autowired + public ConfigController(ConfigService configService, AuthFilter authFilter) + { + this.configService = configService; + this.authFilter = authFilter; + } + + @RequestMapping + public Configuration get(HttpServletRequest request, HttpServletResponse response) throws IOException + { + SessionEntry session = authFilter.handle(request, response); + + if (session == null) + { + return null; + } + + return configService.get(session.getUser()); + } + + @RequestMapping(path = "/{key:.+}", method = PUT) + public void setKey( + HttpServletRequest request, + HttpServletResponse response, + @PathVariable String key, + @RequestBody(required = false) String value + ) throws IOException + { + SessionEntry session = authFilter.handle(request, response); + + if (session == null) + { + return; + } + + configService.setKey(session.getUser(), key, value); + } + + @RequestMapping(path = "/{key:.+}", method = DELETE) + public void unsetKey( + HttpServletRequest request, + HttpServletResponse response, + @PathVariable String key + ) throws IOException + { + SessionEntry session = authFilter.handle(request, response); + + if (session == null) + { + return; + } + + configService.unsetKey(session.getUser(), key); + } +} diff --git a/http-service/src/main/java/net/runelite/http/service/config/ConfigService.java b/http-service/src/main/java/net/runelite/http/service/config/ConfigService.java index 102e2f9c7a..ed96f36cf4 100644 --- a/http-service/src/main/java/net/runelite/http/service/config/ConfigService.java +++ b/http-service/src/main/java/net/runelite/http/service/config/ConfigService.java @@ -24,28 +24,18 @@ */ package net.runelite.http.service.config; -import java.io.IOException; import java.util.List; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import javax.annotation.Nullable; import net.runelite.http.api.config.ConfigEntry; import net.runelite.http.api.config.Configuration; -import net.runelite.http.service.account.AuthFilter; -import net.runelite.http.service.account.beans.SessionEntry; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import static org.springframework.web.bind.annotation.RequestMethod.DELETE; -import static org.springframework.web.bind.annotation.RequestMethod.PUT; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.stereotype.Service; import org.sql2o.Connection; import org.sql2o.Sql2o; import org.sql2o.Sql2oException; -@RestController -@RequestMapping("/config") +@Service public class ConfigService { private static final String CREATE_CONFIG = "CREATE TABLE IF NOT EXISTS `config` (\n" @@ -59,16 +49,13 @@ public class ConfigService + " ADD CONSTRAINT `user_fk` FOREIGN KEY (`user`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;"; private final Sql2o sql2o; - private final AuthFilter auth; @Autowired public ConfigService( - @Qualifier("Runelite SQL2O") Sql2o sql2o, - AuthFilter auth + @Qualifier("Runelite SQL2O") Sql2o sql2o ) { this.sql2o = sql2o; - this.auth = auth; try (Connection con = sql2o.open()) { @@ -87,71 +74,45 @@ public class ConfigService } } - @RequestMapping - public Configuration get(HttpServletRequest request, HttpServletResponse response) throws IOException + public Configuration get(int userId) { - SessionEntry session = auth.handle(request, response); - - if (session == null) - { - return null; - } - List config; try (Connection con = sql2o.open()) { config = con.createQuery("select `key`, value from config where user = :user") - .addParameter("user", session.getUser()) + .addParameter("user", userId) .executeAndFetch(ConfigEntry.class); } return new Configuration(config); } - @RequestMapping(path = "/{key:.+}", method = PUT) public void setKey( - HttpServletRequest request, - HttpServletResponse response, - @PathVariable String key, - @RequestBody(required = false) String value - ) throws IOException + int userId, + String key, + @Nullable String value + ) { - SessionEntry session = auth.handle(request, response); - - if (session == null) - { - return; - } - try (Connection con = sql2o.open()) { con.createQuery("insert into config (user, `key`, value) values (:user, :key, :value) on duplicate key update `key` = :key, value = :value") - .addParameter("user", session.getUser()) + .addParameter("user", userId) .addParameter("key", key) .addParameter("value", value != null ? value : "") .executeUpdate(); } } - @RequestMapping(path = "/{key:.+}", method = DELETE) public void unsetKey( - HttpServletRequest request, - HttpServletResponse response, - @PathVariable String key - ) throws IOException + int userId, + String key + ) { - SessionEntry session = auth.handle(request, response); - - if (session == null) - { - return; - } - try (Connection con = sql2o.open()) { con.createQuery("delete from config where user = :user and `key` = :key") - .addParameter("user", session.getUser()) + .addParameter("user", userId) .addParameter("key", key) .executeUpdate(); } diff --git a/http-service/src/main/java/net/runelite/http/service/feed/FeedController.java b/http-service/src/main/java/net/runelite/http/service/feed/FeedController.java index 8b55d1b93a..fa641d3714 100644 --- a/http-service/src/main/java/net/runelite/http/service/feed/FeedController.java +++ b/http-service/src/main/java/net/runelite/http/service/feed/FeedController.java @@ -71,7 +71,7 @@ public class FeedController } catch (IOException e) { - log.warn(null, e); + log.warn(e.getMessage()); } try @@ -80,7 +80,7 @@ public class FeedController } catch (IOException e) { - log.warn(null, e); + log.warn(e.getMessage()); } try @@ -89,7 +89,7 @@ public class FeedController } catch (IOException e) { - log.warn(null, e); + log.warn(e.getMessage()); } feedResult = new FeedResult(items); diff --git a/http-service/src/main/java/net/runelite/http/service/ge/GrandExchangeController.java b/http-service/src/main/java/net/runelite/http/service/ge/GrandExchangeController.java new file mode 100644 index 0000000000..94c759b130 --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/ge/GrandExchangeController.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2019, Adam + * 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.http.service.ge; + +import java.io.IOException; +import java.util.Collection; +import java.util.stream.Collectors; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import net.runelite.http.api.ge.GrandExchangeTrade; +import net.runelite.http.service.account.AuthFilter; +import net.runelite.http.service.account.beans.SessionEntry; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/ge") +public class GrandExchangeController +{ + private final GrandExchangeService grandExchangeService; + private final AuthFilter authFilter; + + @Autowired + public GrandExchangeController(GrandExchangeService grandExchangeService, AuthFilter authFilter) + { + this.grandExchangeService = grandExchangeService; + this.authFilter = authFilter; + } + + @PostMapping + public void submit(HttpServletRequest request, HttpServletResponse response, @RequestBody GrandExchangeTrade grandExchangeTrade) throws IOException + { + SessionEntry session = authFilter.handle(request, response); + + if (session == null) + { + return; + } + + grandExchangeService.add(session.getUser(), grandExchangeTrade); + } + + @GetMapping + public Collection get(HttpServletRequest request, HttpServletResponse response, + @RequestParam(required = false, defaultValue = "1024") int limit, + @RequestParam(required = false, defaultValue = "0") int offset) throws IOException + { + SessionEntry session = authFilter.handle(request, response); + + if (session == null) + { + return null; + } + + return grandExchangeService.get(session.getUser(), limit, offset).stream() + .map(GrandExchangeController::convert) + .collect(Collectors.toList()); + } + + private static GrandExchangeTrade convert(TradeEntry tradeEntry) + { + GrandExchangeTrade grandExchangeTrade = new GrandExchangeTrade(); + grandExchangeTrade.setBuy(tradeEntry.getAction() == TradeAction.BUY); + grandExchangeTrade.setItemId(tradeEntry.getItem()); + grandExchangeTrade.setQuantity(tradeEntry.getQuantity()); + grandExchangeTrade.setPrice(tradeEntry.getPrice()); + grandExchangeTrade.setTime(tradeEntry.getTime()); + return grandExchangeTrade; + } + + @DeleteMapping + public void delete(HttpServletRequest request, HttpServletResponse response) throws IOException + { + SessionEntry session = authFilter.handle(request, response); + + if (session == null) + { + return; + } + + grandExchangeService.delete(session.getUser()); + } +} diff --git a/http-service/src/main/java/net/runelite/http/service/ge/GrandExchangeService.java b/http-service/src/main/java/net/runelite/http/service/ge/GrandExchangeService.java new file mode 100644 index 0000000000..6456beb85f --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/ge/GrandExchangeService.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2019, Adam + * 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.http.service.ge; + +import java.util.Collection; +import net.runelite.http.api.ge.GrandExchangeTrade; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; +import org.sql2o.Connection; +import org.sql2o.Sql2o; + +@Service +public class GrandExchangeService +{ + private static final String CREATE_TABLE = "CREATE TABLE IF NOT EXISTS `ge_trades` (\n" + + " `id` int(11) NOT NULL AUTO_INCREMENT,\n" + + " `user` int(11) NOT NULL,\n" + + " `action` enum('BUY','SELL') NOT NULL,\n" + + " `item` int(11) NOT NULL,\n" + + " `quantity` int(11) NOT NULL,\n" + + " `price` int(11) NOT NULL,\n" + + " `time` timestamp NOT NULL DEFAULT current_timestamp(),\n" + + " PRIMARY KEY (`id`),\n" + + " KEY `user_time` (`user`, `time`),\n" + + " KEY `time` (`time`),\n" + + " CONSTRAINT `ge_trades_ibfk_1` FOREIGN KEY (`user`) REFERENCES `users` (`id`)\n" + + ") ENGINE=InnoDB;"; + + private final Sql2o sql2o; + + @Autowired + public GrandExchangeService(@Qualifier("Runelite SQL2O") Sql2o sql2o) + { + this.sql2o = sql2o; + + // Ensure necessary tables exist + try (Connection con = sql2o.open()) + { + con.createQuery(CREATE_TABLE).executeUpdate(); + } + } + + public void add(int userId, GrandExchangeTrade grandExchangeTrade) + { + try (Connection con = sql2o.open()) + { + con.createQuery("insert into ge_trades (user, action, item, quantity, price) values (:user," + + " :action, :item, :quantity, :price)") + .addParameter("user", userId) + .addParameter("action", grandExchangeTrade.isBuy() ? "BUY" : "SELL") + .addParameter("item", grandExchangeTrade.getItemId()) + .addParameter("quantity", grandExchangeTrade.getQuantity()) + .addParameter("price", grandExchangeTrade.getPrice()) + .executeUpdate(); + } + } + + public Collection get(int userId, int limit, int offset) + { + try (Connection con = sql2o.open()) + { + return con.createQuery("select id, user, action, item, quantity, price, time from ge_trades where user = :user limit :limit offset :offset") + .addParameter("user", userId) + .addParameter("limit", limit) + .addParameter("offset", offset) + .executeAndFetch(TradeEntry.class); + } + } + + public void delete(int userId) + { + try (Connection con = sql2o.open()) + { + con.createQuery("delete from ge_trades where user = :user") + .addParameter("user", userId) + .executeUpdate(); + } + } + + @Scheduled(fixedDelay = 60 * 60 * 1000) + public void expire() + { + try (Connection con = sql2o.open()) + { + con.createQuery("delete from ge_trades where time < current_timestamp - interval 1 month") + .executeUpdate(); + } + } +} diff --git a/http-service/src/main/java/net/runelite/http/service/ge/TradeAction.java b/http-service/src/main/java/net/runelite/http/service/ge/TradeAction.java new file mode 100644 index 0000000000..fcc96d615f --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/ge/TradeAction.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2019, Adam + * 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.http.service.ge; + +enum TradeAction +{ + BUY, + SELL; +} diff --git a/http-service/src/main/java/net/runelite/http/service/ge/TradeEntry.java b/http-service/src/main/java/net/runelite/http/service/ge/TradeEntry.java new file mode 100644 index 0000000000..bca3869811 --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/ge/TradeEntry.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2019, Adam + * 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.http.service.ge; + +import java.time.Instant; +import lombok.Data; + +@Data +class TradeEntry +{ + private int id; + private int user; + private TradeAction action; + private int item; + private int quantity; + private int price; + private Instant time; +} diff --git a/http-service/src/main/java/net/runelite/http/service/item/ItemService.java b/http-service/src/main/java/net/runelite/http/service/item/ItemService.java index 2da7a50028..32569b0517 100644 --- a/http-service/src/main/java/net/runelite/http/service/item/ItemService.java +++ b/http-service/src/main/java/net/runelite/http/service/item/ItemService.java @@ -489,10 +489,16 @@ public class ItemService public void reloadItems() throws IOException { List items = cacheService.getItems(); + if (items.isEmpty()) + { + log.warn("Failed to load any items from cache, item price updating will be disabled"); + } + tradeableItems = items.stream() .filter(item -> item.isTradeable) .mapToInt(item -> item.id) .toArray(); + log.debug("Loaded {} tradeable items", tradeableItems.length); } diff --git a/http-service/src/main/java/net/runelite/http/service/loottracker/LootTrackerController.java b/http-service/src/main/java/net/runelite/http/service/loottracker/LootTrackerController.java index 36e0a2333d..d5a09f44d2 100644 --- a/http-service/src/main/java/net/runelite/http/service/loottracker/LootTrackerController.java +++ b/http-service/src/main/java/net/runelite/http/service/loottracker/LootTrackerController.java @@ -66,7 +66,7 @@ public class LootTrackerController } @RequestMapping - public Collection getLootRecords(HttpServletRequest request, HttpServletResponse response, @RequestParam(value = "count", defaultValue = "1024") int count) throws IOException + public Collection getLootRecords(HttpServletRequest request, HttpServletResponse response, @RequestParam(value = "count", defaultValue = "1024") int count, @RequestParam(value = "start", defaultValue = "0") int start) throws IOException { SessionEntry e = auth.handle(request, response); if (e == null) @@ -75,7 +75,7 @@ public class LootTrackerController return null; } - return service.get(e.getUser(), count); + return service.get(e.getUser(), count, start); } @DeleteMapping diff --git a/http-service/src/main/java/net/runelite/http/service/loottracker/LootTrackerService.java b/http-service/src/main/java/net/runelite/http/service/loottracker/LootTrackerService.java index d39e159228..b999f4eabb 100644 --- a/http-service/src/main/java/net/runelite/http/service/loottracker/LootTrackerService.java +++ b/http-service/src/main/java/net/runelite/http/service/loottracker/LootTrackerService.java @@ -66,7 +66,7 @@ public class LootTrackerService private static final String INSERT_KILL_QUERY = "INSERT INTO kills (accountId, type, eventId) VALUES (:accountId, :type, :eventId)"; private static final String INSERT_DROP_QUERY = "INSERT INTO drops (killId, itemId, itemQuantity) VALUES (LAST_INSERT_ID(), :itemId, :itemQuantity)"; - private static final String SELECT_LOOT_QUERY = "SELECT killId,time,type,eventId,itemId,itemQuantity FROM kills JOIN drops ON drops.killId = kills.id WHERE accountId = :accountId ORDER BY TIME DESC LIMIT :limit"; + private static final String SELECT_LOOT_QUERY = "SELECT killId,time,type,eventId,itemId,itemQuantity FROM kills JOIN drops ON drops.killId = kills.id WHERE accountId = :accountId ORDER BY TIME DESC LIMIT :limit OFFSET :offset"; private static final String DELETE_LOOT_ACCOUNT = "DELETE FROM kills WHERE accountId = :accountId"; private static final String DELETE_LOOT_ACCOUNT_EVENTID = "DELETE FROM kills WHERE accountId = :accountId AND eventId = :eventId"; @@ -119,7 +119,7 @@ public class LootTrackerService } } - public Collection get(int accountId, int limit) + public Collection get(int accountId, int limit, int offset) { List lootResults; @@ -128,6 +128,7 @@ public class LootTrackerService lootResults = con.createQuery(SELECT_LOOT_QUERY) .addParameter("accountId", accountId) .addParameter("limit", limit) + .addParameter("offset", offset) .executeAndFetch(LootResult.class); } @@ -141,7 +142,7 @@ public class LootTrackerService { if (!gameItems.isEmpty()) { - LootRecord lootRecord = new LootRecord(current.getEventId(), current.getType(), gameItems); + LootRecord lootRecord = new LootRecord(current.getEventId(), current.getType(), gameItems, current.getTime()); lootRecords.add(lootRecord); gameItems = new ArrayList<>(); @@ -156,7 +157,7 @@ public class LootTrackerService if (!gameItems.isEmpty()) { - LootRecord lootRecord = new LootRecord(current.getEventId(), current.getType(), gameItems); + LootRecord lootRecord = new LootRecord(current.getEventId(), current.getType(), gameItems, current.getTime()); lootRecords.add(lootRecord); } diff --git a/http-service/src/main/java/net/runelite/http/service/osbuddy/GrandExchangeController.java b/http-service/src/main/java/net/runelite/http/service/osbuddy/OSBGrandExchangeController.java similarity index 92% rename from http-service/src/main/java/net/runelite/http/service/osbuddy/GrandExchangeController.java rename to http-service/src/main/java/net/runelite/http/service/osbuddy/OSBGrandExchangeController.java index 92ba9093e7..847c415772 100644 --- a/http-service/src/main/java/net/runelite/http/service/osbuddy/GrandExchangeController.java +++ b/http-service/src/main/java/net/runelite/http/service/osbuddy/OSBGrandExchangeController.java @@ -35,12 +35,12 @@ import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/osb/ge") -public class GrandExchangeController +public class OSBGrandExchangeController { - private final GrandExchangeService grandExchangeService; + private final OSBGrandExchangeService grandExchangeService; @Autowired - public GrandExchangeController(GrandExchangeService grandExchangeService) + public OSBGrandExchangeController(OSBGrandExchangeService grandExchangeService) { this.grandExchangeService = grandExchangeService; } diff --git a/http-service/src/main/java/net/runelite/http/service/osbuddy/GrandExchangeService.java b/http-service/src/main/java/net/runelite/http/service/osbuddy/OSBGrandExchangeService.java similarity index 97% rename from http-service/src/main/java/net/runelite/http/service/osbuddy/GrandExchangeService.java rename to http-service/src/main/java/net/runelite/http/service/osbuddy/OSBGrandExchangeService.java index f7addee899..797d7e76e2 100644 --- a/http-service/src/main/java/net/runelite/http/service/osbuddy/GrandExchangeService.java +++ b/http-service/src/main/java/net/runelite/http/service/osbuddy/OSBGrandExchangeService.java @@ -40,7 +40,7 @@ import org.sql2o.Sql2o; @Service @Slf4j -public class GrandExchangeService +public class OSBGrandExchangeService { private static final String CREATE_GRAND_EXCHANGE_PRICES = "CREATE TABLE IF NOT EXISTS `osb_ge` (\n" + " `item_id` int(11) NOT NULL,\n" @@ -56,7 +56,7 @@ public class GrandExchangeService private final Sql2o sql2o; @Autowired - public GrandExchangeService(@Qualifier("Runelite SQL2O") Sql2o sql2o) + public OSBGrandExchangeService(@Qualifier("Runelite SQL2O") Sql2o sql2o) { this.sql2o = sql2o; diff --git a/http-service/src/main/java/net/runelite/http/service/ws/WSService.java b/http-service/src/main/java/net/runelite/http/service/ws/WSService.java deleted file mode 100644 index 05489f2b87..0000000000 --- a/http-service/src/main/java/net/runelite/http/service/ws/WSService.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (c) 2017, Adam - * 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.http.service.ws; - -import com.google.gson.Gson; -import java.util.UUID; -import javax.websocket.CloseReason; -import javax.websocket.EndpointConfig; -import javax.websocket.OnClose; -import javax.websocket.OnError; -import javax.websocket.OnMessage; -import javax.websocket.OnOpen; -import javax.websocket.Session; -import javax.websocket.server.ServerEndpoint; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.Setter; -import net.runelite.http.api.ws.WebsocketGsonFactory; -import net.runelite.http.api.ws.WebsocketMessage; -import net.runelite.http.api.ws.messages.Handshake; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -@ServerEndpoint("/ws") -public class WSService -{ - private static final Logger logger = LoggerFactory.getLogger(WSService.class); - - private static final Gson gson = WebsocketGsonFactory.build(); - - private Session session; - @Getter(AccessLevel.PACKAGE) - @Setter(AccessLevel.PACKAGE) - private UUID uuid; - - public void send(WebsocketMessage message) - { - String json = gson.toJson(message, WebsocketMessage.class); - - logger.debug("Sending {}", json); - - session.getAsyncRemote().sendText(json); - } - - @OnOpen - public void onOpen(Session session, EndpointConfig config) - { - this.session = session; - logger.debug("New session {}", session); - } - - @OnClose - public void onClose(Session session, CloseReason resaon) - { - SessionManager.remove(this); - logger.debug("Close session {}", session); - } - - @OnError - public void onError(Session session, Throwable ex) - { - SessionManager.remove(this); - logger.debug("Error in session {}", session, ex); - } - - @OnMessage - public void onMessage(Session session, String text) - { - WebsocketMessage message = gson.fromJson(text, WebsocketMessage.class); - logger.debug("Got message: {}", message); - - if (message instanceof Handshake) - { - Handshake hs = (Handshake) message; - SessionManager.changeSessionUID(this, hs.getSession()); - } - } -} diff --git a/http-service/src/main/java/net/runelite/http/service/xp/XpMapper.java b/http-service/src/main/java/net/runelite/http/service/xp/XpMapper.java index ad72f43c47..068c6e5438 100644 --- a/http-service/src/main/java/net/runelite/http/service/xp/XpMapper.java +++ b/http-service/src/main/java/net/runelite/http/service/xp/XpMapper.java @@ -38,6 +38,8 @@ public interface XpMapper XpData xpEntityToXpData(XpEntity xpEntity); + @Mapping(target = "time", ignore = true) + @Mapping(source = "attack.experience", target = "attack_xp") @Mapping(source = "defence.experience", target = "defence_xp") @Mapping(source = "strength.experience", target = "strength_xp") diff --git a/http-service/src/main/java/net/runelite/http/service/xp/XpTrackerService.java b/http-service/src/main/java/net/runelite/http/service/xp/XpTrackerService.java index 391d37d1bf..9e8e0a5e06 100644 --- a/http-service/src/main/java/net/runelite/http/service/xp/XpTrackerService.java +++ b/http-service/src/main/java/net/runelite/http/service/xp/XpTrackerService.java @@ -29,8 +29,8 @@ import com.google.common.hash.Funnels; import java.nio.charset.Charset; import java.time.Duration; import java.time.Instant; +import java.util.ArrayDeque; import java.util.Queue; -import java.util.concurrent.ConcurrentLinkedDeque; import java.util.concurrent.ExecutionException; import lombok.extern.slf4j.Slf4j; import net.runelite.http.api.hiscore.HiscoreEndpoint; @@ -50,8 +50,8 @@ import org.sql2o.Sql2o; @Slf4j public class XpTrackerService { - private static final int QUEUE_LIMIT = 100_000; - private static final Duration UPDATE_TIME = Duration.ofMinutes(5); + private static final int QUEUE_LIMIT = 32768; + private static final int BLOOMFILTER_EXPECTED_INSERTIONS = 100_000; @Autowired @Qualifier("Runelite XP Tracker SQL2O") @@ -60,7 +60,7 @@ public class XpTrackerService @Autowired private HiscoreService hiscoreService; - private final Queue usernameUpdateQueue = new ConcurrentLinkedDeque<>(); + private final Queue usernameUpdateQueue = new ArrayDeque<>(); private BloomFilter usernameFilter = createFilter(); public void update(String username) throws ExecutionException @@ -76,13 +76,31 @@ public class XpTrackerService return; } - if (usernameUpdateQueue.size() >= QUEUE_LIMIT) + try (Connection con = sql2o.open()) { - log.warn("Username update queue is full ({})", QUEUE_LIMIT); - return; + PlayerEntity playerEntity = findOrCreatePlayer(con, username); + Duration frequency = updateFrequency(playerEntity); + Instant now = Instant.now(); + Duration timeSinceLastUpdate = Duration.between(playerEntity.getLast_updated(), now); + if (timeSinceLastUpdate.toMillis() < frequency.toMillis()) + { + log.debug("User {} updated too recently", username); + usernameFilter.put(username); + return; + } + + synchronized (usernameUpdateQueue) + { + if (usernameUpdateQueue.size() >= QUEUE_LIMIT) + { + log.warn("Username update queue is full ({})", QUEUE_LIMIT); + return; + } + + usernameUpdateQueue.add(username); + } } - usernameUpdateQueue.add(username); usernameFilter.put(username); } @@ -104,13 +122,6 @@ public class XpTrackerService log.debug("Hiscore for {} already up to date", username); return; } - - Duration difference = Duration.between(currentXp.getTime(), now); - if (difference.compareTo(UPDATE_TIME) <= 0) - { - log.debug("Updated {} too recently", username); - return; - } } con.createQuery("insert into xp (player,attack_xp,defence_xp,strength_xp,hitpoints_xp,ranged_xp,prayer_xp,magic_xp,cooking_xp,woodcutting_xp," @@ -172,6 +183,11 @@ public class XpTrackerService .addParameter("construction_rank", hiscoreResult.getConstruction().getRank()) .addParameter("overall_rank", hiscoreResult.getOverall().getRank()) .executeUpdate(); + + con.createQuery("update player set rank = :rank, last_updated = CURRENT_TIMESTAMP where id = :id") + .addParameter("id", playerEntity.getId()) + .addParameter("rank", hiscoreResult.getOverall().getRank()) + .executeUpdate(); } } @@ -197,6 +213,7 @@ public class XpTrackerService playerEntity.setId(id); playerEntity.setName(username); playerEntity.setTracked_since(now); + playerEntity.setLast_updated(now); return playerEntity; } @@ -220,18 +237,21 @@ public class XpTrackerService @Scheduled(fixedDelay = 1000) public void update() throws ExecutionException { - String next = usernameUpdateQueue.poll(); + String next; + synchronized (usernameUpdateQueue) + { + next = usernameUpdateQueue.poll(); + } if (next == null) { return; } - HiscoreResult hiscoreResult = hiscoreService.lookupUsername(next, HiscoreEndpoint.NORMAL); - update(next, hiscoreResult); + update(next); } - @Scheduled(fixedDelay = 3 * 60 * 60 * 1000) // 3 hours + @Scheduled(fixedDelay = 6 * 60 * 60 * 1000) // 6 hours public void clearFilter() { usernameFilter = createFilter(); @@ -241,14 +261,47 @@ public class XpTrackerService { final BloomFilter filter = BloomFilter.create( Funnels.stringFunnel(Charset.defaultCharset()), - 100_000 + BLOOMFILTER_EXPECTED_INSERTIONS ); - for (String toUpdate : usernameUpdateQueue) + synchronized (usernameUpdateQueue) { - filter.put(toUpdate); + for (String toUpdate : usernameUpdateQueue) + { + filter.put(toUpdate); + } } return filter; } + + /** + * scale how often to check hiscore updates for players based on their rank + * @param playerEntity + * @return + */ + private static Duration updateFrequency(PlayerEntity playerEntity) + { + Integer rank = playerEntity.getRank(); + if (rank == null || rank == -1) + { + return Duration.ofDays(7); + } + else if (rank < 10_000) + { + return Duration.ofHours(6); + } + else if (rank < 50_000) + { + return Duration.ofDays(2); + } + else if (rank < 100_000) + { + return Duration.ofDays(5); + } + else + { + return Duration.ofDays(7); + } + } } diff --git a/http-service/src/main/java/net/runelite/http/service/xp/beans/PlayerEntity.java b/http-service/src/main/java/net/runelite/http/service/xp/beans/PlayerEntity.java index d07d1d4640..11bec532d7 100644 --- a/http-service/src/main/java/net/runelite/http/service/xp/beans/PlayerEntity.java +++ b/http-service/src/main/java/net/runelite/http/service/xp/beans/PlayerEntity.java @@ -33,4 +33,6 @@ public class PlayerEntity private Integer id; private String name; private Instant tracked_since; + private Instant last_updated; + private Integer rank; } diff --git a/http-service/src/main/resources/application.yaml b/http-service/src/main/resources/application.yaml new file mode 100644 index 0000000000..9aa02c58ac --- /dev/null +++ b/http-service/src/main/resources/application.yaml @@ -0,0 +1,12 @@ +datasource: + runelite: + jndiName: java:comp/env/jdbc/runelite + runelite-cache: + jndiName: java:comp/env/jdbc/runelite-cache2 + runelite-tracker: + jndiName: java:comp/env/jdbc/runelite-tracker +# By default Spring tries to register the datasource as an MXBean, +# so if multiple apis are delpoyed on one web container with +# shared datasource it tries to register it multiples times and +# fails when starting the 2nd api +spring.jmx.enabled: false \ No newline at end of file diff --git a/http-service/src/main/resources/net/runelite/http/service/xp/schema.sql b/http-service/src/main/resources/net/runelite/http/service/xp/schema.sql index ba4c395d62..70f83a30fd 100644 --- a/http-service/src/main/resources/net/runelite/http/service/xp/schema.sql +++ b/http-service/src/main/resources/net/runelite/http/service/xp/schema.sql @@ -1,8 +1,8 @@ --- MySQL dump 10.16 Distrib 10.2.9-MariaDB, for Linux (x86_64) +-- MySQL dump 10.16 Distrib 10.2.18-MariaDB, for Linux (x86_64) -- -- Host: localhost Database: xptracker -- ------------------------------------------------------ --- Server version 10.2.9-MariaDB +-- Server version 10.2.18-MariaDB /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; @@ -26,6 +26,8 @@ CREATE TABLE `player` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(32) NOT NULL, `tracked_since` timestamp NOT NULL DEFAULT current_timestamp(), + `last_updated` timestamp NOT NULL DEFAULT current_timestamp(), + `rank` int(11) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `name` (`name`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; @@ -116,7 +118,7 @@ CREATE TABLE `xp` ( `overall_rank` int(11) NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `player_time` (`player`,`time`), - INDEX `idx_time` (`time`), + KEY `idx_time` (`time`), CONSTRAINT `fk_player` FOREIGN KEY (`player`) REFERENCES `player` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; @@ -130,4 +132,4 @@ CREATE TABLE `xp` ( /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; --- Dump completed on 2018-01-20 18:37:09 +-- Dump completed on 2019-02-15 21:01:17 \ No newline at end of file diff --git a/http-service/src/test/java/net/runelite/http/service/SpringBootWebApplicationTest.java b/http-service/src/test/java/net/runelite/http/service/SpringBootWebApplicationTest.java index 0503c075d8..d2d488e1e6 100644 --- a/http-service/src/test/java/net/runelite/http/service/SpringBootWebApplicationTest.java +++ b/http-service/src/test/java/net/runelite/http/service/SpringBootWebApplicationTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, Adam + * Copyright (c) 2019, Adam * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -24,57 +24,23 @@ */ package net.runelite.http.service; -import java.time.Instant; -import java.util.HashMap; -import java.util.Map; -import javax.naming.NamingException; -import net.runelite.http.service.util.InstantConverter; import org.junit.Ignore; import org.junit.Test; import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.context.annotation.Bean; -import org.springframework.scheduling.annotation.EnableScheduling; -import org.sql2o.Sql2o; -import org.sql2o.converters.Converter; -import org.sql2o.quirks.NoQuirks; -@SpringBootApplication -@EnableScheduling public class SpringBootWebApplicationTest { - @Bean("Runelite SQL2O") - Sql2o sql2o() - { - Map converters = new HashMap<>(); - converters.put(Instant.class, new InstantConverter()); - return new Sql2o("jdbc:mysql://192.168.1.2/runelite", "runelite", "runelite", new NoQuirks(converters)); - } - - @Bean("Runelite Cache SQL2O") - Sql2o cacheSql2o() throws NamingException - { - Map converters = new HashMap<>(); - converters.put(Instant.class, new InstantConverter()); - return new Sql2o("jdbc:mysql://192.168.1.2/cache", "runelite", "runelite", new NoQuirks(converters)); - } - - @Bean("Runelite XP Tracker SQL2O") - Sql2o xpSql2o() throws NamingException - { - Map converters = new HashMap<>(); - converters.put(Instant.class, new InstantConverter()); - return new Sql2o("jdbc:mysql://192.168.1.2/xptracker", "runelite", "runelite", new NoQuirks(converters)); - } - @Test @Ignore - public void test() throws InterruptedException + public void run() throws InterruptedException { - SpringApplication.run(SpringBootWebApplicationTest.class, new String[0]); + String[] args = new String[]{ + "--spring.config.location=classpath:/application.yaml,classpath:/dev.yaml" + }; + SpringApplication.run(SpringBootWebApplication.class, args); for (;;) { Thread.sleep(100L); } } -} +} \ No newline at end of file diff --git a/http-service/src/test/java/net/runelite/http/service/config/ConfigControllerTest.java b/http-service/src/test/java/net/runelite/http/service/config/ConfigControllerTest.java new file mode 100644 index 0000000000..5ba857ef0a --- /dev/null +++ b/http-service/src/test/java/net/runelite/http/service/config/ConfigControllerTest.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2019, Adam + * 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.http.service.config; + +import java.io.IOException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import net.runelite.http.service.account.AuthFilter; +import net.runelite.http.service.account.beans.SessionEntry; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@RunWith(SpringRunner.class) +@WebMvcTest(ConfigController.class) +@Slf4j +public class ConfigControllerTest +{ + @Autowired + private MockMvc mockMvc; + + @MockBean + private ConfigService configService; + + @MockBean + private AuthFilter authFilter; + + @Before + public void before() throws IOException + { + when(authFilter.handle(any(HttpServletRequest.class), any(HttpServletResponse.class))) + .thenReturn(mock(SessionEntry.class)); + } + + @Test + public void testSetKey() throws Exception + { + mockMvc.perform(put("/config/key") + .content("value") + .contentType(MediaType.TEXT_PLAIN)) + .andExpect(status().isOk()); + + verify(configService).setKey(anyInt(), eq("key"), eq("value")); + } +} \ No newline at end of file diff --git a/http-service/src/test/java/net/runelite/http/service/loottracker/LootTrackerControllerTest.java b/http-service/src/test/java/net/runelite/http/service/loottracker/LootTrackerControllerTest.java new file mode 100644 index 0000000000..e708de0c29 --- /dev/null +++ b/http-service/src/test/java/net/runelite/http/service/loottracker/LootTrackerControllerTest.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2019, Adam + * 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.http.service.loottracker; + +import java.io.IOException; +import java.time.Instant; +import java.util.Collections; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import net.runelite.http.api.RuneLiteAPI; +import net.runelite.http.api.loottracker.GameItem; +import net.runelite.http.api.loottracker.LootRecord; +import net.runelite.http.api.loottracker.LootRecordType; +import net.runelite.http.service.account.AuthFilter; +import net.runelite.http.service.account.beans.SessionEntry; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@RunWith(SpringRunner.class) +@WebMvcTest(LootTrackerController.class) +@Slf4j +public class LootTrackerControllerTest +{ + @Autowired + private MockMvc mockMvc; + + @MockBean + private LootTrackerService lootTrackerService; + + @MockBean + private AuthFilter authFilter; + + @Before + public void before() throws IOException + { + when(authFilter.handle(any(HttpServletRequest.class), any(HttpServletResponse.class))) + .thenReturn(mock(SessionEntry.class)); + } + + @Test + public void storeLootRecord() throws Exception + { + LootRecord lootRecord = new LootRecord(); + lootRecord.setType(LootRecordType.NPC); + lootRecord.setTime(Instant.now()); + lootRecord.setDrops(Collections.singletonList(new GameItem(4151, 1))); + + String data = RuneLiteAPI.GSON.toJson(lootRecord); + mockMvc.perform(post("/loottracker").content(data).contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()); + + verify(lootTrackerService).store(eq(lootRecord), anyInt()); + } +} \ No newline at end of file diff --git a/http-service/src/test/resources/application.properties b/http-service/src/test/resources/application.properties deleted file mode 100644 index cb3dcc0507..0000000000 --- a/http-service/src/test/resources/application.properties +++ /dev/null @@ -1,11 +0,0 @@ -oauth.client-id=moo -oauth.client-secret=cow -minio.endpoint=http://10.96.22.171:9000 -minio.accesskey=AM54M27O4WZK65N6F8IP -minio.secretkey=/PZCxzmsJzwCHYlogcymuprniGCaaLUOET2n6yMP -minio.bucket=runelite -runelite.twitter.consumerkey=moo -runelite.twitter.secretkey=cow -runelite.twitter.listid=968949795153948673 -logging.level.net.runelite=DEBUG -spring.jackson.serialization.indent_output=true \ No newline at end of file diff --git a/http-service/src/test/resources/application.yaml b/http-service/src/test/resources/application.yaml new file mode 100644 index 0000000000..128e64d8ef --- /dev/null +++ b/http-service/src/test/resources/application.yaml @@ -0,0 +1,35 @@ +oauth: + client-id: moo + client-secret: cow + +minio: + endpoint: http://10.96.22.171:9000 + accesskey: AM54M27O4WZK65N6F8IP + secretkey: /PZCxzmsJzwCHYlogcymuprniGCaaLUOET2n6yMP + bucket: runelite + +runelite: + twitter: + consumerkey: moo + secretkey: cow + listid: 968949795153948673 + +logging: + level: + net: + runelite: + DEBUG + +datasource: + runelite: + driverClassName: org.h2.Driver + type: org.h2.jdbcx.JdbcDataSource + url: jdbc:h2:mem:runelite + runelite-cache: + driverClassName: org.h2.Driver + type: org.h2.jdbcx.JdbcDataSource + url: jdbc:h2:mem:cache + runelite-tracker: + driverClassName: org.h2.Driver + type: org.h2.jdbcx.JdbcDataSource + url: jdbc:h2:mem:xptracker diff --git a/http-service/src/test/resources/dev.yaml b/http-service/src/test/resources/dev.yaml new file mode 100644 index 0000000000..e4967ccee4 --- /dev/null +++ b/http-service/src/test/resources/dev.yaml @@ -0,0 +1,19 @@ +datasource: + runelite: + driverClassName: com.mysql.jdbc.Driver + type: com.mysql.jdbc.jdbc2.optional.MysqlDataSource + url: jdbc:mysql://192.168.1.2/runelite + username: runelite + password: runelite + runelite-cache: + driverClassName: com.mysql.jdbc.Driver + type: com.mysql.jdbc.jdbc2.optional.MysqlDataSource + url: jdbc:mysql://192.168.1.2/cache + username: runelite + password: runelite + runelite-tracker: + driverClassName: com.mysql.jdbc.Driver + type: com.mysql.jdbc.jdbc2.optional.MysqlDataSource + url: jdbc:mysql://192.168.1.2/xptracker + username: runelite + password: runelite diff --git a/pom.xml b/pom.xml index 0c1f89dc1a..a5187a029c 100644 --- a/pom.xml +++ b/pom.xml @@ -28,7 +28,7 @@ net.runelite runelite-parent - 1.5.12-SNAPSHOT + 1.5.14-SNAPSHOT pom RuneLite diff --git a/protocol-api/pom.xml b/protocol-api/pom.xml index 551b441ed7..df42e1e53a 100644 --- a/protocol-api/pom.xml +++ b/protocol-api/pom.xml @@ -29,7 +29,7 @@ net.runelite runelite-parent - 1.5.12-SNAPSHOT + 1.5.14-SNAPSHOT protocol-api diff --git a/protocol-api/src/main/java/net/runelite/protocol/api/handshake/LoginHandshakePacket.java b/protocol-api/src/main/java/net/runelite/protocol/api/handshake/LoginHandshakePacket.java index 4a6090504e..16ccb85694 100644 --- a/protocol-api/src/main/java/net/runelite/protocol/api/handshake/LoginHandshakePacket.java +++ b/protocol-api/src/main/java/net/runelite/protocol/api/handshake/LoginHandshakePacket.java @@ -25,8 +25,10 @@ package net.runelite.protocol.api.handshake; import lombok.Data; +import lombok.EqualsAndHashCode; @Data +@EqualsAndHashCode(callSuper = true) public class LoginHandshakePacket extends HandshakePacket { diff --git a/protocol-api/src/main/java/net/runelite/protocol/api/handshake/UpdateHandshakePacket.java b/protocol-api/src/main/java/net/runelite/protocol/api/handshake/UpdateHandshakePacket.java index 9d1b20c8f2..86b39d41e8 100644 --- a/protocol-api/src/main/java/net/runelite/protocol/api/handshake/UpdateHandshakePacket.java +++ b/protocol-api/src/main/java/net/runelite/protocol/api/handshake/UpdateHandshakePacket.java @@ -26,9 +26,11 @@ package net.runelite.protocol.api.handshake; import lombok.AllArgsConstructor; import lombok.Data; +import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; @Data +@EqualsAndHashCode(callSuper = true) @NoArgsConstructor @AllArgsConstructor public class UpdateHandshakePacket extends HandshakePacket diff --git a/protocol/pom.xml b/protocol/pom.xml index 5255e7bacb..490f2eb372 100644 --- a/protocol/pom.xml +++ b/protocol/pom.xml @@ -29,7 +29,7 @@ net.runelite runelite-parent - 1.5.12-SNAPSHOT + 1.5.14-SNAPSHOT protocol diff --git a/runelite-api/pom.xml b/runelite-api/pom.xml index 903322a106..f9b875d6fa 100644 --- a/runelite-api/pom.xml +++ b/runelite-api/pom.xml @@ -29,7 +29,7 @@ net.runelite runelite-parent - 1.5.12-SNAPSHOT + 1.5.14-SNAPSHOT runelite-api diff --git a/runelite-api/src/main/java/net/runelite/api/AnimationID.java b/runelite-api/src/main/java/net/runelite/api/AnimationID.java index ee1a50d7ed..1f96e9c898 100644 --- a/runelite-api/src/main/java/net/runelite/api/AnimationID.java +++ b/runelite-api/src/main/java/net/runelite/api/AnimationID.java @@ -123,6 +123,7 @@ public final class AnimationID public static final int HERBLORE_POTIONMAKING = 363; //used for both herb and secondary public static final int MAGIC_CHARGING_ORBS = 726; public static final int MAGIC_MAKE_TABLET = 4068; + public static final int MAGIC_ENCHANTING_JEWELRY = 931; public static final int BURYING_BONES = 827; public static final int USING_GILDED_ALTAR = 3705; public static final int LOOKING_INTO = 832; diff --git a/runelite-api/src/main/java/net/runelite/api/Client.java b/runelite-api/src/main/java/net/runelite/api/Client.java index ca51dc74a9..4c44c7e3ef 100644 --- a/runelite-api/src/main/java/net/runelite/api/Client.java +++ b/runelite-api/src/main/java/net/runelite/api/Client.java @@ -1569,4 +1569,16 @@ public interface Client extends GameEngine int getRasterizer3D_clipMidY2(); void checkClickbox(Model model, int orientation, int pitchSin, int pitchCos, int yawSin, int yawCos, int x, int y, int z, long hash); + + /** + * Sets if a widget is in target mode + */ + void setSpellSelected(boolean selected); + + /** + * Returns client item composition cache + */ + NodeCache getItemCompositionCache(); + + EnumComposition getEnum(int id); } diff --git a/runelite-api/src/main/java/net/runelite/api/DecorativeObject.java b/runelite-api/src/main/java/net/runelite/api/DecorativeObject.java index c2bbd14cf3..145531fa5d 100644 --- a/runelite-api/src/main/java/net/runelite/api/DecorativeObject.java +++ b/runelite-api/src/main/java/net/runelite/api/DecorativeObject.java @@ -38,6 +38,7 @@ public interface DecorativeObject extends TileObject * @see net.runelite.api.model.Jarvis */ Polygon getConvexHull(); + Polygon getConvexHull2(); Renderable getRenderable(); Renderable getRenderable2(); diff --git a/runelite-api/src/main/java/net/runelite/api/EnumComposition.java b/runelite-api/src/main/java/net/runelite/api/EnumComposition.java new file mode 100644 index 0000000000..7bbfaa1693 --- /dev/null +++ b/runelite-api/src/main/java/net/runelite/api/EnumComposition.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2019, Adam + * 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; + +public interface EnumComposition +{ + int[] getIntVals(); + + String[] getStringVals(); + + int getIntValue(int key); + + String getStringValue(int key); +} diff --git a/runelite-api/src/main/java/net/runelite/api/ScriptID.java b/runelite-api/src/main/java/net/runelite/api/ScriptID.java index 778b271036..05c057a38d 100644 --- a/runelite-api/src/main/java/net/runelite/api/ScriptID.java +++ b/runelite-api/src/main/java/net/runelite/api/ScriptID.java @@ -45,6 +45,24 @@ public final class ScriptID */ public static final int CHATBOX_INPUT = 96; + /** + * Opens the Private Message chat interface + * + * Jagex refers to this script as {@code meslayer_mode6} + *
    + *
  • String Player to send private message to
  • + *
+ */ + public static final int OPEN_PRIVATE_MESSAGE_INTERFACE = 107; + + /** + * Rebuilds the text input widget inside the chat interface + *
    + *
  • String Message Prefix. Only used inside the GE search interfaces + *
+ */ + public static final int CHAT_TEXT_INPUT_REBUILD = 222; + /** * Layouts the bank widgets * diff --git a/runelite-api/src/main/java/net/runelite/api/SpriteID.java b/runelite-api/src/main/java/net/runelite/api/SpriteID.java index 2c4ac4f878..e707854c12 100644 --- a/runelite-api/src/main/java/net/runelite/api/SpriteID.java +++ b/runelite-api/src/main/java/net/runelite/api/SpriteID.java @@ -1172,7 +1172,7 @@ public final class SpriteID public static final int MINIMAP_ORB_XP_ACTIVATED = 1197; public static final int MINIMAP_ORB_XP_HOVERED = 1198; public static final int MINIMAP_ORB_XP_ACTIVATED_HOVERED = 1199; - public static final int UNKNOWN_BLACK_BLOBS = 1200; + public static final int MINIMAP_CLICK_MASK = 1200; public static final int OPTIONS_ZOOM_SLIDER_THUMB = 1201; public static final int EMOTE_SIT_UP = 1202; public static final int EMOTE_STAR_JUMP = 1203; diff --git a/runelite-api/src/main/java/net/runelite/api/VarClientStr.java b/runelite-api/src/main/java/net/runelite/api/VarClientStr.java index b3bf1b09b6..72f4ee0303 100644 --- a/runelite-api/src/main/java/net/runelite/api/VarClientStr.java +++ b/runelite-api/src/main/java/net/runelite/api/VarClientStr.java @@ -36,6 +36,7 @@ public enum VarClientStr { CHATBOX_TYPED_TEXT(1), INPUT_TEXT(22), + PRIVATE_MESSAGE_TARGET(23), RECENT_CLAN_CHAT(129); private final int index; diff --git a/runelite-api/src/main/java/net/runelite/api/VarPlayer.java b/runelite-api/src/main/java/net/runelite/api/VarPlayer.java index 37b71aaf66..c3183074b0 100644 --- a/runelite-api/src/main/java/net/runelite/api/VarPlayer.java +++ b/runelite-api/src/main/java/net/runelite/api/VarPlayer.java @@ -37,6 +37,10 @@ public enum VarPlayer ATTACK_STYLE(43), QUEST_POINTS(101), IS_POISONED(102), + /** + * Seems to start at 50(10 splash) and goes down by 1 every 30 seconds + */ + DISEASE_VALUE(456), BANK_TAB(115), diff --git a/runelite-api/src/main/java/net/runelite/api/coords/WorldPoint.java b/runelite-api/src/main/java/net/runelite/api/coords/WorldPoint.java index 3f164c1845..a0a23e6674 100644 --- a/runelite-api/src/main/java/net/runelite/api/coords/WorldPoint.java +++ b/runelite-api/src/main/java/net/runelite/api/coords/WorldPoint.java @@ -25,6 +25,10 @@ */ package net.runelite.api.coords; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; import lombok.Value; import net.runelite.api.Client; import static net.runelite.api.Constants.CHUNK_SIZE; @@ -150,7 +154,8 @@ public class WorldPoint } /** - * Gets the coordinate of the tile that contains the passed local point. + * Gets the coordinate of the tile that contains the passed local point, + * accounting for instances. * * @param client the client * @param localPoint the local coordinate @@ -190,6 +195,46 @@ public class WorldPoint } } + /** + * Get occurrences of a tile on the scene, accounting for instances. There may be + * more than one if the same template chunk occurs more than once on the scene. + * @param client + * @param worldPoint + * @return + */ + public static Collection toLocalInstance(Client client, WorldPoint worldPoint) + { + if (!client.isInInstancedRegion()) + { + return Collections.singleton(worldPoint); + } + + // find instance chunks using the template point. there might be more than one. + List worldPoints = new ArrayList<>(); + final int z = client.getPlane(); + int[][][] instanceTemplateChunks = client.getInstanceTemplateChunks(); + for (int x = 0; x < instanceTemplateChunks[z].length; ++x) + { + for (int y = 0; y < instanceTemplateChunks[z][x].length; ++y) + { + int chunkData = instanceTemplateChunks[z][x][y]; + int rotation = chunkData >> 1 & 0x3; + int templateChunkY = (chunkData >> 3 & 0x7FF) * CHUNK_SIZE; + int templateChunkX = (chunkData >> 14 & 0x3FF) * CHUNK_SIZE; + if (worldPoint.getX() >= templateChunkX && worldPoint.getX() < templateChunkX + CHUNK_SIZE + && worldPoint.getY() >= templateChunkY && worldPoint.getY() < templateChunkY + CHUNK_SIZE) + { + WorldPoint p = new WorldPoint(client.getBaseX() + x * CHUNK_SIZE + (worldPoint.getX() & (CHUNK_SIZE - 1)), + client.getBaseY() + y * CHUNK_SIZE + (worldPoint.getY() & (CHUNK_SIZE - 1)), + worldPoint.getPlane()); + p = rotate(p, rotation); + worldPoints.add(p); + } + } + } + return worldPoints; + } + /** * Rotate the coordinates in the chunk according to chunk rotation * diff --git a/runelite-api/src/main/java/net/runelite/api/vars/InputType.java b/runelite-api/src/main/java/net/runelite/api/vars/InputType.java index db1301b281..c94ccd79ef 100644 --- a/runelite-api/src/main/java/net/runelite/api/vars/InputType.java +++ b/runelite-api/src/main/java/net/runelite/api/vars/InputType.java @@ -38,6 +38,7 @@ public enum InputType RUNELITE_CHATBOX_PANEL(-3), RUNELITE(-2), NONE(0), + PRIVATE_MESSAGE(6), SEARCH(11); private final int type; diff --git a/runelite-api/src/main/java/net/runelite/api/widgets/Widget.java b/runelite-api/src/main/java/net/runelite/api/widgets/Widget.java index 3aa6e23f13..ed4277a2a3 100644 --- a/runelite-api/src/main/java/net/runelite/api/widgets/Widget.java +++ b/runelite-api/src/main/java/net/runelite/api/widgets/Widget.java @@ -79,6 +79,7 @@ public interface Widget /** * Gets the current click configuration of the widget. + * @see WidgetConfig * * @see WidgetConfig */ @@ -551,6 +552,13 @@ public interface Widget */ void setOnMouseOverListener(Object... args); + /** + * Sets a script to be ran every frame when the mouse is in the widget bounds + * + * @param args A ScriptID, then the args for the script + */ + void setOnMouseRepeatListener(Object... args); + /** * Sets a script to be ran when the mouse leaves the widget bounds * @@ -565,6 +573,20 @@ public interface Widget */ void setOnTimerListener(Object... args); + /** + * Sets a script to be ran when the target mode has been activated for this widget + * + * @param args A ScriptID, then the args for the script + */ + void setOnTargetEnterListener(Object... args); + + /** + * Sets a script to be ran when the target mode has been deactivated for this widget + * + * @param args A ScriptID, then the args for the script + */ + void setOnTargetLeaveListener(Object... args); + /** * If this widget has any listeners on it */ @@ -769,4 +791,34 @@ public interface Widget * Sets if the rectangle is filled or just stroked */ void setFilled(boolean filled); + + /** + * Verb for spell targets + */ + String getTargetVerb(); + + /** + * Verb for spell targets + */ + void setTargetVerb(String targetVerb); + + /** + * Can widgets under this widgets be clicked in this widgets bounding box + */ + boolean getNoClickThrough(); + + /** + * Can widgets under this widgets be clicked in this widgets bounding box + */ + void setNoClickThrough(boolean noClickThrough); + + /** + * Can widgets under this widgets be scrolled in this widgets bounding box + */ + boolean getNoScrollThrough(); + + /** + * Can widgets under this widgets be scrolled in this widgets bounding box + */ + void setNoScrollThrough(boolean noScrollThrough); } diff --git a/runelite-api/src/main/java/net/runelite/api/widgets/WidgetConfig.java b/runelite-api/src/main/java/net/runelite/api/widgets/WidgetConfig.java index 0b8f6a587d..31829b5417 100644 --- a/runelite-api/src/main/java/net/runelite/api/widgets/WidgetConfig.java +++ b/runelite-api/src/main/java/net/runelite/api/widgets/WidgetConfig.java @@ -24,6 +24,8 @@ */ package net.runelite.api.widgets; +import net.runelite.api.MenuAction; + /** * Utility class used for defining options to be used on the click mask * of a {@link Widget}. @@ -36,12 +38,61 @@ public class WidgetConfig * Enables displaying a ninth option on a menu. */ public static final int SHOW_MENU_OPTION_NINE = 1 << 9; + + /** + * Can this widget be used on a item on the floor + */ + public static final int USE_GROUND_ITEM = 1 << 11; + + /** + * Can this widget be used on a NPC + */ + public static final int USE_NPC = 2 << 11; + + /** + * Can this widget be used on a game object + */ + public static final int USE_OBJECT = 4 << 11; + + /** + * Can this widget be used on a player + */ + public static final int USE_PLAYER = 8 << 11; + + /** + * Can this widget be used on a item in your inventory + */ + public static final int USE_ITEM = 16 << 11; + + /** + * Can this widget be used on a widget with the WIDGET_USE_TARGET flag + */ + public static final int USE_WIDGET = 32 << 11; + /** * Controls whether or not a widget can have another dragged onto it. */ public static final int DRAG_ON = 1 << 17; + /** * Controls whether or not a widget can be dragged around. */ public static final int DRAG = 1 << 20; + + /** + * Can widgets with USE_WIDGET be used on this widget + */ + public static final int WIDGET_USE_TARGET = 1 << 21; + + /** + * Is the widget an (inventory?) item + */ + public static final int ITEM = 1 << 30; + + /** + * Add a USE option + * + * @see MenuAction#ITEM_USE + */ + public static final int ITEM_USE_OP = 1 << 31; } diff --git a/runelite-api/src/main/java/net/runelite/api/widgets/WidgetID.java b/runelite-api/src/main/java/net/runelite/api/widgets/WidgetID.java index 9f07f9ba37..be7e595dd7 100644 --- a/runelite-api/src/main/java/net/runelite/api/widgets/WidgetID.java +++ b/runelite-api/src/main/java/net/runelite/api/widgets/WidgetID.java @@ -125,6 +125,8 @@ public class WidgetID public static final int SKOTIZO_GROUP_ID = 308; public static final int ENTERING_HOUSE_GROUP_ID = 71; public static final int FULLSCREEN_MAP_GROUP_ID = 165; + public static final int QUESTLIST_GROUP_ID = 399; + public static final int SKILLS_GROUP_ID = 320; static class WorldMap { @@ -292,6 +294,7 @@ public class WidgetID static final int TOGGLE_RUN_ORB = 22; // Has the "Toggle run" name static final int RUN_ORB_TEXT = 23; static final int SPEC_ORB = 28; + static final int WORLDMAP_ORB = 40; } static class LoginClickToPlayScreen @@ -747,4 +750,11 @@ public class WidgetID { static final int ROOT = 25; } + + static class QuestList + { + static final int FREE_CONTAINER = 9; + static final int MEMBERS_CONTAINER = 10; + static final int MINIQUEST_CONTAINER = 11; + } } diff --git a/runelite-api/src/main/java/net/runelite/api/widgets/WidgetInfo.java b/runelite-api/src/main/java/net/runelite/api/widgets/WidgetInfo.java index 556dc57bf5..7808e06cba 100644 --- a/runelite-api/src/main/java/net/runelite/api/widgets/WidgetInfo.java +++ b/runelite-api/src/main/java/net/runelite/api/widgets/WidgetInfo.java @@ -159,6 +159,7 @@ public enum WidgetInfo MINIMAP_RUN_ORB_TEXT(WidgetID.MINIMAP_GROUP_ID, WidgetID.Minimap.RUN_ORB_TEXT), MINIMAP_HEALTH_ORB(WidgetID.MINIMAP_GROUP_ID, WidgetID.Minimap.HEALTH_ORB), MINIMAP_SPEC_ORB(WidgetID.MINIMAP_GROUP_ID, WidgetID.Minimap.SPEC_ORB), + MINIMAP_WORLDMAP_ORB(WidgetID.MINIMAP_GROUP_ID, WidgetID.Minimap.WORLDMAP_ORB), LOGIN_CLICK_TO_PLAY_SCREEN(WidgetID.LOGIN_CLICK_TO_PLAY_GROUP_ID, 0), LOGIN_CLICK_TO_PLAY_SCREEN_MESSAGE_OF_THE_DAY(WidgetID.LOGIN_CLICK_TO_PLAY_GROUP_ID, WidgetID.LoginClickToPlayScreen.MESSAGE_OF_THE_DAY), @@ -463,7 +464,11 @@ public enum WidgetInfo SKOTIZO_CONTAINER(WidgetID.SKOTIZO_GROUP_ID, WidgetID.Skotizo.CONTAINER), - FULLSCREEN_MAP_ROOT(WidgetID.FULLSCREEN_MAP_GROUP_ID, WidgetID.FullScreenMap.ROOT); + FULLSCREEN_MAP_ROOT(WidgetID.FULLSCREEN_MAP_GROUP_ID, WidgetID.FullScreenMap.ROOT), + + QUESTLIST_FREE_CONTAINER(WidgetID.QUESTLIST_GROUP_ID, WidgetID.QuestList.FREE_CONTAINER), + QUESTLIST_MEMBERS_CONTAINER(WidgetID.QUESTLIST_GROUP_ID, WidgetID.QuestList.MEMBERS_CONTAINER), + QUESTLIST_MINIQUEST_CONTAINER(WidgetID.QUESTLIST_GROUP_ID, WidgetID.QuestList.MINIQUEST_CONTAINER); private final int groupId; private final int childId; diff --git a/runelite-api/src/main/java/net/runelite/api/widgets/WidgetType.java b/runelite-api/src/main/java/net/runelite/api/widgets/WidgetType.java index da76a7580f..9d140c3cd0 100644 --- a/runelite-api/src/main/java/net/runelite/api/widgets/WidgetType.java +++ b/runelite-api/src/main/java/net/runelite/api/widgets/WidgetType.java @@ -27,9 +27,12 @@ package net.runelite.api.widgets; public final class WidgetType { public static final int LAYER = 0; + public static final int INVENTORY = 2; public static final int RECTANGLE = 3; public static final int TEXT = 4; public static final int GRAPHIC = 5; public static final int MODEL = 6; + public static final int TEXT_INVENTORY = 7; + public static final int IF1_TOOLTIP = 8; public static final int LINE = 9; } diff --git a/runelite-client/pom.xml b/runelite-client/pom.xml index 6306dc3763..e026f85423 100644 --- a/runelite-client/pom.xml +++ b/runelite-client/pom.xml @@ -29,7 +29,7 @@ net.runelite runelite-parent - 1.5.12-SNAPSHOT + 1.5.14-SNAPSHOT client diff --git a/runelite-client/src/main/java/net/runelite/client/RuneLite.java b/runelite-client/src/main/java/net/runelite/client/RuneLite.java index b7fae34e2c..ecdda28103 100644 --- a/runelite-client/src/main/java/net/runelite/client/RuneLite.java +++ b/runelite-client/src/main/java/net/runelite/client/RuneLite.java @@ -183,9 +183,9 @@ public class RuneLite System.exit(0); } - final boolean developerMode = options.has("developer-mode"); + final boolean developerMode = options.has("developer-mode") && RuneLiteProperties.getLauncherVersion() == null; - if (developerMode && RuneLiteProperties.getLauncherVersion() == null) + if (developerMode) { boolean assertions = false; assert assertions = true; diff --git a/runelite-client/src/main/java/net/runelite/client/config/ConfigManager.java b/runelite-client/src/main/java/net/runelite/client/config/ConfigManager.java index 2cde4900ad..b24c9ccb96 100644 --- a/runelite-client/src/main/java/net/runelite/client/config/ConfigManager.java +++ b/runelite-client/src/main/java/net/runelite/client/config/ConfigManager.java @@ -43,8 +43,11 @@ import java.lang.reflect.Modifier; import java.lang.reflect.Proxy; import java.nio.channels.FileLock; import java.nio.charset.Charset; +import java.text.DateFormat; +import java.text.SimpleDateFormat; import java.time.Instant; import java.util.Arrays; +import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -70,6 +73,7 @@ import net.runelite.http.api.config.Configuration; public class ConfigManager { private static final String SETTINGS_FILE_NAME = "settings.properties"; + private static final DateFormat TIME_FORMAT = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss"); @Inject EventBus eventBus; @@ -111,12 +115,17 @@ public class ConfigManager load(); // load profile specific config } + private File getLocalPropertiesFile() + { + return new File(RuneLite.RUNELITE_DIR, SETTINGS_FILE_NAME); + } + private File getPropertiesFile() { // Sessions that aren't logged in have no username if (session == null || session.getUsername() == null) { - return new File(RuneLite.RUNELITE_DIR, SETTINGS_FILE_NAME); + return getLocalPropertiesFile(); } else { @@ -158,7 +167,13 @@ public class ConfigManager for (ConfigEntry entry : configuration.getConfig()) { log.debug("Loading configuration value from client {}: {}", entry.getKey(), entry.getValue()); - final String[] split = entry.getKey().split("\\."); + final String[] split = entry.getKey().split("\\.", 2); + + if (split.length != 2) + { + continue; + } + final String groupName = split[0]; final String key = split[1]; final String value = entry.getValue(); @@ -174,7 +189,7 @@ public class ConfigManager try { - saveToFile(); + saveToFile(propertiesFile); log.debug("Updated configuration on disk with the latest version"); } @@ -184,6 +199,75 @@ public class ConfigManager } } + private synchronized void syncPropertiesFromFile(File propertiesFile) + { + final Properties properties = new Properties(); + try (FileInputStream in = new FileInputStream(propertiesFile)) + { + properties.load(new InputStreamReader(in, Charset.forName("UTF-8"))); + } + catch (Exception e) + { + log.debug("Malformed properties, skipping update"); + return; + } + + final Map copy = (Map) ImmutableMap.copyOf(this.properties); + copy.forEach((groupAndKey, value) -> + { + if (!properties.containsKey(groupAndKey)) + { + final String[] split = groupAndKey.split("\\.", 2); + if (split.length != 2) + { + return; + } + + final String groupName = split[0]; + final String key = split[1]; + unsetConfiguration(groupName, key); + } + }); + + properties.forEach((objGroupAndKey, objValue) -> + { + final String groupAndKey = String.valueOf(objGroupAndKey); + final String[] split = groupAndKey.split("\\.", 2); + if (split.length != 2) + { + return; + } + + final String groupName = split[0]; + final String key = split[1]; + final String value = String.valueOf(objValue); + setConfiguration(groupName, key, value); + }); + } + + public void importLocal() + { + if (session == null) + { + // No session, no import + return; + } + + final File file = new File(propertiesFile.getParent(), propertiesFile.getName() + "." + TIME_FORMAT.format(new Date())); + + try + { + saveToFile(file); + } + catch (IOException e) + { + log.warn("Backup failed, skipping import", e); + return; + } + + syncPropertiesFromFile(getLocalPropertiesFile()); + } + private synchronized void loadFromFile() { properties.clear(); @@ -231,7 +315,7 @@ public class ConfigManager } } - private synchronized void saveToFile() throws IOException + private synchronized void saveToFile(final File propertiesFile) throws IOException { propertiesFile.getParentFile().mkdirs(); @@ -294,8 +378,6 @@ public class ConfigManager public void setConfiguration(String groupName, String key, String value) { - log.debug("Setting configuration value for {}.{} to {}", groupName, key, value); - String oldValue = (String) properties.setProperty(groupName + "." + key, value); if (Objects.equals(oldValue, value)) @@ -303,6 +385,8 @@ public class ConfigManager return; } + log.debug("Setting configuration value for {}.{} to {}", groupName, key, value); + synchronized (pendingChanges) { pendingChanges.put(groupName + "." + key, value); @@ -312,7 +396,7 @@ public class ConfigManager { try { - saveToFile(); + saveToFile(propertiesFile); } catch (IOException ex) { @@ -337,8 +421,6 @@ public class ConfigManager public void unsetConfiguration(String groupName, String key) { - log.debug("Unsetting configuration value for {}.{}", groupName, key); - String oldValue = (String) properties.remove(groupName + "." + key); if (oldValue == null) @@ -346,6 +428,8 @@ public class ConfigManager return; } + log.debug("Unsetting configuration value for {}.{}", groupName, key); + synchronized (pendingChanges) { pendingChanges.put(groupName + "." + key, null); @@ -355,7 +439,7 @@ public class ConfigManager { try { - saveToFile(); + saveToFile(propertiesFile); } catch (IOException ex) { diff --git a/runelite-client/src/main/java/net/runelite/client/discord/DiscordService.java b/runelite-client/src/main/java/net/runelite/client/discord/DiscordService.java index 0c2739b8a2..091c479ac6 100644 --- a/runelite-client/src/main/java/net/runelite/client/discord/DiscordService.java +++ b/runelite-client/src/main/java/net/runelite/client/discord/DiscordService.java @@ -78,7 +78,7 @@ public class DiscordService implements AutoCloseable discordRPC = DiscordRPC.INSTANCE; discordEventHandlers = new DiscordEventHandlers(); } - catch (UnsatisfiedLinkError e) + catch (Error e) { log.warn("Failed to load Discord library, Discord support will be disabled."); } @@ -150,9 +150,12 @@ public class DiscordService implements AutoCloseable ? "default" : discordPresence.getLargeImageKey(); discordRichPresence.largeImageText = discordPresence.getLargeImageText(); - discordRichPresence.smallImageKey = Strings.isNullOrEmpty(discordPresence.getSmallImageKey()) - ? "default" - : discordPresence.getSmallImageKey(); + + if (!Strings.isNullOrEmpty(discordPresence.getSmallImageKey())) + { + discordRichPresence.smallImageKey = discordPresence.getSmallImageKey(); + } + discordRichPresence.smallImageText = discordPresence.getSmallImageText(); discordRichPresence.partyId = discordPresence.getPartyId(); discordRichPresence.partySize = discordPresence.getPartySize(); diff --git a/runelite-client/src/main/java/net/runelite/client/events/ChatboxInput.java b/runelite-client/src/main/java/net/runelite/client/events/ChatboxInput.java index 6388c8b433..cad7d38077 100644 --- a/runelite-client/src/main/java/net/runelite/client/events/ChatboxInput.java +++ b/runelite-client/src/main/java/net/runelite/client/events/ChatboxInput.java @@ -25,8 +25,10 @@ package net.runelite.client.events; import lombok.Data; +import lombok.EqualsAndHashCode; @Data +@EqualsAndHashCode(callSuper = true) public abstract class ChatboxInput extends ChatInput { private final String value; diff --git a/runelite-client/src/main/java/net/runelite/client/events/PrivateMessageInput.java b/runelite-client/src/main/java/net/runelite/client/events/PrivateMessageInput.java index fc31fae7f1..80a189f273 100644 --- a/runelite-client/src/main/java/net/runelite/client/events/PrivateMessageInput.java +++ b/runelite-client/src/main/java/net/runelite/client/events/PrivateMessageInput.java @@ -25,8 +25,10 @@ package net.runelite.client.events; import lombok.Data; +import lombok.EqualsAndHashCode; @Data +@EqualsAndHashCode(callSuper = true) public abstract class PrivateMessageInput extends ChatInput { private final String target; diff --git a/runelite-client/src/main/java/net/runelite/client/game/AgilityShortcut.java b/runelite-client/src/main/java/net/runelite/client/game/AgilityShortcut.java new file mode 100644 index 0000000000..c376bd34f1 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/game/AgilityShortcut.java @@ -0,0 +1,242 @@ +/* + * Copyright (c) 2018, SomeoneWithAnInternetConnection + * Copyright (c) 2019, MrGroggle + * 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.game; + +import lombok.Getter; +import static net.runelite.api.NullObjectID.*; +import static net.runelite.api.ObjectID.*; +import net.runelite.api.coords.WorldPoint; + +@Getter +public enum AgilityShortcut +{ + GENERIC_SHORTCUT(1, "Shortcut", null, + // Trollheim + ROCKS_3790, ROCKS_3791, + // Fremennik Slayer Cave + STEPS_29993, + // Fossil Island + LADDER_30938, LADDER_30939, LADDER_30940, LADDER_30941, RUBBER_CAP_MUSHROOM, + // Brimhaven dungeon + CREVICE_30198, + // Lumbridge + STILE_12982, + // Gu'Tanoth Bridge + GAP, GAP_2831, + // Lumbridge Swamp Caves + STEPPING_STONE_5948, STEPPING_STONE_5949, ROCKS_6673, + // Morytania Pirate Ship + ROCK_16115, + // Lumber Yard + BROKEN_FENCE_2618, + // McGrubor's Wood + LOOSE_RAILING, + // Underwater Area Fossil Island + TUNNEL_30959, HOLE_30966, OBSTACLE, OBSTACLE_30767, OBSTACLE_30964, OBSTACLE_30962, + // Tree Gnome Village + LOOSE_RAILING_2186, + // Burgh de Rott + LOW_FENCE, + // Taverley + STILE, + // Asgarnian Ice Dungeon + STEPS, + // Fossil Island Wyvern Cave + STAIRS_31485), + BRIMHAVEN_DUNGEON_MEDIUM_PIPE_RETURN(1, "Pipe Squeeze", null, new WorldPoint(2698, 9491, 0), PIPE_21727), + BRIMHAVEN_DUNGEON_PIPE_RETURN(1, "Pipe Squeeze", null, new WorldPoint(2655, 9573, 0), PIPE_21728), + BRIMHAVEN_DUNGEON_STEPPING_STONES_RETURN(1, "Pipe Squeeze", null, STEPPING_STONE_21739), + BRIMHAVEN_DUNGEON_LOG_BALANCE_RETURN(1, "Log Balance", null, LOG_BALANCE_20884), + AGILITY_PYRAMID_ROCKS_WEST(1, "Rocks", null, CLIMBING_ROCKS_11948), + CAIRN_ISLE_CLIMBING_ROCKS(1, "Rocks", null, CLIMBING_ROCKS), + KARAMJA_GLIDER_LOG(1, "Log Balance", new WorldPoint(2906, 3050, 0), A_WOODEN_LOG ), + FALADOR_CRUMBLING_WALL(5, "Crumbling Wall", new WorldPoint(2936, 3357, 0), CRUMBLING_WALL_24222 ), + RIVER_LUM_GRAPPLE_WEST(8, "Grapple Broken Raft", new WorldPoint(3245, 3179, 0), BROKEN_RAFT), + RIVER_LUM_GRAPPLE_EAST(8, "Grapple Broken Raft", new WorldPoint(3258, 3179, 0), BROKEN_RAFT), + CORSAIR_COVE_ROCKS(10, "Rocks", new WorldPoint(2545, 2871, 0), ROCKS_31757), + KARAMJA_MOSS_GIANT_SWING(10, "Rope", null, ROPESWING_23568, ROPESWING_23569), + FALADOR_GRAPPLE_WALL(11, "Grapple Wall", new WorldPoint(3031, 3391, 0), WALL_17049, WALL_17050), + BRIMHAVEN_DUNGEON_STEPPING_STONES(12, "Stepping Stones", null, STEPPING_STONE_21738), + VARROCK_SOUTH_FENCE(13, "Fence", new WorldPoint(3239, 3334, 0), FENCE_16518), + GOBLIN_VILLAGE_WALL(14, "Wall", new WorldPoint(2925, 3523, 0), TIGHTGAP), + CORSAIR_COVE_DUNGEON_PILLAR(15, "Pillar Jump", new WorldPoint(1980, 8996, 0), PILLAR_31809), + EDGEVILLE_DUNGEON_MONKEYBARS(15, "Monkey Bars", null, MONKEYBARS_23566), + TROLLHEIM_ROCKS(15, "Rocks", null, new WorldPoint(2838, 3614, 0), ROCKS_3748), // No fixed world map location, but rocks near death plateau have a requirement of 15 + YANILLE_UNDERWALL_TUNNEL(16, "Underwall Tunnel", new WorldPoint(2574, 3109, 0), HOLE_16520, WALL_17047), + YANILLE_WATCHTOWER_TRELLIS(18, "Trellis", null, TRELLIS_20056), + COAL_TRUCKS_LOG_BALANCE(20, "Log Balance", new WorldPoint(2598, 3475, 0), LOG_BALANCE_23274), + GRAND_EXCHANGE_UNDERWALL_TUNNEL(21, "Underwall Tunnel", new WorldPoint(3139, 3515, 0), UNDERWALL_TUNNEL_16529, UNDERWALL_TUNNEL_16530), + BRIMHAVEN_DUNGEON_PIPE(22, "Pipe Squeeze", new WorldPoint(2654, 9569, 0), PIPE_21728), + OBSERVATORY_SCALE_CLIFF(23, "Grapple Rocks", new WorldPoint(2447, 3155, 0), NULL_31849), + EAGLES_PEAK_ROCK_CLIMB(25, "Rock Climb", new WorldPoint(2320, 3499, 0), ROCKS_19849), + FALADOR_UNDERWALL_TUNNEL(26, "Underwall Tunnel", new WorldPoint(2947, 3313, 0), UNDERWALL_TUNNEL, UNDERWALL_TUNNEL_16528), + MOUNT_KARUULM_LOWER(29, "Rocks", new WorldPoint(1324, 3782, 0), ROCKS_34397), + CORSAIR_COVE_RESOURCE_ROCKS(30, "Rocks", new WorldPoint(2486, 2898, 0), ROCKS_31758, ROCKS_31759), + SOUTHEAST_KARAJMA_STEPPING_STONES(30, "Stepping Stones", new WorldPoint(2924, 2946, 0), STEPPING_STONES, STEPPING_STONES_23646, STEPPING_STONES_23647), + BRIMHAVEN_DUNGEON_LOG_BALANCE(30, "Log Balance", null, LOG_BALANCE_20882), + AGILITY_PYRAMID_ROCKS_EAST(30, "Rocks", null, CLIMBING_ROCKS_11949), + DRAYNOR_MANOR_STEPPING_STONES(31, "Stepping Stones", new WorldPoint(3150, 3362, 0), STEPPING_STONE_16533), + CATHERBY_CLIFFSIDE_GRAPPLE(32, "Grapple Rock", new WorldPoint(2868, 3429, 0), ROCKS_17042), + CAIRN_ISLE_ROCKS(32, "Rocks", null, ROCKS_2231), + ARDOUGNE_LOG_BALANCE(33, "Log Balance", new WorldPoint(2602, 3336, 0), LOG_BALANCE_16546, LOG_BALANCE_16547, LOG_BALANCE_16548), + BRIMHAVEN_DUNGEON_MEDIUM_PIPE(34, "Pipe Squeeze", null, new WorldPoint(2698, 9501, 0), PIPE_21727), + CATHERBY_OBELISK_GRAPPLE(36, "Grapple Rock", new WorldPoint(2841, 3434, 0), CROSSBOW_TREE_17062), + GNOME_STRONGHOLD_ROCKS(37, "Rocks", new WorldPoint(2485, 3515, 0), ROCKS_16534, ROCKS_16535), + AL_KHARID_MINING_PITCLIFF_SCRAMBLE(38, "Rocks", new WorldPoint(3305, 3315, 0), ROCKS_16549, ROCKS_16550), + YANILLE_WALL_GRAPPLE(39, "Grapple Wall", new WorldPoint(2552, 3072, 0), CASTLE_WALL), + NEITIZNOT_BRIDGE_REPAIR(40, "Bridge Repair - Quest", new WorldPoint(2315, 3828, 0), ROPE_BRIDGE_21306, ROPE_BRIDGE_21307), + KOUREND_LAKE_JUMP_EAST(40, "Stepping Stones", new WorldPoint(1612, 3570, 0), STEPPING_STONE_29729, STEPPING_STONE_29730), + KOUREND_LAKE_JUMP_WEST(40, "Stepping Stones", new WorldPoint(1604, 3572, 0), STEPPING_STONE_29729, STEPPING_STONE_29730), + YANILLE_DUNGEON_BALANCE(40, "Balancing Ledge", null, BALANCING_LEDGE_23548), + TROLLHEIM_EASY_CLIFF_SCRAMBLE(41, "Rocks", new WorldPoint(2869, 3670, 0), ROCKS_16521), + DWARVEN_MINE_NARROW_CREVICE(42, "Narrow Crevice", new WorldPoint(3034, 9806, 0), CREVICE_16543), + DRAYNOR_UNDERWALL_TUNNEL(42, "Underwall Tunnel", new WorldPoint(3068, 3261, 0), UNDERWALL_TUNNEL_19032, UNDERWALL_TUNNEL_19036), + TROLLHEIM_MEDIUM_CLIFF_SCRAMBLE_NORTH(43, "Rocks", new WorldPoint(2886, 3684, 0), ROCKS_3803, ROCKS_3804, ROCKS_16522), + TROLLHEIM_MEDIUM_CLIFF_SCRAMBLE_SOUTH(43, "Rocks", new WorldPoint(2876, 3666, 0), ROCKS_3803, ROCKS_3804, ROCKS_16522), + TROLLHEIM_ADVANCED_CLIFF_SCRAMBLE(44, "Rocks", new WorldPoint(2907, 3686, 0), ROCKS_16523, ROCKS_3748), + KOUREND_RIVER_STEPPING_STONES(45, "Stepping Stones", new WorldPoint(1721, 3509, 0), STEPPING_STONE_29728), + TIRANNWN_LOG_BALANCE(45, "Log Balance", null, LOG_BALANCE_3933, LOG_BALANCE_3931, LOG_BALANCE_3930, LOG_BALANCE_3929, LOG_BALANCE_3932), + COSMIC_ALTAR_MEDIUM_WALKWAY(46, "Narrow Walkway", new WorldPoint(2399, 4403, 0), JUTTING_WALL_17002), + DEEP_WILDERNESS_DUNGEON_CREVICE_NORTH(46, "Narrow Crevice", new WorldPoint(3047, 10335, 0), CREVICE_19043), + DEEP_WILDERNESS_DUNGEON_CREVICE_SOUTH(46, "Narrow Crevice", new WorldPoint(3045, 10327, 0), CREVICE_19043), + TROLLHEIM_HARD_CLIFF_SCRAMBLE(47, "Rocks", new WorldPoint(2902, 3680, 0), ROCKS_16524), + FREMENNIK_LOG_BALANCE(48, "Log Balance", new WorldPoint(2721, 3591, 0), LOG_BALANCE_16540, LOG_BALANCE_16541, LOG_BALANCE_16542), + YANILLE_DUNGEON_PIPE_SQUEEZE(49, "Pipe Squeeze", null, OBSTACLE_PIPE_23140), + ARCEUUS_ESSENCE_MINE_BOULDER(49, "Boulder", new WorldPoint(1774, 3888, 0), BOULDER_27990), + MORYTANIA_STEPPING_STONE(50, "Stepping Stone", new WorldPoint(3418, 3326, 0), STEPPING_STONE_13504), + VARROCK_SEWERS_PIPE_SQUEEZE(51, "Pipe Squeeze", new WorldPoint(3152, 9905, 0), OBSTACLE_PIPE_16511), + ARCEUUS_ESSENCE_MINE_EAST_SCRAMBLE(52, "Rock Climb", new WorldPoint(1770, 3851, 0), ROCKS_27987, ROCKS_27988), + KARAMJA_VOLCANO_GRAPPLE_NORTH(53, "Grapple Rock", new WorldPoint(2873, 3143, 0), STRONG_TREE_17074), + KARAMJA_VOLCANO_GRAPPLE_SOUTH(53, "Grapple Rock", new WorldPoint(2874, 3128, 0), STRONG_TREE_17074), + MOTHERLODE_MINE_WALL_EAST(54, "Wall", new WorldPoint(3124, 9703, 0), DARK_TUNNEL_10047), + MOTHERLODE_MINE_WALL_WEST(54, "Wall", new WorldPoint(3118, 9702, 0), DARK_TUNNEL_10047), + MISCELLANIA_DOCK_STEPPING_STONE(55, "Stepping Stone", new WorldPoint(2572, 3862, 0), STEPPING_STONE_11768), + ISAFDAR_FOREST_OBSTACLES(56, "Trap", null, DENSE_FOREST_3938, DENSE_FOREST_3939, DENSE_FOREST_3998, DENSE_FOREST_3999, DENSE_FOREST, LEAVES, LEAVES_3924, LEAVES_3925, STICKS, TRIPWIRE), + RELEKKA_EAST_FENCE(57, "Fence", new WorldPoint(2688, 3697, 0), BROKEN_FENCE), + YANILLE_DUNGEON_MONKEY_BARS(57, "Monkey Bars", null, MONKEYBARS_23567), + PHASMATYS_ECTOPOOL_SHORTCUT(58, "Weathered Wall", null , WEATHERED_WALL, WEATHERED_WALL_16526), + ELVEN_OVERPASS_CLIFF_SCRAMBLE(59, "Rocks", new WorldPoint(2345, 3300, 0), ROCKS_16514, ROCKS_16515), + WILDERNESS_GWD_CLIMB_EAST(60, "Rocks", new WorldPoint(2943, 3770, 0), ROCKY_HANDHOLDS_26400, ROCKY_HANDHOLDS_26401, ROCKY_HANDHOLDS_26402, ROCKY_HANDHOLDS_26404, ROCKY_HANDHOLDS_26405, ROCKY_HANDHOLDS_26406), + WILDERNESS_GWD_CLIMB_WEST(60, "Rocks", new WorldPoint(2928, 3760, 0), ROCKY_HANDHOLDS_26400, ROCKY_HANDHOLDS_26401, ROCKY_HANDHOLDS_26402, ROCKY_HANDHOLDS_26404, ROCKY_HANDHOLDS_26405, ROCKY_HANDHOLDS_26406), + MOS_LEHARMLESS_STEPPING_STONE(60, "Stepping Stone", new WorldPoint(3710, 2970, 0), STEPPING_STONE_19042), + WINTERTODT_GAP(60, "Gap", new WorldPoint(1629, 4023, 0), GAP_29326), + UNGAEL_ICE(60, "Ice Chunks", null, NULL_25337, NULL_29868, NULL_29869, NULL_29870, ICE_CHUNKS_31822, NULL_31823, ICE_CHUNKS_31990), + SLAYER_TOWER_MEDIUM_CHAIN_FIRST(61, "Spiked Chain (Floor 1)", new WorldPoint(3421, 3550, 0), SPIKEY_CHAIN), + SLAYER_TOWER_MEDIUM_CHAIN_SECOND(61, "Spiked Chain (Floor 2)", new WorldPoint(3420, 3551, 0), SPIKEY_CHAIN_16538), + SLAYER_DUNGEON_CREVICE(62, "Narrow Crevice", new WorldPoint(2729, 10008, 0), CREVICE_16539), + MOUNT_KARUULM_UPPER(62, "Rocks", new WorldPoint(1322, 3791, 0), ROCKS_34396), + TAVERLEY_DUNGEON_RAILING(63, "Loose Railing", new WorldPoint(2935, 9811, 0), LOOSE_RAILING_28849), + TROLLHEIM_WILDERNESS_ROCKS_EAST(64, "Rocks", new WorldPoint(2945, 3678, 0), ROCKS_16545), + TROLLHEIM_WILDERNESS_ROCKS_WEST(64, "Rocks", new WorldPoint(2917, 3672, 0), ROCKS_16545), + FOSSIL_ISLAND_VOLCANO(64, "Rope", new WorldPoint(3780, 3822, 0), ROPE_ANCHOR, ROPE_ANCHOR_30917), + MORYTANIA_TEMPLE(65, "Loose Railing", new WorldPoint(3422, 3476, 0), ROCKS_16998, ROCKS_16999, ORNATE_RAILING, ORNATE_RAILING_17000), + REVENANT_CAVES_GREEN_DRAGONS(65, "Jump", new WorldPoint(3220, 10086, 0), PILLAR_31561), + COSMIC_ALTAR_ADVANCED_WALKWAY(66, "Narrow Walkway", new WorldPoint(2408, 4401, 0), JUTTING_WALL_17002), + LUMBRIDGE_DESERT_STEPPING_STONE(66, "Stepping Stone", new WorldPoint(3210, 3135, 0), STEPPING_STONE_16513), + HEROES_GUILD_TUNNEL_EAST(67, "Crevice", new WorldPoint(2898, 9901, 0), CREVICE_9739, CREVICE_9740), + HEROES_GUILD_TUNNEL_WEST(67, "Crevice", new WorldPoint(2913, 9895, 0), CREVICE_9739, CREVICE_9740), + YANILLE_DUNGEON_RUBBLE_CLIMB(67, "Pile of Rubble", null, PILE_OF_RUBBLE_23563, PILE_OF_RUBBLE_23564), + ELVEN_OVERPASS_MEDIUM_CLIFF(68, "Rocks", new WorldPoint(2337, 3288, 0), ROCKS_16514, ROCKS_16515), + WEISS_OBSTACLES(68, "Shortcut", null, LITTLE_BOULDER, ROCKSLIDE_33184, ROCKSLIDE_33185, NULL_33327, NULL_33328, LEDGE_33190, ROCKSLIDE_33191, FALLEN_TREE_33192), + ARCEUUS_ESSENSE_NORTH(69, "Rock Climb", new WorldPoint(1759, 3873, 0), ROCKS_27984, ROCKS_27985), + TAVERLEY_DUNGEON_PIPE_BLUE_DRAGON(70, "Pipe Squeeze", new WorldPoint(2886, 9798, 0), OBSTACLE_PIPE_16509), + TAVERLEY_DUNGEON_ROCKS_NORTH(70, "Rocks", new WorldPoint(2887, 9823, 0), ROCKS, ROCKS_14106), + TAVERLEY_DUNGEON_ROCKS_SOUTH(70, "Rocks", new WorldPoint(2887, 9631, 0), ROCKS, ROCKS_14106), + FOSSIL_ISLAND_HARDWOOD_NORTH(70, "Hole" , new WorldPoint(3713, 3827, 0), HOLE_31481, HOLE_31482), + FOSSIL_ISLAND_HARDWOOD_SOUTH(70, "Hole" , new WorldPoint(3715, 3817, 0), HOLE_31481, HOLE_31482), + AL_KHARID_WINDOW(70, "Window", new WorldPoint(3293, 3158, 0), BROKEN_WALL_33344, BIG_WINDOW), + GWD_SARADOMIN_ROPE_NORTH(70, "Rope Descent", new WorldPoint(2912, 5300, 0), NULL_26371), + GWD_SARADOMIN_ROPE_SOUTH(70, "Rope Descent", new WorldPoint(2951, 5267, 0), NULL_26375), + SLAYER_TOWER_ADVANCED_CHAIN_FIRST(71, "Spiked Chain (Floor 2)", new WorldPoint(3447, 3578, 0), SPIKEY_CHAIN ), + SLAYER_TOWER_ADVANCED_CHAIN_SECOND(71, "Spiked Chain (Floor 3)", new WorldPoint(3446, 3576, 0), SPIKEY_CHAIN_16538), + STRONGHOLD_SLAYER_CAVE_TUNNEL(72, "Tunnel", new WorldPoint(2431, 9806, 0), TUNNEL_30174, TUNNEL_30175), + TROLL_STRONGHOLD_WALL_CLIMB(73, "Rocks", new WorldPoint(2841, 3694, 0), ROCKS_16464), + ARCEUUS_ESSENSE_MINE_WEST(73, "Rock Climb", new WorldPoint(1742, 3853, 0), ROCKS_27984, ROCKS_27985 ), + LAVA_DRAGON_ISLE_JUMP(74, "Stepping Stone", new WorldPoint(3200, 3807, 0), STEPPING_STONE_14918), + REVENANT_CAVES_DEMONS_JUMP(75, "Jump", new WorldPoint(3199, 10135, 0), PILLAR_31561), + REVENANT_CAVES_ANKOU_EAST(75, "Jump", new WorldPoint(3201, 10195, 0), PILLAR_31561), + REVENANT_CAVES_ANKOU_NORTH(75, "Jump", new WorldPoint(3180, 10209, 0), PILLAR_31561), + ZUL_ANDRA_ISLAND_CROSSING(76, "Stepping Stone", new WorldPoint(2156, 3073, 0), STEPPING_STONE_10663), + SHILO_VILLAGE_STEPPING_STONES( 77, "Stepping Stones", new WorldPoint(2863, 2974, 0), STEPPING_STONE_16466), + KHARAZI_JUNGLE_VINE_CLIMB(79, "Vine", new WorldPoint(2897, 2939, 0), NULL_26884, NULL_26886), + TAVERLEY_DUNGEON_SPIKED_BLADES(80, "Strange Floor", new WorldPoint(2877, 9813, 0), STRANGE_FLOOR), + SLAYER_DUNGEON_CHASM_JUMP(81, "Spiked Blades", new WorldPoint(2770, 10003, 0), STRANGE_FLOOR_16544), + LAVA_MAZE_NORTH_JUMP(82, "Stepping Stone", new WorldPoint(3092, 3880, 0), STEPPING_STONE_14917), + BRIMHAVEN_DUNGEON_EAST_STEPPING_STONES_NORTH(83, "Stepping Stones", new WorldPoint(2685, 9547, 0), STEPPING_STONE_19040), + BRIMHAVEN_DUNGEON_EAST_STEPPING_STONES_SOUTH(83, "Stepping Stones", new WorldPoint(2693, 9529, 0), STEPPING_STONE_19040), + ELVEN_ADVANCED_CLIFF_SCRAMBLE(85, "Rocks", new WorldPoint(2337, 3253, 0), ROCKS_16514, ROCKS_16514), + KALPHITE_WALL(86, "Crevice", new WorldPoint(3214, 9508, 0), CREVICE_16465), + BRIMHAVEN_DUNGEON_VINE_EAST(87, "Vine", new WorldPoint(2672, 9582, 0), VINE_26880, VINE_26882), + BRIMHAVEN_DUNGEON_VINE_WEST(87, "Vine", new WorldPoint(2606, 9584, 0), VINE_26880, VINE_26882), + REVENANT_CAVES_CHAMBER_JUMP(89, "Jump", new WorldPoint(3240, 10144, 0), PILLAR_31561); + + /** + * The agility level required to pass the shortcut + */ + @Getter + private final int level; + /** + * Brief description of the shortcut (e.g. 'Rocks', 'Stepping Stones', 'Jump') + */ + @Getter + private final String description; + /** + * The location of the Shortcut icon on the world map (null if there is no icon) + */ + @Getter + private final WorldPoint worldMapLocation; + /** + * An optional location in case the location of the shortcut icon is either + * null or isn't close enough to the obstacle + */ + @Getter + private final WorldPoint worldLocation; + /** + * Array of obstacles, null objects, decorations etc. that this shortcut uses. + * Typically an ObjectID/NullObjectID + */ + @Getter + private final int[] obstacleIds; + + AgilityShortcut(int level, String description, WorldPoint mapLocation, WorldPoint worldLocation, int... obstacleIds) + { + this.level = level; + this.description = description; + this.worldMapLocation = mapLocation; + this.worldLocation = worldLocation; + this.obstacleIds = obstacleIds; + } + + AgilityShortcut(int level, String description, WorldPoint location, int... obstacleIds) + { + this(level, description, location, location, obstacleIds); + } + + public String getTooltip() + { + return description + " - Level " + level; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/game/ItemManager.java b/runelite-client/src/main/java/net/runelite/client/game/ItemManager.java index ad06024a7d..c91de9262c 100644 --- a/runelite-client/src/main/java/net/runelite/client/game/ItemManager.java +++ b/runelite-client/src/main/java/net/runelite/client/game/ItemManager.java @@ -256,6 +256,15 @@ public class ItemManager itemCompositions.put(event.getItemComposition().getId(), event.getItemComposition()); } + /** + * Invalidates internal item manager item composition cache (but not client item composition cache) + * @see Client#getItemCompositionCache() + */ + public void invalidateItemCompositionCache() + { + itemCompositions.invalidateAll(); + } + /** * Look up an item's price * diff --git a/runelite-client/src/main/java/net/runelite/client/game/SpriteManager.java b/runelite-client/src/main/java/net/runelite/client/game/SpriteManager.java index f0df8286cd..f177377818 100644 --- a/runelite-client/src/main/java/net/runelite/client/game/SpriteManager.java +++ b/runelite-client/src/main/java/net/runelite/client/game/SpriteManager.java @@ -28,6 +28,7 @@ import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import com.google.inject.Inject; import java.awt.image.BufferedImage; +import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; import javax.annotation.Nullable; @@ -36,11 +37,14 @@ import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JLabel; import javax.swing.SwingUtilities; +import lombok.extern.slf4j.Slf4j; import net.runelite.api.Client; import net.runelite.api.GameState; import net.runelite.api.SpritePixels; import net.runelite.client.callback.ClientThread; +import net.runelite.client.util.ImageUtil; +@Slf4j @Singleton public class SpriteManager { @@ -127,4 +131,36 @@ public class SpriteManager }); }); } + + public void addSpriteOverrides(SpriteOverride[] add) + { + if (add.length <= 0) + { + return; + } + + clientThread.invokeLater(() -> + { + Map overrides = client.getSpriteOverrides(); + Class owner = add[0].getClass(); + for (SpriteOverride o : add) + { + BufferedImage image = ImageUtil.getResourceStreamFromClass(owner, o.getFileName()); + SpritePixels sp = ImageUtil.getImageSpritePixels(image, client); + overrides.put(o.getSpriteId(), sp); + } + }); + } + + public void removeSpriteOverrides(SpriteOverride[] remove) + { + clientThread.invokeLater(() -> + { + Map overrides = client.getSpriteOverrides(); + for (SpriteOverride o : remove) + { + overrides.remove(o.getSpriteId()); + } + }); + } } diff --git a/runelite-client/src/main/java/net/runelite/client/game/SpriteOverride.java b/runelite-client/src/main/java/net/runelite/client/game/SpriteOverride.java new file mode 100644 index 0000000000..a4f894d5c0 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/game/SpriteOverride.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2018 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.client.game; + +import net.runelite.api.SpriteID; + +public interface SpriteOverride +{ + /** + * An ID for a sprite. Negative numbers are used by RuneLite specific sprites + * + * @see SpriteID + */ + int getSpriteId(); + + /** + * The file name for the resource to be loaded, relative to the implementing class + */ + String getFileName(); +} diff --git a/runelite-client/src/main/java/net/runelite/client/game/chatbox/ChatboxTextInput.java b/runelite-client/src/main/java/net/runelite/client/game/chatbox/ChatboxTextInput.java index e340027d1a..e59e47059d 100644 --- a/runelite-client/src/main/java/net/runelite/client/game/chatbox/ChatboxTextInput.java +++ b/runelite-client/src/main/java/net/runelite/client/game/chatbox/ChatboxTextInput.java @@ -24,7 +24,10 @@ */ package net.runelite.client.game.chatbox; +import com.google.common.base.Strings; +import com.google.common.primitives.Ints; import com.google.inject.Inject; +import java.awt.Point; import java.awt.Rectangle; import java.awt.Toolkit; import java.awt.datatransfer.DataFlavor; @@ -33,21 +36,25 @@ import java.awt.datatransfer.UnsupportedFlavorException; import java.awt.event.KeyEvent; import java.awt.event.MouseEvent; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; import java.util.function.Consumer; import java.util.function.IntPredicate; import java.util.function.Predicate; import java.util.function.ToIntFunction; +import java.util.regex.Pattern; import javax.swing.SwingUtilities; +import lombok.AllArgsConstructor; import lombok.Getter; import lombok.extern.slf4j.Slf4j; -import net.runelite.api.FontTypeFace; import net.runelite.api.FontID; -import net.runelite.api.widgets.WidgetType; +import net.runelite.api.FontTypeFace; import net.runelite.api.widgets.JavaScriptCallback; import net.runelite.api.widgets.Widget; import net.runelite.api.widgets.WidgetPositionMode; import net.runelite.api.widgets.WidgetSizeMode; import net.runelite.api.widgets.WidgetTextAlignment; +import net.runelite.api.widgets.WidgetType; import net.runelite.client.callback.ClientThread; import net.runelite.client.input.KeyListener; import net.runelite.client.input.MouseListener; @@ -57,6 +64,7 @@ import net.runelite.client.util.Text; public class ChatboxTextInput extends ChatboxInput implements KeyListener, MouseListener { private static final int CURSOR_FLASH_RATE_MILLIS = 1000; + private static final Pattern BREAK_MATCHER = Pattern.compile("[^a-zA-Z0-9']"); private final ChatboxPanelManager chatboxPanelManager; private final ClientThread clientThread; @@ -66,13 +74,24 @@ public class ChatboxTextInput extends ChatboxInput implements KeyListener, Mouse return i -> i >= 32 && i < 127; } + @AllArgsConstructor + private static class Line + { + private final int start; + private final int end; + private final String text; + } + @Getter private String prompt; + @Getter + private int lines; + private StringBuffer value = new StringBuffer(); @Getter - private int cursor = 0; + private int cursorStart = 0; @Getter private int cursorEnd = 0; @@ -95,12 +114,14 @@ public class ChatboxTextInput extends ChatboxInput implements KeyListener, Mouse @Getter private int fontID = FontID.QUILL_8; - // This is a lambda so I can have atomic updates for it's captures - private ToIntFunction getCharOffset = null; - private Predicate isInBounds = null; - + @Getter private boolean built = false; + // These are lambdas for atomic updates + private Predicate isInBounds = null; + private ToIntFunction getLineOffset = null; + private ToIntFunction getPointCharOffset = null; + @Inject protected ChatboxTextInput(ChatboxPanelManager chatboxPanelManager, ClientThread clientThread) { @@ -108,6 +129,16 @@ public class ChatboxTextInput extends ChatboxInput implements KeyListener, Mouse this.clientThread = clientThread; } + public ChatboxTextInput lines(int lines) + { + this.lines = lines; + if (built) + { + clientThread.invoke(this::update); + } + return this; + } + public ChatboxTextInput prompt(String prompt) { this.prompt = prompt; @@ -157,7 +188,7 @@ public class ChatboxTextInput extends ChatboxInput implements KeyListener, Mouse end = v; } - this.cursor = start; + this.cursorStart = start; this.cursorEnd = end; if (built) @@ -209,7 +240,6 @@ public class ChatboxTextInput extends ChatboxInput implements KeyListener, Mouse protected void update() { - this.built = true; Widget container = chatboxPanelManager.getContainerWidget(); container.deleteAllChildren(); @@ -232,103 +262,209 @@ public class ChatboxTextInput extends ChatboxInput implements KeyListener, Mouse protected void buildEdit(int x, int y, int w, int h) { + final List editLines = new ArrayList<>(); + Widget container = chatboxPanelManager.getContainerWidget(); - String lt = Text.escapeJagex(value.substring(0, this.cursor)); - String mt = Text.escapeJagex(value.substring(this.cursor, this.cursorEnd)); - String rt = Text.escapeJagex(value.substring(this.cursorEnd)); - - Widget leftText = container.createChild(-1, WidgetType.TEXT); - Widget cursor = container.createChild(-1, WidgetType.RECTANGLE); - Widget middleText = container.createChild(-1, WidgetType.TEXT); - Widget rightText = container.createChild(-1, WidgetType.TEXT); - - leftText.setFontId(fontID); - FontTypeFace font = leftText.getFont(); + final Widget cursor = container.createChild(-1, WidgetType.RECTANGLE); + long start = System.currentTimeMillis(); + cursor.setOnTimerListener((JavaScriptCallback) ev -> + { + boolean on = (System.currentTimeMillis() - start) % CURSOR_FLASH_RATE_MILLIS > (CURSOR_FLASH_RATE_MILLIS / 2); + cursor.setOpacity(on ? 255 : 0); + }); + cursor.setTextColor(0xFFFFFF); + cursor.setHasListener(true); + cursor.setFilled(true); + cursor.setFontId(fontID); + FontTypeFace font = cursor.getFont(); if (h <= 0) { h = font.getBaseline(); } - int ltw = font.getTextWidth(lt); - int mtw = font.getTextWidth(mt); - int rtw = font.getTextWidth(rt); + final int oy = y; + final int ox = x; + final int oh = h; - int fullWidth = ltw + mtw + rtw; - - int ox = x; - if (w > 0) + int breakIndex = -1; + final StringBuilder sb = new StringBuilder(); + for (int i = 0; i < value.length(); i++) { - x += (w - fullWidth) / 2; - } - - int ltx = x; - int mtx = ltx + ltw; - int rtx = mtx + mtw; - - leftText.setText(lt); - leftText.setOriginalX(ltx); - leftText.setOriginalY(y); - leftText.setOriginalWidth(ltw); - leftText.setOriginalHeight(h); - leftText.revalidate(); - - if (!mt.isEmpty()) - { - cursor.setTextColor(0x113399); - } - else - { - cursor.setTextColor(0xFFFFFF); - long start = System.currentTimeMillis(); - cursor.setOnTimerListener((JavaScriptCallback) ev -> + int count = i - sb.length(); + final String c = value.charAt(i) + ""; + sb.append(c); + if (BREAK_MATCHER.matcher(c).matches()) { - boolean on = (System.currentTimeMillis() - start) % CURSOR_FLASH_RATE_MILLIS > (CURSOR_FLASH_RATE_MILLIS / 2); - cursor.setOpacity(on ? 255 : 0); - }); - cursor.setHasListener(true); + breakIndex = sb.length(); + } + + if (i == value.length() - 1) + { + Line line = new Line(count, count + sb.length() - 1, sb.toString()); + editLines.add(line); + break; + } + + if (font.getTextWidth(sb.toString() + value.charAt(i + 1)) < w) + { + continue; + } + + if (editLines.size() < this.lines - 1 || this.lines == 0) + { + if (breakIndex > 1) + { + String str = sb.substring(0, breakIndex); + Line line = new Line(count, count + str.length() - 1, str); + editLines.add(line); + + sb.replace(0, breakIndex, ""); + breakIndex = -1; + continue; + } + + Line line = new Line(count, count + sb.length() - 1, sb.toString()); + editLines.add(line); + sb.replace(0, sb.length(), ""); + } } - cursor.setFilled(true); - cursor.setOriginalX(mtx - 1); - cursor.setOriginalY(y); - cursor.setOriginalWidth(2 + mtw); - cursor.setOriginalHeight(h); - cursor.revalidate(); - middleText.setText(mt); - middleText.setFontId(fontID); - middleText.setOriginalX(mtx); - middleText.setOriginalY(y); - middleText.setOriginalWidth(mtw); - middleText.setOriginalHeight(h); - middleText.setTextColor(0xFFFFFF); - middleText.revalidate(); + Rectangle bounds = new Rectangle(container.getCanvasLocation().getX() + container.getWidth(), y, 0, editLines.size() * oh); + for (int i = 0; i < editLines.size() || i == 0; i++) + { + final Line line = editLines.size() > 0 ? editLines.get(i) : new Line(0, 0, ""); + final String text = line.text; + final int len = text.length(); - rightText.setText(rt); - rightText.setFontId(fontID); - rightText.setOriginalX(rtx); - rightText.setOriginalY(y); - rightText.setOriginalWidth(rtw); - rightText.setOriginalHeight(h); - rightText.revalidate(); + String lt = Text.escapeJagex(text); + String mt = ""; + String rt = ""; + + final boolean isStartLine = cursorOnLine(cursorStart, line.start, line.end) + || (cursorOnLine(cursorStart, line.start, line.end + 1) && i == editLines.size() - 1); + + final boolean isEndLine = cursorOnLine(cursorEnd, line.start, line.end); + + if (isStartLine || isEndLine || (cursorEnd > line.end && cursorStart < line.start)) + { + final int cIdx = Ints.constrainToRange(cursorStart - line.start, 0, len); + final int ceIdx = Ints.constrainToRange(cursorEnd - line.start, 0, len); + + lt = Text.escapeJagex(text.substring(0, cIdx)); + mt = Text.escapeJagex(text.substring(cIdx, ceIdx)); + rt = Text.escapeJagex(text.substring(ceIdx)); + } + + final int ltw = font.getTextWidth(lt); + final int mtw = font.getTextWidth(mt); + final int rtw = font.getTextWidth(rt); + final int fullWidth = ltw + mtw + rtw; + + int ltx = ox; + if (w > 0) + { + ltx += (w - fullWidth) / 2; + } + + final int mtx = ltx + ltw; + final int rtx = mtx + mtw; + + if (ltx < bounds.x) + { + bounds.setLocation(ltx, bounds.y); + } + + if (fullWidth > bounds.width) + { + bounds.setSize(fullWidth, bounds.height); + } + + if (editLines.size() == 0 || isStartLine) + { + cursor.setOriginalX(mtx - 1); + cursor.setOriginalY(y); + cursor.setOriginalWidth(2); + cursor.setOriginalHeight(h); + cursor.revalidate(); + } + + if (!Strings.isNullOrEmpty(lt)) + { + final Widget leftText = container.createChild(-1, WidgetType.TEXT); + leftText.setFontId(fontID); + leftText.setText(lt); + leftText.setOriginalX(ltx); + leftText.setOriginalY(y); + leftText.setOriginalWidth(ltw); + leftText.setOriginalHeight(h); + leftText.revalidate(); + } + + if (!Strings.isNullOrEmpty(mt)) + { + final Widget background = container.createChild(-1, WidgetType.RECTANGLE); + background.setTextColor(0x113399); + background.setFilled(true); + background.setOriginalX(mtx - 1); + background.setOriginalY(y); + background.setOriginalWidth(2 + mtw); + background.setOriginalHeight(h); + background.revalidate(); + + final Widget middleText = container.createChild(-1, WidgetType.TEXT); + middleText.setText(mt); + middleText.setFontId(fontID); + middleText.setOriginalX(mtx); + middleText.setOriginalY(y); + middleText.setOriginalWidth(mtw); + middleText.setOriginalHeight(h); + middleText.setTextColor(0xFFFFFF); + middleText.revalidate(); + } + + if (!Strings.isNullOrEmpty(rt)) + { + final Widget rightText = container.createChild(-1, WidgetType.TEXT); + rightText.setText(rt); + rightText.setFontId(fontID); + rightText.setOriginalX(rtx); + rightText.setOriginalY(y); + rightText.setOriginalWidth(rtw); + rightText.setOriginalHeight(h); + rightText.revalidate(); + } + + y += h; + } net.runelite.api.Point ccl = container.getCanvasLocation(); - int canvasX = ltx + ccl.getX(); - Rectangle bounds = new Rectangle(ccl.getX() + ox, ccl.getY() + y, w > 0 ? w : fullWidth, h); - String tsValue = value.toString(); - isInBounds = ev -> bounds.contains(ev.getPoint()); - getCharOffset = ev -> + isInBounds = ev -> bounds.contains(new Point(ev.getX() - ccl.getX(), ev.getY() - ccl.getY())); + getPointCharOffset = p -> { - if (fullWidth <= 0) + if (bounds.width <= 0) { return 0; } - int cx = ev.getX() - canvasX; + int cx = p.x - ccl.getX() - ox; + int cy = p.y - ccl.getY() - oy; - int charIndex = (tsValue.length() * cx) / fullWidth; + int currentLine = Ints.constrainToRange(cy / oh, 0, editLines.size() - 1); + + final Line line = editLines.get(currentLine); + final String tsValue = line.text; + int charIndex = tsValue.length(); + int fullWidth = font.getTextWidth(tsValue); + + int tx = ox; + if (w > 0) + { + tx += (w - fullWidth) / 2; + } + cx -= tx; // `i` is used to track max execution time incase there is a font with ligature width data that causes this to fail for (int i = tsValue.length(); i >= 0 && charIndex >= 0 && charIndex <= tsValue.length(); i--) @@ -353,22 +489,72 @@ public class ChatboxTextInput extends ChatboxInput implements KeyListener, Mouse break; } - if (charIndex < 0) + charIndex = Ints.constrainToRange(charIndex, 0, tsValue.length()); + return line.start + charIndex; + }; + + getLineOffset = code -> + { + if (editLines.size() < 2) { - charIndex = 0; - } - if (charIndex > tsValue.length()) - { - charIndex = tsValue.length(); + return cursorStart; } - return charIndex; + int currentLine = -1; + for (int i = 0; i < editLines.size(); i++) + { + Line l = editLines.get(i); + if (cursorOnLine(cursorStart, l.start, l.end) + || (cursorOnLine(cursorStart, l.start, l.end + 1) && i == editLines.size() - 1)) + { + currentLine = i; + break; + } + } + + if (currentLine == -1 + || (code == KeyEvent.VK_UP && currentLine == 0) + || (code == KeyEvent.VK_DOWN && currentLine == editLines.size() - 1)) + { + return cursorStart; + } + + final Line line = editLines.get(currentLine); + final int direction = code == KeyEvent.VK_UP ? -1 : 1; + final Point dest = new Point(cursor.getCanvasLocation().getX(), cursor.getCanvasLocation().getY() + (direction * oh)); + final int charOffset = getPointCharOffset.applyAsInt(dest); + + // Place cursor on right line if whitespace keep it on the same line or skip a line + final Line nextLine = editLines.get(currentLine + direction); + if ((direction == -1 && charOffset >= line.start) + || (direction == 1 && (charOffset > nextLine.end && (currentLine + direction != editLines.size() - 1)))) + { + return nextLine.end; + } + + return charOffset; }; } + private boolean cursorOnLine(final int cursor, final int start, final int end) + { + return (cursor >= start) && (cursor <= end); + } + + private int getCharOffset(MouseEvent ev) + { + if (getPointCharOffset == null) + { + return 0; + } + + return getPointCharOffset.applyAsInt(ev.getPoint()); + } + @Override protected void open() { + this.built = true; update(); } @@ -398,12 +584,12 @@ public class ChatboxTextInput extends ChatboxInput implements KeyListener, Mouse char c = e.getKeyChar(); if (charValidator.test(c)) { - if (cursor != cursorEnd) + if (cursorStart != cursorEnd) { - value.delete(cursor, cursorEnd); + value.delete(cursorStart, cursorEnd); } - value.insert(cursor, c); - cursorAt(cursor + 1); + value.insert(cursorStart, c); + cursorAt(cursorStart + 1); if (onChanged != null) { onChanged.accept(getValue()); @@ -421,13 +607,13 @@ public class ChatboxTextInput extends ChatboxInput implements KeyListener, Mouse { case KeyEvent.VK_X: case KeyEvent.VK_C: - if (cursor != cursorEnd) + if (cursorStart != cursorEnd) { - String s = value.substring(cursor, cursorEnd); + String s = value.substring(cursorStart, cursorEnd); if (code == KeyEvent.VK_X) { - value.delete(cursor, cursorEnd); - cursorAt(cursor); + value.delete(cursorStart, cursorEnd); + cursorAt(cursorStart); } Toolkit.getDefaultToolkit() .getSystemClipboard() @@ -441,20 +627,20 @@ public class ChatboxTextInput extends ChatboxInput implements KeyListener, Mouse .getSystemClipboard() .getData(DataFlavor.stringFlavor) .toString(); - if (cursor != cursorEnd) + if (cursorStart != cursorEnd) { - value.delete(cursor, cursorEnd); + value.delete(cursorStart, cursorEnd); } for (int i = 0; i < s.length(); i++) { char ch = s.charAt(i); if (charValidator.test(ch)) { - value.insert(cursor, ch); - cursor++; + value.insert(cursorStart, ch); + cursorStart++; } } - cursorAt(cursor); + cursorAt(cursorStart); if (onChanged != null) { onChanged.accept(getValue()); @@ -468,13 +654,13 @@ public class ChatboxTextInput extends ChatboxInput implements KeyListener, Mouse } return; } - int newPos = cursor; + int newPos = cursorStart; if (ev.isShiftDown()) { if (selectionEnd == -1 || selectionStart == -1) { - selectionStart = cursor; - selectionEnd = cursor; + selectionStart = cursorStart; + selectionEnd = cursorStart; } newPos = selectionEnd; } @@ -486,20 +672,20 @@ public class ChatboxTextInput extends ChatboxInput implements KeyListener, Mouse switch (code) { case KeyEvent.VK_DELETE: - if (cursor != cursorEnd) + if (cursorStart != cursorEnd) { - value.delete(cursor, cursorEnd); - cursorAt(cursor); + value.delete(cursorStart, cursorEnd); + cursorAt(cursorStart); if (onChanged != null) { onChanged.accept(getValue()); } return; } - if (cursor < value.length()) + if (cursorStart < value.length()) { - value.deleteCharAt(cursor); - cursorAt(cursor); + value.deleteCharAt(cursorStart); + cursorAt(cursorStart); if (onChanged != null) { onChanged.accept(getValue()); @@ -507,20 +693,20 @@ public class ChatboxTextInput extends ChatboxInput implements KeyListener, Mouse } return; case KeyEvent.VK_BACK_SPACE: - if (cursor != cursorEnd) + if (cursorStart != cursorEnd) { - value.delete(cursor, cursorEnd); - cursorAt(cursor); + value.delete(cursorStart, cursorEnd); + cursorAt(cursorStart); if (onChanged != null) { onChanged.accept(getValue()); } return; } - if (cursor > 0) + if (cursorStart > 0) { - value.deleteCharAt(cursor - 1); - cursorAt(cursor - 1); + value.deleteCharAt(cursorStart - 1); + cursorAt(cursorStart - 1); if (onChanged != null) { onChanged.accept(getValue()); @@ -535,6 +721,14 @@ public class ChatboxTextInput extends ChatboxInput implements KeyListener, Mouse ev.consume(); newPos++; break; + case KeyEvent.VK_UP: + ev.consume(); + newPos = getLineOffset.applyAsInt(code); + break; + case KeyEvent.VK_DOWN: + ev.consume(); + newPos = getLineOffset.applyAsInt(code); + break; case KeyEvent.VK_HOME: ev.consume(); newPos = 0; @@ -553,9 +747,9 @@ public class ChatboxTextInput extends ChatboxInput implements KeyListener, Mouse return; case KeyEvent.VK_ESCAPE: ev.consume(); - if (cursor != cursorEnd) + if (cursorStart != cursorEnd) { - cursorAt(cursor); + cursorAt(cursorStart); return; } chatboxPanelManager.close(); @@ -602,16 +796,16 @@ public class ChatboxTextInput extends ChatboxInput implements KeyListener, Mouse } if (isInBounds == null || !isInBounds.test(mouseEvent)) { - if (cursor != cursorEnd) + if (cursorStart != cursorEnd) { selectionStart = -1; selectionEnd = -1; - cursorAt(getCharOffset.applyAsInt(mouseEvent)); + cursorAt(getCharOffset(mouseEvent)); } return mouseEvent; } - int nco = getCharOffset.applyAsInt(mouseEvent); + int nco = getCharOffset(mouseEvent); if (mouseEvent.isShiftDown() && selectionEnd != -1) { @@ -653,7 +847,7 @@ public class ChatboxTextInput extends ChatboxInput implements KeyListener, Mouse return mouseEvent; } - int nco = getCharOffset.applyAsInt(mouseEvent); + int nco = getCharOffset(mouseEvent); if (selectionStart != -1) { selectionEnd = nco; diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/agility/AgilityOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/agility/AgilityOverlay.java index bc6e953f6d..e9c205f64f 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/agility/AgilityOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/agility/AgilityOverlay.java @@ -36,6 +36,7 @@ import net.runelite.api.Client; import net.runelite.api.Point; import net.runelite.api.Tile; import net.runelite.api.coords.LocalPoint; +import net.runelite.client.game.AgilityShortcut; import net.runelite.client.ui.overlay.Overlay; import net.runelite.client.ui.overlay.OverlayLayer; import net.runelite.client.ui.overlay.OverlayPosition; @@ -44,6 +45,7 @@ import net.runelite.client.ui.overlay.OverlayUtil; class AgilityOverlay extends Overlay { private static final int MAX_DISTANCE = 2350; + private static final Color SHORTCUT_HIGH_LEVEL_COLOR = Color.ORANGE; private final Client client; private final AgilityPlugin plugin; @@ -66,14 +68,15 @@ class AgilityOverlay extends Overlay LocalPoint playerLocation = client.getLocalPlayer().getLocalLocation(); Point mousePosition = client.getMouseCanvasPosition(); final List marksOfGrace = plugin.getMarksOfGrace(); - plugin.getObstacles().forEach((object, tile) -> + plugin.getObstacles().forEach((object, obstacle) -> { - if (Obstacles.SHORTCUT_OBSTACLE_IDS.contains(object.getId()) && !config.highlightShortcuts() || + if (Obstacles.SHORTCUT_OBSTACLE_IDS.containsKey(object.getId()) && !config.highlightShortcuts() || Obstacles.TRAP_OBSTACLE_IDS.contains(object.getId()) && !config.showTrapOverlay()) { return; } + Tile tile = obstacle.getTile(); if (tile.getPlane() == client.getPlane() && object.getLocalLocation().distanceTo(playerLocation) < MAX_DISTANCE) { @@ -87,11 +90,11 @@ class AgilityOverlay extends Overlay } return; } - Area objectClickbox = object.getClickbox(); if (objectClickbox != null) { - Color configColor = config.getOverlayColor(); + AgilityShortcut agilityShortcut = obstacle.getShortcut(); + Color configColor = agilityShortcut == null || agilityShortcut.getLevel() <= plugin.getAgilityLevel() ? config.getOverlayColor() : SHORTCUT_HIGH_LEVEL_COLOR; if (config.highlightMarks() && !marksOfGrace.isEmpty()) { configColor = config.getMarkColor(); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/agility/AgilityPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/agility/AgilityPlugin.java index cd60c6b940..c895a07980 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/agility/AgilityPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/agility/AgilityPlugin.java @@ -38,10 +38,12 @@ import net.runelite.api.Item; import net.runelite.api.ItemID; import static net.runelite.api.ItemID.AGILITY_ARENA_TICKET; import net.runelite.api.Player; +import net.runelite.api.Skill; import static net.runelite.api.Skill.AGILITY; import net.runelite.api.Tile; import net.runelite.api.TileObject; import net.runelite.api.coords.WorldPoint; +import net.runelite.api.events.BoostedLevelChanged; import net.runelite.api.events.ConfigChanged; import net.runelite.api.events.DecorativeObjectChanged; import net.runelite.api.events.DecorativeObjectDespawned; @@ -63,6 +65,7 @@ import net.runelite.api.events.WallObjectSpawned; import net.runelite.client.Notifier; import net.runelite.client.config.ConfigManager; import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.game.AgilityShortcut; import net.runelite.client.game.ItemManager; import net.runelite.client.plugins.Plugin; import net.runelite.client.plugins.PluginDescriptor; @@ -80,7 +83,7 @@ public class AgilityPlugin extends Plugin private static final int AGILITY_ARENA_REGION_ID = 11157; @Getter - private final Map obstacles = new HashMap<>(); + private final Map obstacles = new HashMap<>(); @Getter private final List marksOfGrace = new ArrayList<>(); @@ -115,6 +118,9 @@ public class AgilityPlugin extends Plugin private int lastAgilityXp; private WorldPoint lastArenaTicketPosition; + @Getter + private int agilityLevel; + @Provides AgilityConfig getConfig(ConfigManager configManager) { @@ -126,6 +132,7 @@ public class AgilityPlugin extends Plugin { overlayManager.add(agilityOverlay); overlayManager.add(lapCounterOverlay); + agilityLevel = client.getBoostedSkillLevel(Skill.AGILITY); } @Override @@ -136,6 +143,7 @@ public class AgilityPlugin extends Plugin marksOfGrace.clear(); obstacles.clear(); session = null; + agilityLevel = 0; } @Subscribe @@ -208,6 +216,17 @@ public class AgilityPlugin extends Plugin } } + + @Subscribe + public void onBoostedLevelChanged(BoostedLevelChanged boostedLevelChanged) + { + Skill skill = boostedLevelChanged.getSkill(); + if (skill == AGILITY) + { + agilityLevel = client.getBoostedSkillLevel(skill); + } + } + @Subscribe public void onItemSpawned(ItemSpawned itemSpawned) { @@ -366,11 +385,40 @@ public class AgilityPlugin extends Plugin } if (Obstacles.COURSE_OBSTACLE_IDS.contains(newObject.getId()) || - Obstacles.SHORTCUT_OBSTACLE_IDS.contains(newObject.getId()) || (Obstacles.TRAP_OBSTACLE_IDS.contains(newObject.getId()) && Obstacles.TRAP_OBSTACLE_REGIONS.contains(newObject.getWorldLocation().getRegionID()))) { - obstacles.put(newObject, tile); + obstacles.put(newObject, new Obstacle(tile, null)); + } + + if (Obstacles.SHORTCUT_OBSTACLE_IDS.containsKey(newObject.getId())) + { + AgilityShortcut closestShortcut = null; + int distance = -1; + + // Find the closest shortcut to this object + for (AgilityShortcut shortcut : Obstacles.SHORTCUT_OBSTACLE_IDS.get(newObject.getId())) + { + if (shortcut.getWorldLocation() == null) + { + closestShortcut = shortcut; + break; + } + else + { + int newDistance = shortcut.getWorldLocation().distanceTo2D(newObject.getWorldLocation()); + if (closestShortcut == null || newDistance < distance) + { + closestShortcut = shortcut; + distance = newDistance; + } + } + } + + if (closestShortcut != null) + { + obstacles.put(newObject, new Obstacle(tile, closestShortcut)); + } } } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/agility/Obstacle.java b/runelite-client/src/main/java/net/runelite/client/plugins/agility/Obstacle.java new file mode 100644 index 0000000000..6038de468b --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/agility/Obstacle.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2019, MrGroggle + * 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 HOLDER 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.agility; + +import javax.annotation.Nullable; +import lombok.AllArgsConstructor; +import lombok.Value; +import net.runelite.api.Tile; +import net.runelite.client.game.AgilityShortcut; + +@Value +@AllArgsConstructor +class Obstacle +{ + private final Tile tile; + @Nullable + private final AgilityShortcut shortcut; +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/agility/Obstacles.java b/runelite-client/src/main/java/net/runelite/client/plugins/agility/Obstacles.java index 43a96bd151..92df605a36 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/agility/Obstacles.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/agility/Obstacles.java @@ -25,11 +25,27 @@ package net.runelite.client.plugins.agility; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Multimap; import java.util.List; import java.util.Set; -import static net.runelite.api.NullObjectID.*; +import static net.runelite.api.NullObjectID.NULL_10872; +import static net.runelite.api.NullObjectID.NULL_10873; +import static net.runelite.api.NullObjectID.NULL_12945; +import static net.runelite.api.NullObjectID.NULL_18083; +import static net.runelite.api.NullObjectID.NULL_18116; +import static net.runelite.api.NullObjectID.NULL_18122; +import static net.runelite.api.NullObjectID.NULL_18124; +import static net.runelite.api.NullObjectID.NULL_18129; +import static net.runelite.api.NullObjectID.NULL_18130; +import static net.runelite.api.NullObjectID.NULL_18132; +import static net.runelite.api.NullObjectID.NULL_18133; +import static net.runelite.api.NullObjectID.NULL_18135; +import static net.runelite.api.NullObjectID.NULL_18136; +import static net.runelite.api.NullObjectID.NULL_3550; import static net.runelite.api.ObjectID.*; +import net.runelite.client.game.AgilityShortcut; class Obstacles { @@ -62,7 +78,7 @@ class Obstacles STEPPING_STONE_15412, TROPICAL_TREE_15414, MONKEYBARS_15417, SKULL_SLOPE_15483, ROPE_15487, TROPICAL_TREE_16062, // Falador ROUGH_WALL_10833, TIGHTROPE_10834, HAND_HOLDS_10836, GAP_11161, GAP_11360, TIGHTROPE_11361, - TIGHTROPE_11364, GAP_11365, LEDGE_11366, LEDGE_11367, LEDGE_11368, LEDGE_11370, EDGE_11371, + TIGHTROPE_11364, GAP_11365, LEDGE_11366, LEDGE_11367, LEDGE_11369, LEDGE_11370, EDGE_11371, // Wilderness OBSTACLE_PIPE_23137, ROPESWING_23132, STEPPING_STONE_23556, LOG_BALANCE_23542, ROCKS_23640, // Seers @@ -91,144 +107,7 @@ class Obstacles ZIP_LINE_11645, ZIP_LINE_11646 ); - static final Set SHORTCUT_OBSTACLE_IDS = ImmutableSet.of( - // Grand Exchange - UNDERWALL_TUNNEL_16529, UNDERWALL_TUNNEL_16530, - // South Varrock - STEPPING_STONE_16533, FENCE_16518, ROCKS_16549, ROCKS_16550, - // Falador - WALL_17049, WALL_17050, CRUMBLING_WALL_24222, UNDERWALL_TUNNEL, UNDERWALL_TUNNEL_16528, CREVICE_16543, - // Draynor - UNDERWALL_TUNNEL_19032, UNDERWALL_TUNNEL_19036, - // South Lumbridge - BROKEN_RAFT, STEPPING_STONE_16513, - // Trollheim - ROCKS_3790, ROCKS_3791, ROCKS_3803, ROCKS_3804, ROCKS_16523, ROCKS_16524, ROCKS_3748, ROCKS_16545, ROCKS_16521, - ROCKS_16522, ROCKS_16464, - // North Camelot - LOG_BALANCE_16540, LOG_BALANCE_16541, LOG_BALANCE_16542, - // Rellekka - BROKEN_FENCE, - // Ardougne - LOG_BALANCE_16546, LOG_BALANCE_16547, LOG_BALANCE_16548, - // Yanille - CASTLE_WALL, HOLE_16520, WALL_17047, - // Observatory - NULL_31849, - // Gnome Stronghold - ROCKS_16534, ROCKS_16535, - // Karamja Volcano - STRONG_TREE_17074, - // Shilo Village - STEPPING_STONE_16466, - // Vine east of Shilo Village - NULL_26884, NULL_26886, - // Stepping stones east of Shilo Village - STEPPING_STONES, STEPPING_STONES_23646, STEPPING_STONES_23647, - // Middle of Karamja - A_WOODEN_LOG, - // Slayer Tower - SPIKEY_CHAIN, SPIKEY_CHAIN_16538, - // Fremennik Slayer Cave - STRANGE_FLOOR_16544, CREVICE_16539, STEPS_29993, - // Wilderness - STEPPING_STONE_14918, STEPPING_STONE_14917, ROCKY_HANDHOLDS_26404, ROCKY_HANDHOLDS_26405, ROCKY_HANDHOLDS_26406, - // Godwars - ROCKY_HANDHOLDS_26400, ROCKY_HANDHOLDS_26401, ROCKY_HANDHOLDS_26402, - // Seers' Village Coal Mine - LOG_BALANCE_23274, - // Arceuus Essence Mine - ROCKS_27984, ROCKS_27985, BOULDER_27990, ROCKS_27987, ROCKS_27988, - // Wintertodt - GAP_29326, - // Gnome Stronghold Slayer Underground - TUNNEL_30174, TUNNEL_30175, - // Taverley Underground - OBSTACLE_PIPE_16509, STRANGE_FLOOR, ROCKS, ROCKS_14106, LOOSE_RAILING_28849, - // Heroes Guild - CREVICE_9739, CREVICE_9740, - // Fossil Island - HOLE_31481, HOLE_31482, LADDER_30938, LADDER_30939, LADDER_30940, LADDER_30941, ROPE_ANCHOR, ROPE_ANCHOR_30917, - RUBBER_CAP_MUSHROOM, - ROCKS_31757, ROCKS_31758, ROCKS_31759, PILLAR_31809, - // West Brimhaven - ROPESWING_23568, ROPESWING_23569, - // Brimhaven Dungeon - VINE_26880, VINE_26882, PIPE_21728, STEPPING_STONE_19040, PIPE_21727, LOG_BALANCE_20882, LOG_BALANCE_20884, - STEPPING_STONE_21738, STEPPING_STONE_21739, TIGHTGAP, - // Lumbridge - STILE_12982, - // Edgeville Dungeon - MONKEYBARS_23566, OBSTACLE_PIPE_16511, - // Miscellania - STEPPING_STONE_11768, - // Kalphite - CREVICE_16465, - // Eagles' Peak - ROCKS_19849, - // Catherby - CROSSBOW_TREE_17062, ROCKS_17042, - // McGrubor's Woods - LOOSE_RAILING, - // Cairn Isle - ROCKS_2231, - // South Kourend - STEPPING_STONE_29728, STEPPING_STONE_29729, STEPPING_STONE_29730, - // Cosmic Temple - JUTTING_WALL_17002, - // Arandar - ROCKS_16514, ROCKS_16515, LOG_BALANCE_3933, - // South River Salve - STEPPING_STONE_13504, - DARK_TUNNEL_10047, - // Ectofuntus - WEATHERED_WALL, WEATHERED_WALL_16526, - // Mos Le'Harmless - STEPPING_STONE_19042, - // North River Salve - ROCKS_16998, ROCKS_16999, ORNATE_RAILING, ORNATE_RAILING_17000, - // West Zul-Andra - STEPPING_STONE_10663, - // Yanille Agility Dungeon - BALANCING_LEDGE_23548, OBSTACLE_PIPE_23140, MONKEYBARS_23567, PILE_OF_RUBBLE_23563, PILE_OF_RUBBLE_23564, - // High Level Wilderness Dungeon - CREVICE_19043, - // Revenant Caves - PILLAR_31561, - // Elf Camp Isafdar Tirranwn - LOG_BALANCE_3931, LOG_BALANCE_3930, LOG_BALANCE_3929, LOG_BALANCE_3932, DENSE_FOREST_3938, DENSE_FOREST_3939, - DENSE_FOREST_3998, DENSE_FOREST_3999, DENSE_FOREST, LEAVES, LEAVES_3924, LEAVES_3925, STICKS, TRIPWIRE, - // Gu'Tanoth bridge - GAP, GAP_2831, - // Lumbridge Swamp Caves - STEPPING_STONE_5948, STEPPING_STONE_5949, ROCKS_6673, - // Morytania Pirate Ship - ROCK_16115, - // Agility Pyramid Entrance - CLIMBING_ROCKS_11948, CLIMBING_ROCKS_11949, - // Lumber Yard - BROKEN_FENCE_2618, - // Ungael and Vorkath crater - NULL_25337, NULL_29868, NULL_29869, NULL_29870, ICE_CHUNKS_31822, NULL_31823, ICE_CHUNKS_31990, - // Underwater Area Fossil Island - TUNNEL_30959, HOLE_30966, OBSTACLE, OBSTACLE_30767, OBSTACLE_30964, OBSTACLE_30962, - // Tree Gnome Village - LOOSE_RAILING_2186, - // Weiss - LITTLE_BOULDER, ROCKSLIDE_33184, ROCKSLIDE_33185, NULL_33327, NULL_33328, LEDGE_33190, ROCKSLIDE_33191, FALLEN_TREE_33192, - // Al-Kharid - BROKEN_WALL_33344, BIG_WINDOW, - // Burgh de Rott - LOW_FENCE, - // Taverley - STILE, - // Asgarnian Ice Dungeon - STEPS, - // Fossil Island Wyvern Cave - STAIRS_31485, - // Mount Karuulm - ROCKS_34397, ROCKS_34396 - ); + static final Multimap SHORTCUT_OBSTACLE_IDS; static final Set TRAP_OBSTACLE_IDS = ImmutableSet.of( // Agility pyramid @@ -236,4 +115,17 @@ class Obstacles ); static final List TRAP_OBSTACLE_REGIONS = ImmutableList.of(12105, 13356); + + static + { + final ImmutableMultimap.Builder builder = ImmutableMultimap.builder(); + for (final AgilityShortcut item : AgilityShortcut.values()) + { + for (int obstacle : item.getObstacleIds()) + { + builder.put(obstacle, item); + } + } + SHORTCUT_OBSTACLE_IDS = builder.build(); + } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/BankTagsPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/BankTagsPlugin.java index 620cc586be..6a3a7f3416 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/BankTagsPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/BankTagsPlugin.java @@ -60,6 +60,7 @@ import net.runelite.client.callback.ClientThread; import net.runelite.client.config.ConfigManager; import net.runelite.client.eventbus.Subscribe; import net.runelite.client.game.ItemManager; +import net.runelite.client.game.SpriteManager; import net.runelite.client.game.chatbox.ChatboxPanelManager; import net.runelite.client.input.KeyListener; import net.runelite.client.input.KeyManager; @@ -125,6 +126,9 @@ public class BankTagsPlugin extends Plugin implements MouseWheelListener, KeyLis @Inject private KeyManager keyManager; + @Inject + private SpriteManager spriteManager; + private boolean shiftPressed = false; @Provides @@ -139,7 +143,7 @@ public class BankTagsPlugin extends Plugin implements MouseWheelListener, KeyLis keyManager.registerKeyListener(this); mouseManager.registerMouseWheelListener(this); clientThread.invokeLater(tabInterface::init); - client.getSpriteOverrides().putAll(TabSprites.toMap(client)); + spriteManager.addSpriteOverrides(TabSprites.values()); } @Override @@ -148,11 +152,7 @@ public class BankTagsPlugin extends Plugin implements MouseWheelListener, KeyLis keyManager.unregisterKeyListener(this); mouseManager.unregisterMouseWheelListener(this); clientThread.invokeLater(tabInterface::destroy); - - for (TabSprites value : TabSprites.values()) - { - client.getSpriteOverrides().remove(value.getSpriteId()); - } + spriteManager.removeSpriteOverrides(TabSprites.values()); shiftPressed = false; } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/TagManager.java b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/TagManager.java index c20974758d..c1d5ff8c11 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/TagManager.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/TagManager.java @@ -167,6 +167,20 @@ public class TagManager } } + public void renameTag(String oldTag, String newTag) + { + List items = getItemsForTag(Text.standardize(oldTag)); + items.forEach(id -> + { + Collection tags = getTags(id, id < 0); + + tags.remove(Text.standardize(oldTag)); + tags.add(Text.standardize(newTag)); + + setTags(id, tags, id < 0); + }); + } + private int getItemId(int itemId, boolean variation) { itemId = Math.abs(itemId); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/MenuIndexes.java b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/MenuIndexes.java index afa8f6e276..51e179b143 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/MenuIndexes.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/MenuIndexes.java @@ -39,5 +39,6 @@ class MenuIndexes static final int CHANGE_ICON = 3; static final int DELETE_TAB = 4; static final int EXPORT_TAB = 5; + static final int RENAME_TAB = 6; } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TabInterface.java b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TabInterface.java index 148b965043..2daaf0f5c6 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TabInterface.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TabInterface.java @@ -64,7 +64,6 @@ import net.runelite.api.SpriteID; import net.runelite.api.VarClientInt; import net.runelite.api.VarClientStr; import net.runelite.api.Varbits; -import net.runelite.api.widgets.WidgetType; import net.runelite.api.events.MenuEntryAdded; import net.runelite.api.events.MenuOptionClicked; import net.runelite.api.vars.InputType; @@ -73,15 +72,14 @@ import net.runelite.api.widgets.JavaScriptCallback; import net.runelite.api.widgets.Widget; import net.runelite.api.widgets.WidgetConfig; import net.runelite.api.widgets.WidgetInfo; +import net.runelite.api.widgets.WidgetSizeMode; +import net.runelite.api.widgets.WidgetType; import net.runelite.client.Notifier; import net.runelite.client.callback.ClientThread; -import net.runelite.client.config.ConfigManager; import net.runelite.client.game.ItemManager; import net.runelite.client.game.chatbox.ChatboxPanelManager; import net.runelite.client.plugins.banktags.BankTagsConfig; import net.runelite.client.plugins.banktags.BankTagsPlugin; -import static net.runelite.client.plugins.banktags.BankTagsPlugin.CONFIG_GROUP; -import static net.runelite.client.plugins.banktags.BankTagsPlugin.ICON_SEARCH; import static net.runelite.client.plugins.banktags.BankTagsPlugin.TAG_SEARCH; import static net.runelite.client.plugins.banktags.BankTagsPlugin.VAR_TAG_SUFFIX; import net.runelite.client.plugins.banktags.TagManager; @@ -102,6 +100,7 @@ public class TabInterface private static final String EXPORT_TAB = "Export tag tab"; private static final String IMPORT_TAB = "Import tag tab"; private static final String VIEW_TAB = "View tag tab"; + private static final String RENAME_TAB = "Rename tag tab"; private static final String CHANGE_ICON = "Change icon"; private static final String REMOVE_TAG = "Remove-tag"; private static final String TAG_GEAR = "Tag-equipment"; @@ -111,11 +110,12 @@ public class TabInterface private static final int BUTTON_HEIGHT = 20; private static final int MARGIN = 1; private static final int SCROLL_TICK = 500; + private static final int INCINERATOR_WIDTH = 48; + private static final int INCINERATOR_HEIGHT = 39; private final Client client; private final ClientThread clientThread; private final ItemManager itemManager; - private final ConfigManager configManager; private final TagManager tagManager; private final TabManager tabManager; private final ChatboxPanelManager chatboxPanelManager; @@ -148,7 +148,6 @@ public class TabInterface final Client client, final ClientThread clientThread, final ItemManager itemManager, - final ConfigManager configManager, final TagManager tagManager, final TabManager tabManager, final ChatboxPanelManager chatboxPanelManager, @@ -159,7 +158,6 @@ public class TabInterface this.client = client; this.clientThread = clientThread; this.itemManager = itemManager; - this.configManager = configManager; this.tagManager = tagManager; this.tabManager = tabManager; this.chatboxPanelManager = chatboxPanelManager; @@ -211,7 +209,7 @@ public class TabInterface if (config.rememberTab() && !Strings.isNullOrEmpty(config.tab())) { - openTag(TAG_SEARCH + config.tab()); + openTag(config.tab()); } } @@ -239,7 +237,7 @@ public class TabInterface tagManager.addTag(item, activeTab.getTag(), false); } - openTag(TAG_SEARCH + activeTab.getTag()); + openTag(activeTab.getTag()); } return; @@ -292,7 +290,7 @@ public class TabInterface final Iterator dataIter = Text.fromCSV(dataString).iterator(); final String name = dataIter.next(); final String icon = dataIter.next(); - configManager.setConfiguration(CONFIG_GROUP, ICON_SEARCH + name, icon); + tabManager.setIcon(name, icon); while (dataIter.hasNext()) { @@ -306,7 +304,7 @@ public class TabInterface if (activeTab != null && name.equals(activeTab.getTag())) { - openTag(TAG_SEARCH + activeTab.getTag()); + openTag(activeTab.getTag()); } notifier.notify("Tag tab " + name + " has been imported from your clipboard!"); @@ -336,7 +334,7 @@ public class TabInterface } else { - openTag(TAG_SEARCH + Text.removeTags(clicked.getName())); + openTag(Text.removeTags(clicked.getName())); } client.playSoundEffect(SoundEffectID.UI_BOOP); @@ -373,6 +371,10 @@ public class TabInterface Toolkit.getDefaultToolkit().getSystemClipboard().setContents(stringSelection, null); notifier.notify("Tag tab " + tagTab.getTag() + " has been copied to your clipboard!"); break; + case Tab.RENAME_TAB: + String renameTarget = Text.standardize(event.getOpbase()); + renameTab(renameTarget); + break; } } @@ -550,7 +552,7 @@ public class TabInterface int itemId = itemManager.canonicalize(item.getId()); iconToSet.setIconItemId(itemId); iconToSet.getIcon().setItemId(itemId); - configManager.setConfiguration(CONFIG_GROUP, ICON_SEARCH + iconToSet.getTag(), itemId + ""); + tabManager.setIcon(iconToSet.getTag(), itemId + ""); event.consume(); } @@ -597,7 +599,7 @@ public class TabInterface { if (activeTab != null && tags.contains(activeTab.getTag())) { - openTag(TAG_SEARCH + activeTab.getTag()); + openTag(activeTab.getTag()); } } @@ -683,6 +685,7 @@ public class TabInterface btn.setAction(2, CHANGE_ICON); btn.setAction(3, REMOVE_TAB); btn.setAction(4, EXPORT_TAB); + btn.setAction(5, RENAME_TAB); btn.setOnOpListener((JavaScriptCallback) this::handleTagTab); tagTab.setBackground(btn); } @@ -712,13 +715,66 @@ public class TabInterface } tabManager.remove(tag); - configManager.unsetConfiguration(CONFIG_GROUP, ICON_SEARCH + tag); tabManager.save(); updateBounds(); scrollTab(0); } + private void renameTab(String oldTag) + { + chatboxPanelManager.openTextInput("Enter new tag name for tag \"" + oldTag + "\":") + .onDone((newTag) -> clientThread.invoke(() -> + { + if (!Strings.isNullOrEmpty(newTag) && !newTag.equalsIgnoreCase(oldTag)) + { + if (tabManager.find(newTag) == null) + { + TagTab tagTab = tabManager.find(oldTag); + tagTab.setTag(newTag); + + final String coloredName = ColorUtil.wrapWithColorTag(newTag, HILIGHT_COLOR); + tagTab.getIcon().setName(coloredName); + tagTab.getBackground().setName(coloredName); + + tabManager.removeIcon(oldTag); + tabManager.setIcon(newTag, tagTab.getIconItemId() + ""); + + tabManager.save(); + tagManager.renameTag(oldTag, newTag); + + if (activeTab != null && activeTab.equals(tagTab)) + { + openTag(newTag); + } + } + else + { + chatboxPanelManager.openTextMenuInput("The specified bank tag already exists.") + .option("1. Merge into existing tag \"" + newTag + "\".", () -> + clientThread.invoke(() -> + { + tagManager.renameTag(oldTag, newTag); + final String activeTag = activeTab != null ? activeTab.getTag() : ""; + deleteTab(oldTag); + + if (activeTag.equals(oldTag)) + { + openTag(newTag); + } + }) + ) + .option("2. Choose a different name.", () -> + clientThread.invoke(() -> + renameTab(oldTag)) + ) + .build(); + } + } + })) + .build(); + } + private void scrollTick(int direction) { // This ensures that dragging on scroll buttons do not scrolls too fast @@ -805,17 +861,18 @@ public class TabInterface if (incinerator != null && !incinerator.isHidden()) { - // This is the required way to move incinerator, don't change it! - incinerator.setOriginalHeight(39); - incinerator.setOriginalWidth(48); - incinerator.setRelativeY(itemContainer.getHeight()); - incinerator.revalidate(); + incinerator.setOriginalHeight(INCINERATOR_HEIGHT); + incinerator.setOriginalWidth(INCINERATOR_WIDTH); + incinerator.setOriginalY(INCINERATOR_HEIGHT); Widget child = incinerator.getDynamicChildren()[0]; - child.setHeight(39); - child.setWidth(48); + child.setOriginalHeight(INCINERATOR_HEIGHT); + child.setOriginalWidth(INCINERATOR_WIDTH); + child.setWidthMode(WidgetSizeMode.ABSOLUTE); + child.setHeightMode(WidgetSizeMode.ABSOLUTE); child.setType(WidgetType.GRAPHIC); child.setSpriteId(TabSprites.INCINERATOR.getSpriteId()); + incinerator.revalidate(); bounds.setSize(TAB_WIDTH + MARGIN * 2, height - incinerator.getHeight()); } @@ -900,7 +957,6 @@ public class TabInterface private void updateWidget(Widget t, int y) { t.setOriginalY(y); - t.setRelativeY(y); t.setHidden(y < (bounds.y + BUTTON_HEIGHT + MARGIN) || y > (bounds.y + bounds.height - TAB_HEIGHT - MARGIN - BUTTON_HEIGHT)); t.revalidate(); } @@ -913,10 +969,10 @@ public class TabInterface return itemManager.getItemComposition(item.getId()); } - private void openTag(String tag) + private void openTag(final String tag) { - bankSearch.search(InputType.SEARCH, tag, true); - activateTab(tabManager.find(tag.substring(TAG_SEARCH.length()))); + bankSearch.search(InputType.SEARCH, TAG_SEARCH + tag, true); + activateTab(tabManager.find(tag)); // When tab is selected with search window open, the search window closes but the search button // stays highlighted, this solves that issue diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TabManager.java b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TabManager.java index 3f1430dd7c..e1995a5095 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TabManager.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TabManager.java @@ -115,6 +115,7 @@ class TabManager { tagTab.setHidden(true); tabs.remove(tagTab); + removeIcon(tag); } } @@ -124,6 +125,16 @@ class TabManager configManager.setConfiguration(CONFIG_GROUP, TAG_TABS_CONFIG, tags); } + void removeIcon(final String tag) + { + configManager.unsetConfiguration(CONFIG_GROUP, ICON_SEARCH + Text.standardize(tag)); + } + + void setIcon(final String tag, final String icon) + { + configManager.setConfiguration(CONFIG_GROUP, ICON_SEARCH + Text.standardize(tag), icon); + } + int size() { return tabs.size(); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TabSprites.java b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TabSprites.java index e7706679b0..20f9d0dfb6 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TabSprites.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TabSprites.java @@ -25,17 +25,12 @@ */ package net.runelite.client.plugins.banktags.tabs; -import java.awt.image.BufferedImage; -import java.util.HashMap; -import java.util.Map; import lombok.Getter; -import lombok.extern.slf4j.Slf4j; -import net.runelite.api.Client; -import net.runelite.api.SpritePixels; -import net.runelite.client.util.ImageUtil; +import lombok.RequiredArgsConstructor; +import net.runelite.client.game.SpriteOverride; -@Slf4j -public enum TabSprites +@RequiredArgsConstructor +public enum TabSprites implements SpriteOverride { INCINERATOR(-200, "incinerator.png"), TAB_BACKGROUND(-201, "tag-tab.png"), @@ -46,23 +41,7 @@ public enum TabSprites @Getter private final int spriteId; - private final BufferedImage image; - TabSprites(final int spriteId, final String imageName) - { - this.spriteId = spriteId; - this.image = ImageUtil.getResourceStreamFromClass(this.getClass(), imageName); - } - - public static Map toMap(Client client) - { - final Map map = new HashMap<>(); - - for (TabSprites value : values()) - { - map.put(value.spriteId, ImageUtil.getImageSpritePixels(value.image, client)); - } - - return map; - } + @Getter + private final String fileName; } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TagTab.java b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TagTab.java index d3d417f465..004e5f45ed 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TagTab.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/tabs/TagTab.java @@ -33,7 +33,7 @@ import net.runelite.api.widgets.Widget; @EqualsAndHashCode(of = "tag") class TagTab { - private final String tag; + private String tag; private int iconItemId; private Widget background; private Widget icon; diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cannon/CannonCounter.java b/runelite-client/src/main/java/net/runelite/client/plugins/cannon/CannonCounter.java index 2db9d6f3e0..bd13e194be 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/cannon/CannonCounter.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cannon/CannonCounter.java @@ -26,15 +26,15 @@ package net.runelite.client.plugins.cannon; import java.awt.Color; import java.awt.image.BufferedImage; -import net.runelite.client.ui.overlay.infobox.Counter; +import net.runelite.client.ui.overlay.infobox.InfoBox; -public class CannonCounter extends Counter +public class CannonCounter extends InfoBox { private final CannonPlugin plugin; - public CannonCounter(BufferedImage img, CannonPlugin plugin) + CannonCounter(BufferedImage img, CannonPlugin plugin) { - super(img, plugin, String.valueOf(plugin.getCballsLeft())); + super(img, plugin); this.plugin = plugin; } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cerberus/CerberusPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/cerberus/CerberusPlugin.java index 322e8c38cd..47f1adbec4 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/cerberus/CerberusPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cerberus/CerberusPlugin.java @@ -75,7 +75,8 @@ public class CerberusPlugin extends Plugin @Subscribe public void onGameStateChanged(GameStateChanged event) { - if (event.getGameState() == GameState.LOADING) + GameState gameState = event.getGameState(); + if (gameState == GameState.LOGIN_SCREEN || gameState == GameState.HOPPING || gameState == GameState.CONNECTION_LOST) { ghosts.clear(); } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/chatcommands/ChatCommandsPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/chatcommands/ChatCommandsPlugin.java index 42a3a9baba..7295577e34 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/chatcommands/ChatCommandsPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/chatcommands/ChatCommandsPlugin.java @@ -299,7 +299,7 @@ public class ChatCommandsPlugin extends Plugin Widget boss = bossChildren[i]; Widget kill = killsChildren[i]; - String bossName = boss.getText(); + String bossName = boss.getText().replace(":", ""); int kc = Integer.parseInt(kill.getText().replace(",", "")); if (kc != getKc(bossName)) { @@ -1089,6 +1089,7 @@ public class ChatCommandsPlugin extends Plugin case "barrows": return "Barrows Chests"; + // cox case "cox": case "xeric": case "chambers": @@ -1096,6 +1097,15 @@ public class ChatCommandsPlugin extends Plugin case "raids": return "Chambers of Xeric"; + // cox cm + case "cox cm": + case "xeric cm": + case "chambers cm": + case "olm cm": + case "raids cm": + return "Chambers of Xeric Challenge Mode"; + + // tob case "tob": case "theatre": case "verzik": diff --git a/http-service/src/main/java/net/runelite/http/service/ws/SessionManager.java b/runelite-client/src/main/java/net/runelite/client/plugins/chathistory/ChatHistoryConfig.java similarity index 61% rename from http-service/src/main/java/net/runelite/http/service/ws/SessionManager.java rename to runelite-client/src/main/java/net/runelite/client/plugins/chathistory/ChatHistoryConfig.java index 815ab911e8..e30629c742 100644 --- a/http-service/src/main/java/net/runelite/http/service/ws/SessionManager.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/chathistory/ChatHistoryConfig.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, Adam + * Copyright (c) 2018, TheStonedTurtle * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -22,46 +22,34 @@ * (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.http.service.ws; +package net.runelite.client.plugins.chathistory; -import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; +import net.runelite.client.config.Config; +import net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigItem; -public class SessionManager +@ConfigGroup("chathistory") +public interface ChatHistoryConfig extends Config { - private static final ConcurrentMap sessions = new ConcurrentHashMap<>(); - - public static void changeSessionUID(WSService service, UUID uuid) + @ConfigItem( + keyName = "retainChatHistory", + name = "Retain Chat History", + description = "Retains chat history when logging in/out or world hopping", + position = 0 + ) + default boolean retainChatHistory() { - synchronized (service) - { - remove(service); - service.setUuid(uuid); - sessions.put(uuid, service); - } + return true; } - static void remove(WSService service) + @ConfigItem( + keyName = "pmTargetCycling", + name = "PM Target Cycling", + description = "Pressing Tab while sending a PM will cycle the target username based on PM history", + position = 1 + ) + default boolean pmTargetCycling() { - synchronized (service) - { - UUID current = service.getUuid(); - if (current != null) - { - sessions.remove(current); - service.setUuid(null); - } - } - } - - public static WSService findSession(UUID uuid) - { - return sessions.get(uuid); - } - - public static int getCount() - { - return sessions.size(); + return true; } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/chathistory/ChatHistoryPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/chathistory/ChatHistoryPlugin.java index 6ac89bf605..43919214ea 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/chathistory/ChatHistoryPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/chathistory/ChatHistoryPlugin.java @@ -25,47 +25,74 @@ package net.runelite.client.plugins.chathistory; import com.google.common.collect.EvictingQueue; -import com.google.common.collect.Sets; +import com.google.inject.Provides; +import java.awt.event.KeyEvent; +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.Iterator; import java.util.Queue; -import java.util.Set; import javax.inject.Inject; import net.runelite.api.ChatMessageType; +import net.runelite.api.Client; +import net.runelite.api.ScriptID; +import net.runelite.api.VarClientInt; +import net.runelite.api.VarClientStr; import net.runelite.api.events.ChatMessage; import net.runelite.api.events.MenuOptionClicked; +import net.runelite.api.vars.InputType; +import net.runelite.client.callback.ClientThread; import net.runelite.client.chat.ChatMessageManager; import net.runelite.client.chat.QueuedMessage; +import net.runelite.client.config.ConfigManager; import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.input.KeyListener; +import net.runelite.client.input.KeyManager; import net.runelite.client.plugins.Plugin; import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.util.Text; @PluginDescriptor( name = "Chat History", - description = "Retain your chat history when logging in/out or world hopping" + description = "Retain your chat history when logging in/out or world hopping", + tags = {"chat", "history", "retain", "cycle", "pm"} ) -public class ChatHistoryPlugin extends Plugin +public class ChatHistoryPlugin extends Plugin implements KeyListener { private static final String WELCOME_MESSAGE = "Welcome to Old School RuneScape."; private static final String CLEAR_HISTORY = "Clear history"; private static final String CLEAR_PRIVATE = "Private:"; - private static final Set ALLOWED_HISTORY = Sets.newHashSet( - ChatMessageType.PUBLIC, - ChatMessageType.PUBLIC_MOD, - ChatMessageType.CLANCHAT, - ChatMessageType.PRIVATE_MESSAGE_RECEIVED, - ChatMessageType.PRIVATE_MESSAGE_SENT, - ChatMessageType.PRIVATE_MESSAGE_RECEIVED_MOD, - ChatMessageType.GAME - ); + private static final int CYCLE_HOTKEY = KeyEvent.VK_TAB; private Queue messageQueue; + private Deque friends; + + @Inject + private Client client; + + @Inject + private ClientThread clientThread; + + @Inject + private ChatHistoryConfig config; + + @Inject + private KeyManager keyManager; @Inject private ChatMessageManager chatMessageManager; + + @Provides + ChatHistoryConfig getConfig(ConfigManager configManager) + { + return configManager.getConfig(ChatHistoryConfig.class); + } @Override protected void startUp() { messageQueue = EvictingQueue.create(100); + friends = new ArrayDeque<>(5); + keyManager.registerKeyListener(this); } @Override @@ -73,6 +100,9 @@ public class ChatHistoryPlugin extends Plugin { messageQueue.clear(); messageQueue = null; + friends.clear(); + friends = null; + keyManager.unregisterKeyListener(this); } @Subscribe @@ -82,6 +112,11 @@ public class ChatHistoryPlugin extends Plugin // of information that chat history was reset if (chatMessage.getMessage().equals(WELCOME_MESSAGE)) { + if (!config.retainChatHistory()) + { + return; + } + QueuedMessage queuedMessage; while ((queuedMessage = messageQueue.poll()) != null) @@ -92,21 +127,33 @@ public class ChatHistoryPlugin extends Plugin return; } - if (ALLOWED_HISTORY.contains(chatMessage.getType())) + switch (chatMessage.getType()) { - final QueuedMessage queuedMessage = QueuedMessage.builder() - .type(chatMessage.getType()) - .name(chatMessage.getName()) - .sender(chatMessage.getSender()) - .value(nbsp(chatMessage.getMessage())) - .runeLiteFormattedMessage(nbsp(chatMessage.getMessageNode().getRuneLiteFormatMessage())) - .timestamp(chatMessage.getTimestamp()) - .build(); + case PRIVATE_MESSAGE_SENT: + case PRIVATE_MESSAGE_RECEIVED: + case PRIVATE_MESSAGE_RECEIVED_MOD: + final String name = Text.removeTags(chatMessage.getName()); + // Remove to ensure uniqueness & its place in history + friends.remove(name); + friends.add(name); + // intentional fall-through + case PUBLIC: + case PUBLIC_MOD: + case CLANCHAT: + case GAME: + final QueuedMessage queuedMessage = QueuedMessage.builder() + .type(chatMessage.getType()) + .name(chatMessage.getName()) + .sender(chatMessage.getSender()) + .value(nbsp(chatMessage.getMessage())) + .runeLiteFormattedMessage(nbsp(chatMessage.getMessageNode().getRuneLiteFormatMessage())) + .timestamp(chatMessage.getTimestamp()) + .build(); - if (!messageQueue.contains(queuedMessage)) - { - messageQueue.offer(queuedMessage); - } + if (!messageQueue.contains(queuedMessage)) + { + messageQueue.offer(queuedMessage); + } } } @@ -143,4 +190,64 @@ public class ChatHistoryPlugin extends Plugin return null; } + + @Override + public void keyPressed(KeyEvent e) + { + if (e.getKeyCode() != CYCLE_HOTKEY || !config.pmTargetCycling()) + { + return; + } + + if (client.getVar(VarClientInt.INPUT_TYPE) != InputType.PRIVATE_MESSAGE.getType()) + { + return; + } + + clientThread.invoke(() -> + { + final String target = findPreviousFriend(); + if (target == null) + { + return; + } + + final String currentMessage = client.getVar(VarClientStr.INPUT_TEXT); + + client.runScript(ScriptID.OPEN_PRIVATE_MESSAGE_INTERFACE, target); + + client.setVar(VarClientStr.INPUT_TEXT, currentMessage); + client.runScript(ScriptID.CHAT_TEXT_INPUT_REBUILD, ""); + }); + } + + @Override + public void keyTyped(KeyEvent e) + { + } + + @Override + public void keyReleased(KeyEvent e) + { + } + + private String findPreviousFriend() + { + final String currentTarget = client.getVar(VarClientStr.PRIVATE_MESSAGE_TARGET); + if (currentTarget == null || friends.isEmpty()) + { + return null; + } + + for (Iterator it = friends.descendingIterator(); it.hasNext(); ) + { + String friend = it.next(); + if (friend.equals(currentTarget)) + { + return it.hasNext() ? it.next() : friends.getLast(); + } + } + + return friends.getLast(); + } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/EmoteClue.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/EmoteClue.java index 30a33d7e50..d3123ce2da 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/EmoteClue.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/EmoteClue.java @@ -133,8 +133,8 @@ public class EmoteClue extends ClueScroll implements TextClueScroll, LocationClu new EmoteClue("Panic by the pilot on White Wolf Mountain. Beware of double agents! Equip mithril platelegs, a ring of life and a rune axe.", GNOME_GLIDER_ON_WHITE_WOLF_MOUNTAIN, new WorldPoint(2847, 3499, 0), PANIC, item(MITHRIL_PLATELEGS), item(RING_OF_LIFE), item(RUNE_AXE)), new EmoteClue("Panic by the big egg where no one dare goes and the ground is burnt. Beware of double agents! Equip a dragon med helm, a TokTz-Ket-Xil, a brine sabre, rune platebody and an uncharged amulet of glory.", SOUTHEAST_CORNER_OF_LAVA_DRAGON_ISLE, new WorldPoint(3227, 3831, 0), PANIC, item(DRAGON_MED_HELM), item(TOKTZKETXIL), item(BRINE_SABRE), item(RUNE_PLATEBODY), item(AMULET_OF_GLORY)), new EmoteClue("Panic at the area flowers meet snow. Equip Blue D'hide vambs, a dragon spear and a rune plateskirt.", HALFWAY_DOWN_TROLLWEISS_MOUNTAIN, new WorldPoint(2776, 3781, 0), PANIC, item(BLUE_DHIDE_VAMB), item(DRAGON_SPEAR), item(RUNE_PLATESKIRT), item(SLED_4084)), - new EmoteClue("Do a push up at the bank of the Warrior's guild. Beware of double agents! Equip a dragon battleaxe, a dragon defender and a slayer helm of any kind.", WARRIORS_GUILD_BANK_29047, new WorldPoint(2843, 3543, 0), PUSH_UP, item(DRAGON_BATTLEAXE), item(DRAGON_DEFENDER), any("Any slayer helmet", item(SLAYER_HELMET), item(BLACK_SLAYER_HELMET), item(GREEN_SLAYER_HELMET), item(PURPLE_SLAYER_HELMET), item(RED_SLAYER_HELMET), item(TURQUOISE_SLAYER_HELMET), item(SLAYER_HELMET_I), item(BLACK_SLAYER_HELMET_I), item(GREEN_SLAYER_HELMET_I), item(PURPLE_SLAYER_HELMET_I), item(RED_SLAYER_HELMET_I), item(TURQUOISE_SLAYER_HELMET_I))), - new EmoteClue("Blow a raspberry at the bank of the Warrior's guild. Beware of double agents! Equip a dragon battleaxe, a dragon defender and a slayer helm of any kind.", WARRIORS_GUILD_BANK_29047, new WorldPoint(2843, 3543, 0), RASPBERRY, item(DRAGON_BATTLEAXE), item(DRAGON_DEFENDER), any("Any slayer helmet", item(SLAYER_HELMET), item(BLACK_SLAYER_HELMET), item(GREEN_SLAYER_HELMET), item(PURPLE_SLAYER_HELMET), item(RED_SLAYER_HELMET), item(TURQUOISE_SLAYER_HELMET), item(SLAYER_HELMET_I), item(BLACK_SLAYER_HELMET_I), item(GREEN_SLAYER_HELMET_I), item(PURPLE_SLAYER_HELMET_I), item(RED_SLAYER_HELMET_I), item(TURQUOISE_SLAYER_HELMET_I))), + new EmoteClue("Do a push up at the bank of the Warrior's guild. Beware of double agents! Equip a dragon battleaxe, a dragon defender and a slayer helm of any kind.", WARRIORS_GUILD_BANK_29047, new WorldPoint(2843, 3543, 0), PUSH_UP, item(DRAGON_BATTLEAXE), item(DRAGON_DEFENDER), any("Any slayer helmet", item(SLAYER_HELMET), item(BLACK_SLAYER_HELMET), item(GREEN_SLAYER_HELMET), item(PURPLE_SLAYER_HELMET), item(RED_SLAYER_HELMET), item(TURQUOISE_SLAYER_HELMET), item(SLAYER_HELMET_I), item(BLACK_SLAYER_HELMET_I), item(GREEN_SLAYER_HELMET_I), item(PURPLE_SLAYER_HELMET_I), item(RED_SLAYER_HELMET_I), item(TURQUOISE_SLAYER_HELMET_I), item(HYDRA_SLAYER_HELMET), item(HYDRA_SLAYER_HELMET_I))), + new EmoteClue("Blow a raspberry at the bank of the Warrior's guild. Beware of double agents! Equip a dragon battleaxe, a dragon defender and a slayer helm of any kind.", WARRIORS_GUILD_BANK_29047, new WorldPoint(2843, 3543, 0), RASPBERRY, item(DRAGON_BATTLEAXE), item(DRAGON_DEFENDER), any("Any slayer helmet", item(SLAYER_HELMET), item(BLACK_SLAYER_HELMET), item(GREEN_SLAYER_HELMET), item(PURPLE_SLAYER_HELMET), item(RED_SLAYER_HELMET), item(TURQUOISE_SLAYER_HELMET), item(SLAYER_HELMET_I), item(BLACK_SLAYER_HELMET_I), item(GREEN_SLAYER_HELMET_I), item(PURPLE_SLAYER_HELMET_I), item(RED_SLAYER_HELMET_I), item(TURQUOISE_SLAYER_HELMET_I), item(HYDRA_SLAYER_HELMET), item(HYDRA_SLAYER_HELMET_I))), new EmoteClue("Blow a raspberry at the monkey cage in Ardougne Zoo. Equip a studded leather body, bronze platelegs and a normal staff with no orb.", NEAR_THE_PARROTS_IN_ARDOUGNE_ZOO, new WorldPoint(2607, 3282, 0), RASPBERRY, item(STUDDED_BODY), item(BRONZE_PLATELEGS), item(STAFF)), new EmoteClue("Blow raspberries outside the entrance to Keep Le Faye. Equip a coif, an iron platebody and leather gloves.", OUTSIDE_KEEP_LE_FAYE, new WorldPoint(2757, 3401, 0), RASPBERRY, item(COIF), item(IRON_PLATEBODY), item(LEATHER_GLOVES)), new EmoteClue("Blow a raspberry in the Fishing Guild bank. Beware of double agents! Equip an elemental shield, blue dragonhide chaps and a rune warhammer.", FISHING_GUILD_BANK, new WorldPoint(2588, 3419, 0), RASPBERRY, item(ELEMENTAL_SHIELD), item(BLUE_DHIDE_CHAPS), item(RUNE_WARHAMMER)), diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cooking/CookingOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/cooking/CookingOverlay.java index af0a5a8b62..7e25f3ad31 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/cooking/CookingOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cooking/CookingOverlay.java @@ -78,7 +78,6 @@ class CookingOverlay extends Overlay return null; } - panelComponent.setPreferredSize(new Dimension(145, 0)); panelComponent.getChildren().clear(); if (isCooking() || Duration.between(session.getLastCookingAction(), Instant.now()).getSeconds() < COOK_TIMEOUT) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsOverlay.java index 0b9be07b7e..fd96de011c 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsOverlay.java @@ -347,6 +347,12 @@ class DevToolsOverlay extends Overlay { graphics.drawPolygon(p); } + + p = decorObject.getConvexHull2(); + if (p != null) + { + graphics.drawPolygon(p); + } } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/WidgetInfoTableModel.java b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/WidgetInfoTableModel.java index 79bb99bc5a..acd8851c0b 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/WidgetInfoTableModel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/WidgetInfoTableModel.java @@ -183,6 +183,9 @@ public class WidgetInfoTableModel extends AbstractTableModel out.add(new WidgetField<>("ScrollHeight", Widget::getScrollHeight, Widget::setScrollHeight, Integer.class)); out.add(new WidgetField<>("DragDeadZone", Widget::getDragDeadZone, Widget::setDragDeadZone, Integer.class)); out.add(new WidgetField<>("DragDeadTime", Widget::getDragDeadTime, Widget::setDragDeadTime, Integer.class)); + out.add(new WidgetField<>("NoClickThrough", Widget::getNoClickThrough, Widget::setNoClickThrough, Boolean.class)); + out.add(new WidgetField<>("NoScrollThrough", Widget::getNoScrollThrough, Widget::setNoScrollThrough, Boolean.class)); + out.add(new WidgetField<>("TargetVerb", Widget::getTargetVerb, Widget::setTargetVerb, String.class)); return out; } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/discord/DiscordPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/discord/DiscordPlugin.java index 06e0369535..9598b7eff5 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/discord/DiscordPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/discord/DiscordPlugin.java @@ -130,6 +130,7 @@ public class DiscordPlugin extends Plugin clientToolbar.addNavigation(discordButton); checkForGameStateUpdate(); + checkForAreaUpdate(); if (discordService.getCurrentUser() != null) { diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/discord/DiscordState.java b/runelite-client/src/main/java/net/runelite/client/plugins/discord/DiscordState.java index 625aed1f08..0b57424f85 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/discord/DiscordState.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/discord/DiscordState.java @@ -34,6 +34,7 @@ import java.util.Optional; import java.util.UUID; import javax.inject.Inject; import lombok.Data; +import net.runelite.client.RuneLiteProperties; import net.runelite.client.discord.DiscordPresence; import net.runelite.client.discord.DiscordService; import net.runelite.client.ws.PartyService; @@ -57,14 +58,16 @@ class DiscordState private final DiscordService discordService; private final DiscordConfig config; private PartyService party; + private final RuneLiteProperties properties; private DiscordPresence lastPresence; @Inject - private DiscordState(final DiscordService discordService, final DiscordConfig config, final PartyService party) + private DiscordState(final DiscordService discordService, final DiscordConfig config, final PartyService party, final RuneLiteProperties properties) { this.discordService = discordService; this.config = config; this.party = party; + this.properties = properties; } /** @@ -90,6 +93,7 @@ class DiscordState final DiscordPresence.DiscordPresenceBuilder presenceBuilder = DiscordPresence.builder() .state(lastPresence.getState()) .details(lastPresence.getDetails()) + .largeImageText(lastPresence.getLargeImageText()) .startTimestamp(lastPresence.getStartTimestamp()) .smallImageKey(lastPresence.getSmallImageKey()) .partyMax(lastPresence.getPartyMax()) @@ -168,11 +172,15 @@ class DiscordState } } + // Replace snapshot with + to make tooltip shorter (so it will span only 1 line) + final String versionShortHand = properties.getVersion().replace("-SNAPSHOT", "+"); + final DiscordPresence.DiscordPresenceBuilder presenceBuilder = DiscordPresence.builder() .state(MoreObjects.firstNonNull(state, "")) .details(MoreObjects.firstNonNull(details, "")) + .largeImageText(properties.getTitle() + " v" + versionShortHand) .startTimestamp(event.getStart()) - .smallImageKey(MoreObjects.firstNonNull(imageKey, "default")) + .smallImageKey(imageKey) .partyMax(PARTY_MAX) .partySize(party.getMembers().size()); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/discord/DiscordUserInfo.java b/runelite-client/src/main/java/net/runelite/client/plugins/discord/DiscordUserInfo.java index b358a7d01e..360d58652e 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/discord/DiscordUserInfo.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/discord/DiscordUserInfo.java @@ -24,10 +24,12 @@ */ package net.runelite.client.plugins.discord; +import lombok.EqualsAndHashCode; import lombok.Value; import net.runelite.http.api.ws.messages.party.PartyMemberMessage; @Value +@EqualsAndHashCode(callSuper = true) class DiscordUserInfo extends PartyMemberMessage { private final String userId; diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/fishing/FishingPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/fishing/FishingPlugin.java index 07977334e4..8da48d7cdc 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/fishing/FishingPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/fishing/FishingPlugin.java @@ -147,7 +147,8 @@ public class FishingPlugin extends Plugin @Subscribe public void onGameStateChanged(GameStateChanged gameStateChanged) { - if (gameStateChanged.getGameState() == GameState.LOADING) + GameState gameState = gameStateChanged.getGameState(); + if (gameState == GameState.CONNECTION_LOST || gameState == GameState.LOGIN_SCREEN || gameState == GameState.HOPPING) { fishingSpots.clear(); minnowSpots.clear(); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GpuPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GpuPlugin.java index a7871f95da..93d8a522d9 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GpuPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GpuPlugin.java @@ -1151,8 +1151,6 @@ public class GpuPlugin extends Plugin implements DrawCallbacks gl.glTexSubImage2D(gl.GL_TEXTURE_2D, 0, 0, 0, width, height, gl.GL_BGRA, gl.GL_UNSIGNED_INT_8_8_8_8_REV, interfaceBuffer); } - gl.glBindTexture(gl.GL_TEXTURE_2D, interfaceTexture); - if (client.isStretchedEnabled()) { Dimension dim = client.getStretchedDimensions(); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangePlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangePlugin.java index cd4f3f050e..5a59ede074 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangePlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/GrandExchangePlugin.java @@ -1,5 +1,5 @@ /* - * + * Copyright (c) 2019, Adam * Copyright (c) 2017, Robbie * Copyright (c) 2018, SomeoneWithAnInternetConnection * All rights reserved. @@ -46,6 +46,7 @@ import net.runelite.api.ChatMessageType; import net.runelite.api.Client; import net.runelite.api.GameState; import net.runelite.api.GrandExchangeOffer; +import net.runelite.api.GrandExchangeOfferState; import net.runelite.api.ItemComposition; import net.runelite.api.MenuAction; import net.runelite.api.MenuEntry; @@ -56,11 +57,15 @@ import net.runelite.api.events.GameStateChanged; import net.runelite.api.events.GameTick; import net.runelite.api.events.GrandExchangeOfferChanged; import net.runelite.api.events.MenuEntryAdded; +import net.runelite.api.events.SessionClose; +import net.runelite.api.events.SessionOpen; import net.runelite.api.events.WidgetLoaded; import net.runelite.api.widgets.Widget; import net.runelite.api.widgets.WidgetID; import net.runelite.api.widgets.WidgetInfo; import net.runelite.client.Notifier; +import net.runelite.client.account.AccountSession; +import net.runelite.client.account.SessionManager; import net.runelite.client.config.ConfigManager; import net.runelite.client.eventbus.Subscribe; import net.runelite.client.game.ItemManager; @@ -73,8 +78,10 @@ import net.runelite.client.ui.NavigationButton; import net.runelite.client.util.ImageUtil; import net.runelite.client.util.StackFormatter; import net.runelite.client.util.Text; -import net.runelite.http.api.osbuddy.GrandExchangeClient; -import net.runelite.http.api.osbuddy.GrandExchangeResult; +import net.runelite.http.api.ge.GrandExchangeClient; +import net.runelite.http.api.ge.GrandExchangeTrade; +import net.runelite.http.api.osbuddy.OSBGrandExchangeClient; +import net.runelite.http.api.osbuddy.OSBGrandExchangeResult; @PluginDescriptor( name = "Grand Exchange", @@ -86,7 +93,7 @@ public class GrandExchangePlugin extends Plugin { private static final int OFFER_CONTAINER_ITEM = 21; private static final int OFFER_DEFAULT_ITEM_ID = 6512; - private static final GrandExchangeClient CLIENT = new GrandExchangeClient(); + private static final OSBGrandExchangeClient CLIENT = new OSBGrandExchangeClient(); private static final String OSB_GE_TEXT = "
OSBuddy Actively traded price: "; private static final String BUY_LIMIT_GE_TEXT = "
Buy limit: "; @@ -134,10 +141,38 @@ public class GrandExchangePlugin extends Plugin @Inject private ScheduledExecutorService executorService; + @Inject + private SessionManager sessionManager; + + @Inject + private ConfigManager configManager; + private Widget grandExchangeText; private Widget grandExchangeItem; private Map itemGELimits; + private GrandExchangeClient grandExchangeClient; + + private SavedOffer getOffer(int slot) + { + String offer = configManager.getConfiguration("geoffer." + client.getUsername().toLowerCase(), Integer.toString(slot)); + if (offer == null) + { + return null; + } + return GSON.fromJson(offer, SavedOffer.class); + } + + private void setOffer(int slot, SavedOffer offer) + { + configManager.setConfiguration("geoffer." + client.getUsername().toLowerCase(), Integer.toString(slot), GSON.toJson(offer)); + } + + private void deleteOffer(int slot) + { + configManager.unsetConfiguration("geoffer." + client.getUsername().toLowerCase(), Integer.toString(slot)); + } + @Provides GrandExchangeConfig provideConfig(ConfigManager configManager) { @@ -167,6 +202,12 @@ public class GrandExchangePlugin extends Plugin mouseManager.registerMouseListener(inputListener); keyManager.registerKeyListener(inputListener); } + + AccountSession accountSession = sessionManager.getAccountSession(); + if (accountSession != null) + { + grandExchangeClient = new GrandExchangeClient(accountSession.getUuid()); + } } @Override @@ -178,6 +219,27 @@ public class GrandExchangePlugin extends Plugin grandExchangeText = null; grandExchangeItem = null; itemGELimits = null; + grandExchangeClient = null; + } + + @Subscribe + public void onSessionOpen(SessionOpen sessionOpen) + { + AccountSession accountSession = sessionManager.getAccountSession(); + if (accountSession.getUuid() != null) + { + grandExchangeClient = new GrandExchangeClient(accountSession.getUuid()); + } + else + { + grandExchangeClient = null; + } + } + + @Subscribe + public void onSessionClose(SessionClose sessionClose) + { + grandExchangeClient = null; } @Subscribe @@ -204,11 +266,79 @@ public class GrandExchangePlugin extends Plugin @Subscribe public void onGrandExchangeOfferChanged(GrandExchangeOfferChanged offerEvent) { - GrandExchangeOffer offer = offerEvent.getOffer(); + final int slot = offerEvent.getSlot(); + final GrandExchangeOffer offer = offerEvent.getOffer(); + ItemComposition offerItem = itemManager.getItemComposition(offer.getItemId()); boolean shouldStack = offerItem.isStackable() || offer.getTotalQuantity() > 1; BufferedImage itemImage = itemManager.getImage(offer.getItemId(), offer.getTotalQuantity(), shouldStack); - SwingUtilities.invokeLater(() -> panel.getOffersPanel().updateOffer(offerItem, itemImage, offerEvent.getOffer(), offerEvent.getSlot())); + SwingUtilities.invokeLater(() -> panel.getOffersPanel().updateOffer(offerItem, itemImage, offer, slot)); + + submitTrades(slot, offer); + + updateConfig(slot, offer); + } + + private void submitTrades(int slot, GrandExchangeOffer offer) + { + if (grandExchangeClient == null) + { + return; + } + + // Only interested in offers which are fully bought/sold + if (offer.getState() != GrandExchangeOfferState.BOUGHT && offer.getState() != GrandExchangeOfferState.SOLD) + { + return; + } + + SavedOffer savedOffer = getOffer(slot); + if (!shouldUpdate(savedOffer, offer)) + { + return; + } + + // getPrice() is the price of the offer, not necessarily what the item bought at + int priceEach = offer.getSpent() / offer.getTotalQuantity(); + + GrandExchangeTrade grandExchangeTrade = new GrandExchangeTrade(); + grandExchangeTrade.setBuy(offer.getState() == GrandExchangeOfferState.BOUGHT); + grandExchangeTrade.setItemId(offer.getItemId()); + grandExchangeTrade.setQuantity(offer.getTotalQuantity()); + grandExchangeTrade.setPrice(priceEach); + + log.debug("Submitting trade: {}", grandExchangeTrade); + grandExchangeClient.submit(grandExchangeTrade); + } + + private void updateConfig(int slot, GrandExchangeOffer offer) + { + if (offer.getState() == GrandExchangeOfferState.EMPTY) + { + deleteOffer(slot); + } + else + { + SavedOffer savedOffer = new SavedOffer(); + savedOffer.setItemId(offer.getItemId()); + savedOffer.setQuantitySold(offer.getQuantitySold()); + savedOffer.setTotalQuantity(offer.getTotalQuantity()); + savedOffer.setPrice(offer.getPrice()); + savedOffer.setSpent(offer.getSpent()); + savedOffer.setState(offer.getState()); + setOffer(slot, savedOffer); + } + } + + private boolean shouldUpdate(SavedOffer savedOffer, GrandExchangeOffer grandExchangeOffer) + { + if (savedOffer == null) + { + return false; + } + + // Only update offer if state has changed + return savedOffer.getState() != grandExchangeOffer.getState(); } @Subscribe @@ -346,7 +476,7 @@ public class GrandExchangePlugin extends Plugin try { - final GrandExchangeResult result = CLIENT.lookupItem(itemId); + final OSBGrandExchangeResult result = CLIENT.lookupItem(itemId); final String text = geText.getText() + OSB_GE_TEXT + StackFormatter.formatNumber(result.getOverall_average()); geText.setText(text); } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/SavedOffer.java b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/SavedOffer.java new file mode 100644 index 0000000000..58a4055fed --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grandexchange/SavedOffer.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2019, Adam + * 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.grandexchange; + +import lombok.Data; +import net.runelite.api.GrandExchangeOfferState; + +@Data +class SavedOffer +{ + private int itemId; + private int quantitySold; + private int totalQuantity; + private int price; + private int spent; + private GrandExchangeOfferState state; +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/groundmarkers/GroundMarkerPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/groundmarkers/GroundMarkerPlugin.java index 12ceda382a..2cb7ebf1c2 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/groundmarkers/GroundMarkerPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/groundmarkers/GroundMarkerPlugin.java @@ -34,13 +34,13 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.stream.Collectors; import javax.inject.Inject; import lombok.AccessLevel; import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; import net.runelite.api.Client; -import static net.runelite.api.Constants.CHUNK_SIZE; import net.runelite.api.GameState; import net.runelite.api.MenuAction; import net.runelite.api.MenuEntry; @@ -155,87 +155,23 @@ public class GroundMarkerPlugin extends Plugin return Collections.EMPTY_LIST; } - List worldPoints = new ArrayList<>(); - for (GroundMarkerPoint point : points) - { - int regionId = point.getRegionId(); - int regionX = point.getRegionX(); - int regionY = point.getRegionY(); - int z = point.getZ(); - - // world point of the tile marker - WorldPoint worldPoint = new WorldPoint( - ((regionId >>> 8) << 6) + regionX, - ((regionId & 0xff) << 6) + regionY, - z - ); - - if (!client.isInInstancedRegion()) + return points.stream() + .map(point -> { - worldPoints.add(worldPoint); - continue; - } + int regionId = point.getRegionId(); + int regionX = point.getRegionX(); + int regionY = point.getRegionY(); + int z = point.getZ(); - // find instance chunks using the template point. there might be more than one. - int[][][] instanceTemplateChunks = client.getInstanceTemplateChunks(); - for (int x = 0; x < instanceTemplateChunks[z].length; ++x) - { - for (int y = 0; y < instanceTemplateChunks[z][x].length; ++y) - { - int chunkData = instanceTemplateChunks[z][x][y]; - int rotation = chunkData >> 1 & 0x3; - int templateChunkY = (chunkData >> 3 & 0x7FF) * CHUNK_SIZE; - int templateChunkX = (chunkData >> 14 & 0x3FF) * CHUNK_SIZE; - if (worldPoint.getX() >= templateChunkX && worldPoint.getX() < templateChunkX + CHUNK_SIZE - && worldPoint.getY() >= templateChunkY && worldPoint.getY() < templateChunkY + CHUNK_SIZE) - { - WorldPoint p = new WorldPoint(client.getBaseX() + x * CHUNK_SIZE + (worldPoint.getX() & (CHUNK_SIZE - 1)), - client.getBaseY() + y * CHUNK_SIZE + (worldPoint.getY() & (CHUNK_SIZE - 1)), - worldPoint.getPlane()); - p = rotate(p, rotation); - worldPoints.add(p); - } - } - } - } - return worldPoints; - } - - /** - * Rotate the chunk containing the given point to rotation 0 - * - * @param point point - * @param rotation rotation - * @return world point - */ - private static WorldPoint rotateInverse(WorldPoint point, int rotation) - { - return rotate(point, 4 - rotation); - } - - /** - * Rotate the coordinates in the chunk according to chunk rotation - * - * @param point point - * @param rotation rotation - * @return world point - */ - private static WorldPoint rotate(WorldPoint point, int rotation) - { - int chunkX = point.getX() & ~(CHUNK_SIZE - 1); - int chunkY = point.getY() & ~(CHUNK_SIZE - 1); - int x = point.getX() & (CHUNK_SIZE - 1); - int y = point.getY() & (CHUNK_SIZE - 1); - switch (rotation) - { - case 1: - return new WorldPoint(chunkX + y, chunkY + (CHUNK_SIZE - 1 - x), point.getPlane()); - case 2: - return new WorldPoint(chunkX + (CHUNK_SIZE - 1 - x), chunkY + (CHUNK_SIZE - 1 - y), point.getPlane()); - case 3: - return new WorldPoint(chunkX + (CHUNK_SIZE - 1 - y), chunkY + x, point.getPlane()); - } - return point; + // world point of the tile marker + return new WorldPoint( + ((regionId >>> 8) << 6) + regionX, + ((regionId & 0xff) << 6) + regionY, + z + ); + }) + .flatMap(wp -> WorldPoint.toLocalInstance(client, wp).stream()) + .collect(Collectors.toList()); } @Subscribe diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/idlenotifier/IdleNotifierPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/idlenotifier/IdleNotifierPlugin.java index 9a47e57de0..8a23af905f 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/idlenotifier/IdleNotifierPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/idlenotifier/IdleNotifierPlugin.java @@ -202,6 +202,7 @@ public class IdleNotifierPlugin extends Plugin case MAGIC_CHARGING_ORBS: case MAGIC_LUNAR_STRING_JEWELRY: case MAGIC_MAKE_TABLET: + case MAGIC_ENCHANTING_JEWELRY: /* Prayer */ case USING_GILDED_ALTAR: /* Farming */ diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/info/InfoPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/info/InfoPanel.java index 2edb250135..203b688b4e 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/info/InfoPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/info/InfoPanel.java @@ -40,6 +40,7 @@ import javax.inject.Singleton; import javax.swing.Box; import javax.swing.ImageIcon; import javax.swing.JLabel; +import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.border.EmptyBorder; import javax.swing.event.HyperlinkEvent; @@ -48,6 +49,7 @@ import net.runelite.api.events.SessionClose; import net.runelite.api.events.SessionOpen; import net.runelite.client.RuneLiteProperties; import net.runelite.client.account.SessionManager; +import net.runelite.client.config.ConfigManager; import net.runelite.client.eventbus.EventBus; import net.runelite.client.eventbus.Subscribe; import net.runelite.client.ui.ColorScheme; @@ -66,9 +68,12 @@ public class InfoPanel extends PluginPanel private static final ImageIcon DISCORD_ICON; private static final ImageIcon PATREON_ICON; private static final ImageIcon WIKI_ICON; + private static final ImageIcon IMPORT_ICON; private final JLabel loggedLabel = new JLabel(); private final JRichTextPane emailLabel = new JRichTextPane(); + private JPanel syncPanel; + private JPanel actionsContainer; @Inject @Nullable @@ -86,6 +91,9 @@ public class InfoPanel extends PluginPanel @Inject private ScheduledExecutorService executor; + @Inject + private ConfigManager configManager; + static { ARROW_RIGHT_ICON = new ImageIcon(ImageUtil.getResourceStreamFromClass(InfoPanel.class, "/util/arrow_right.png")); @@ -93,6 +101,7 @@ public class InfoPanel extends PluginPanel DISCORD_ICON = new ImageIcon(ImageUtil.getResourceStreamFromClass(InfoPanel.class, "discord_icon.png")); PATREON_ICON = new ImageIcon(ImageUtil.getResourceStreamFromClass(InfoPanel.class, "patreon_icon.png")); WIKI_ICON = new ImageIcon(ImageUtil.getResourceStreamFromClass(InfoPanel.class, "wiki_icon.png")); + IMPORT_ICON = new ImageIcon(ImageUtil.getResourceStreamFromClass(InfoPanel.class, "import_icon.png")); } void init() @@ -150,11 +159,22 @@ public class InfoPanel extends PluginPanel versionPanel.add(loggedLabel); versionPanel.add(emailLabel); - updateLoggedIn(); - - JPanel actionsContainer = new JPanel(); + actionsContainer = new JPanel(); actionsContainer.setBorder(new EmptyBorder(10, 0, 0, 0)); - actionsContainer.setLayout(new GridLayout(4, 1, 0, 10)); + actionsContainer.setLayout(new GridLayout(0, 1, 0, 10)); + + syncPanel = buildLinkPanel(IMPORT_ICON, "Import local settings", "to remote RuneLite account", () -> + { + final int result = JOptionPane.showOptionDialog(syncPanel, + "This will replace your current RuneLite account settings with settings from your local profile.", + "Are you sure?", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE, + null, new String[]{"Yes", "No"}, "No"); + + if (result == JOptionPane.YES_OPTION) + { + configManager.importLocal(); + } + }); actionsContainer.add(buildLinkPanel(GITHUB_ICON, "Report an issue or", "make a suggestion", runeLiteProperties.getGithubLink())); actionsContainer.add(buildLinkPanel(DISCORD_ICON, "Talk to us on our", "discord server", runeLiteProperties.getDiscordInvite())); @@ -164,6 +184,7 @@ public class InfoPanel extends PluginPanel add(versionPanel, BorderLayout.NORTH); add(actionsContainer, BorderLayout.CENTER); + updateLoggedIn(); eventBus.register(this); } @@ -171,6 +192,14 @@ public class InfoPanel extends PluginPanel * Builds a link panel with a given icon, text and url to redirect to. */ private static JPanel buildLinkPanel(ImageIcon icon, String topText, String bottomText, String url) + { + return buildLinkPanel(icon, topText, bottomText, () -> LinkBrowser.browse(url)); + } + + /** + * Builds a link panel with a given icon, text and callable to call. + */ + private static JPanel buildLinkPanel(ImageIcon icon, String topText, String bottomText, Runnable callback) { JPanel container = new JPanel(); container.setBackground(ColorScheme.DARKER_GRAY_COLOR); @@ -193,7 +222,6 @@ public class InfoPanel extends PluginPanel @Override public void mousePressed(MouseEvent mouseEvent) { - LinkBrowser.browse(url); container.setBackground(pressedColor); textContainer.setBackground(pressedColor); } @@ -201,6 +229,7 @@ public class InfoPanel extends PluginPanel @Override public void mouseReleased(MouseEvent e) { + callback.run(); container.setBackground(hoverColor); textContainer.setBackground(hoverColor); } @@ -252,12 +281,14 @@ public class InfoPanel extends PluginPanel emailLabel.setContentType("text/plain"); emailLabel.setText(name); loggedLabel.setText("Logged in as"); + actionsContainer.add(syncPanel, 0); } else { emailLabel.setContentType("text/html"); emailLabel.setText("Login to sync settings to the cloud."); loggedLabel.setText("Not logged in"); + actionsContainer.remove(syncPanel); } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/itemcharges/ItemChargeConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/itemcharges/ItemChargeConfig.java index 40488c5137..28c503b20a 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/itemcharges/ItemChargeConfig.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/itemcharges/ItemChargeConfig.java @@ -203,4 +203,15 @@ public interface ItemChargeConfig extends Config { return false; } + + @ConfigItem( + keyName = "showInfoboxes", + name = "Show Infoboxes", + description = "Configures whether to show an infobox equipped charge items", + position = 15 + ) + default boolean showInfoboxes() + { + return false; + } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/itemcharges/ItemChargeInfobox.java b/runelite-client/src/main/java/net/runelite/client/plugins/itemcharges/ItemChargeInfobox.java new file mode 100644 index 0000000000..7ee70d44b5 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/itemcharges/ItemChargeInfobox.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2018, Hydrox6 + * 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.itemcharges; + +import java.awt.Color; +import java.awt.image.BufferedImage; +import lombok.Getter; +import net.runelite.api.EquipmentInventorySlot; +import net.runelite.client.ui.overlay.infobox.Counter; + +@Getter +class ItemChargeInfobox extends Counter +{ + private final ItemChargePlugin plugin; + private final ItemWithSlot item; + private final EquipmentInventorySlot slot; + + ItemChargeInfobox( + ItemChargePlugin plugin, + BufferedImage image, + String name, + int charges, + ItemWithSlot item, + EquipmentInventorySlot slot) + { + super(image, plugin, charges); + setTooltip(name); + this.plugin = plugin; + this.item = item; + this.slot = slot; + } + + @Override + public Color getTextColor() + { + return getPlugin().getColor(getCount()); + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/itemcharges/ItemChargeOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/itemcharges/ItemChargeOverlay.java index 7730d46ffd..27bb35b21d 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/itemcharges/ItemChargeOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/itemcharges/ItemChargeOverlay.java @@ -24,7 +24,6 @@ */ package net.runelite.client.plugins.itemcharges; -import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics2D; import java.awt.Point; @@ -83,7 +82,7 @@ class ItemChargeOverlay extends Overlay continue; } - charges = itemChargePlugin.getDodgyCharges(); + charges = config.dodgyNecklace(); } else { @@ -112,7 +111,7 @@ class ItemChargeOverlay extends Overlay final TextComponent textComponent = new TextComponent(); textComponent.setPosition(new Point(bounds.x, bounds.y + 16)); textComponent.setText(charges < 0 ? "?" : String.valueOf(charges)); - textComponent.setColor(getColor(charges)); + textComponent.setColor(itemChargePlugin.getColor(charges)); textComponent.render(graphics); } return null; @@ -137,20 +136,6 @@ class ItemChargeOverlay extends Overlay return jewellery; } - private Color getColor(int charges) - { - Color color = Color.WHITE; - if (charges <= config.veryLowWarning()) - { - color = config.veryLowWarningColor(); - } - else if (charges <= config.lowWarning()) - { - color = config.lowWarningolor(); - } - return color; - } - private boolean displayOverlay() { return config.showTeleportCharges() || config.showDodgyCount() || config.showFungicideCharges() diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/itemcharges/ItemChargePlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/itemcharges/ItemChargePlugin.java index c545e9a5bb..e295efd9e5 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/itemcharges/ItemChargePlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/itemcharges/ItemChargePlugin.java @@ -1,5 +1,6 @@ /* * Copyright (c) 2017, Seth + * Copyright (c) 2018, Hydrox6 * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -25,19 +26,29 @@ package net.runelite.client.plugins.itemcharges; import com.google.inject.Provides; +import java.awt.Color; +import java.awt.image.BufferedImage; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.inject.Inject; -import lombok.AccessLevel; -import lombok.Getter; import net.runelite.api.ChatMessageType; +import net.runelite.api.Client; +import net.runelite.api.EquipmentInventorySlot; +import net.runelite.api.InventoryID; +import net.runelite.api.Item; +import net.runelite.api.ItemContainer; +import net.runelite.api.ItemID; import net.runelite.api.events.ChatMessage; +import net.runelite.api.events.ConfigChanged; +import net.runelite.api.events.ItemContainerChanged; import net.runelite.client.Notifier; import net.runelite.client.config.ConfigManager; import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.game.ItemManager; import net.runelite.client.plugins.Plugin; import net.runelite.client.plugins.PluginDescriptor; import net.runelite.client.ui.overlay.OverlayManager; +import net.runelite.client.ui.overlay.infobox.InfoBoxManager; @PluginDescriptor( name = "Item Charges", @@ -56,21 +67,27 @@ public class ItemChargePlugin extends Plugin private static final int MAX_DODGY_CHARGES = 10; + @Inject + private Client client; + @Inject private OverlayManager overlayManager; @Inject private ItemChargeOverlay overlay; + @Inject + private ItemManager itemManager; + + @Inject + private InfoBoxManager infoBoxManager; + @Inject private Notifier notifier; @Inject private ItemChargeConfig config; - @Getter(AccessLevel.PACKAGE) - private int dodgyCharges; - @Provides ItemChargeConfig getConfig(ConfigManager configManager) { @@ -81,13 +98,43 @@ public class ItemChargePlugin extends Plugin protected void startUp() { overlayManager.add(overlay); - dodgyCharges = config.dodgyNecklace(); } @Override protected void shutDown() throws Exception { overlayManager.remove(overlay); + infoBoxManager.removeIf(ItemChargeInfobox.class::isInstance); + } + + @Subscribe + public void onConfigChanged(ConfigChanged event) + { + if (!event.getGroup().equals("itemCharge")) + { + return; + } + + if (!config.showInfoboxes()) + { + infoBoxManager.removeIf(ItemChargeInfobox.class::isInstance); + return; + } + + if (!config.showTeleportCharges()) + { + removeInfobox(ItemWithSlot.TELEPORT); + } + + if (!config.showAbyssalBraceletCharges()) + { + removeInfobox(ItemWithSlot.ABYSSAL_BRACELET); + } + + if (!config.showDodgyCount()) + { + removeInfobox(ItemWithSlot.DODGY_NECKLACE); + } } @Subscribe @@ -110,22 +157,141 @@ public class ItemChargePlugin extends Plugin notifier.notify("Your dodgy necklace has crumbled to dust."); } - setDodgyCharges(MAX_DODGY_CHARGES); + updateDodgyNecklaceCharges(MAX_DODGY_CHARGES); } else if (dodgyCheckMatcher.find()) { - setDodgyCharges(Integer.parseInt(dodgyCheckMatcher.group(1))); + updateDodgyNecklaceCharges(Integer.parseInt(dodgyCheckMatcher.group(1))); } else if (dodgyProtectMatcher.find()) { - setDodgyCharges(Integer.parseInt(dodgyProtectMatcher.group(1))); + updateDodgyNecklaceCharges(Integer.parseInt(dodgyProtectMatcher.group(1))); } } } - private void setDodgyCharges(int dodgyCharges) + @Subscribe + public void onItemContainerChanged(ItemContainerChanged event) { - this.dodgyCharges = dodgyCharges; - config.dodgyNecklace(dodgyCharges); + if (event.getItemContainer() != client.getItemContainer(InventoryID.EQUIPMENT) || !config.showInfoboxes()) + { + return; + } + + final Item[] items = event.getItemContainer().getItems(); + + if (config.showTeleportCharges()) + { + updateJewelleryInfobox(ItemWithSlot.TELEPORT, items); + } + + if (config.showDodgyCount()) + { + updateJewelleryInfobox(ItemWithSlot.DODGY_NECKLACE, items); + } + + if (config.showAbyssalBraceletCharges()) + { + updateJewelleryInfobox(ItemWithSlot.ABYSSAL_BRACELET, items); + } + } + + private void updateDodgyNecklaceCharges(final int value) + { + config.dodgyNecklace(value); + + if (config.showInfoboxes() && config.showDodgyCount()) + { + final ItemContainer itemContainer = client.getItemContainer(InventoryID.EQUIPMENT); + + if (itemContainer == null) + { + return; + } + + updateJewelleryInfobox(ItemWithSlot.DODGY_NECKLACE, itemContainer.getItems()); + } + } + + private void updateJewelleryInfobox(ItemWithSlot item, Item[] items) + { + for (final EquipmentInventorySlot equipmentInventorySlot : item.getSlots()) + { + updateJewelleryInfobox(item, items, equipmentInventorySlot); + } + } + + private void updateJewelleryInfobox(ItemWithSlot type, Item[] items, EquipmentInventorySlot slot) + { + removeInfobox(type, slot); + + if (slot.getSlotIdx() >= items.length) + { + return; + } + + final int id = items[slot.getSlotIdx()].getId(); + if (id < 0) + { + return; + } + + final ItemWithCharge itemWithCharge = ItemWithCharge.findItem(id); + int charges = -1; + + if (itemWithCharge == null) + { + if (id == ItemID.DODGY_NECKLACE && type == ItemWithSlot.DODGY_NECKLACE) + { + charges = config.dodgyNecklace(); + } + } + else if (itemWithCharge.getType() == type.getType()) + { + charges = itemWithCharge.getCharges(); + } + + if (charges <= 0) + { + return; + } + + final String name = itemManager.getItemComposition(id).getName(); + final BufferedImage image = itemManager.getImage(id); + final ItemChargeInfobox infobox = new ItemChargeInfobox(this, image, name, charges, type, slot); + infoBoxManager.addInfoBox(infobox); + } + + private void removeInfobox(final ItemWithSlot item) + { + infoBoxManager.removeIf(t -> t instanceof ItemChargeInfobox && ((ItemChargeInfobox) t).getItem() == item); + } + + private void removeInfobox(final ItemWithSlot item, final EquipmentInventorySlot slot) + { + infoBoxManager.removeIf(t -> + { + if (!(t instanceof ItemChargeInfobox)) + { + return false; + } + + final ItemChargeInfobox i = (ItemChargeInfobox)t; + return i.getItem() == item && i.getSlot() == slot; + }); + } + + Color getColor(int charges) + { + Color color = Color.WHITE; + if (charges <= config.veryLowWarning()) + { + color = config.veryLowWarningColor(); + } + else if (charges <= config.lowWarning()) + { + color = config.lowWarningolor(); + } + return color; } } \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/itemcharges/ItemChargeType.java b/runelite-client/src/main/java/net/runelite/client/plugins/itemcharges/ItemChargeType.java index 2f25c12164..cbe5d20ff3 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/itemcharges/ItemChargeType.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/itemcharges/ItemChargeType.java @@ -32,5 +32,6 @@ enum ItemChargeType IMPBOX, TELEPORT, WATERCAN, - WATERSKIN + WATERSKIN, + DODGY_NECKLACE } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/itemcharges/ItemWithSlot.java b/runelite-client/src/main/java/net/runelite/client/plugins/itemcharges/ItemWithSlot.java new file mode 100644 index 0000000000..3fbd2bac66 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/itemcharges/ItemWithSlot.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2019, Tomas Slusny + * 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.itemcharges; + +import com.google.common.collect.Sets; +import java.util.Set; +import lombok.Getter; +import net.runelite.api.EquipmentInventorySlot; + +@Getter +enum ItemWithSlot +{ + ABYSSAL_BRACELET(ItemChargeType.ABYSSAL_BRACELET, EquipmentInventorySlot.GLOVES), + DODGY_NECKLACE(ItemChargeType.DODGY_NECKLACE, EquipmentInventorySlot.AMULET), + TELEPORT(ItemChargeType.TELEPORT, EquipmentInventorySlot.WEAPON, EquipmentInventorySlot.AMULET, EquipmentInventorySlot.GLOVES, EquipmentInventorySlot.RING); + + private final ItemChargeType type; + private final Set slots; + + ItemWithSlot(final ItemChargeType type, final EquipmentInventorySlot... slots) + { + this.type = type; + this.slots = Sets.newHashSet(slots); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/itemprices/ItemPricesConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/itemprices/ItemPricesConfig.java index ed69408341..a7b62e52b3 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/itemprices/ItemPricesConfig.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/itemprices/ItemPricesConfig.java @@ -74,4 +74,16 @@ public interface ItemPricesConfig extends Config { return true; } + + @ConfigItem( + keyName = "showAlchProfit", + name = "Show High Alchemy Profit", + description = "Show the profit from casting high alchemy on items", + position = 5 + ) + default boolean showAlchProfit() + { + return false; + } + } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/itemprices/ItemPricesOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/itemprices/ItemPricesOverlay.java index 7e170db214..7c748a7702 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/itemprices/ItemPricesOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/itemprices/ItemPricesOverlay.java @@ -196,6 +196,7 @@ class ItemPricesOverlay extends Overlay int gePrice = 0; int haPrice = 0; + int haProfit = 0; if (config.showGEPrice()) { @@ -205,16 +206,20 @@ class ItemPricesOverlay extends Overlay { haPrice = Math.round(itemDef.getPrice() * HIGH_ALCHEMY_CONSTANT); } + if (gePrice > 0 && haPrice > 0 && config.showAlchProfit()) + { + haProfit = calculateHAProfit(haPrice, gePrice); + } if (gePrice > 0 || haPrice > 0) { - return stackValueText(qty, gePrice, haPrice); + return stackValueText(qty, gePrice, haPrice, haProfit); } return null; } - private String stackValueText(int qty, int gePrice, int haValue) + private String stackValueText(int qty, int gePrice, int haValue, int haProfit) { if (gePrice > 0) { @@ -246,9 +251,36 @@ class ItemPricesOverlay extends Overlay } } + if (haProfit != 0) + { + Color haColor = haProfitColor(haProfit); + + itemStringBuilder.append("
"); + itemStringBuilder.append("HA Profit: ") + .append(ColorUtil.wrapWithColorTag(String.valueOf(haProfit * qty), haColor)) + .append(" gp"); + if (config.showEA() && qty > 1) + { + itemStringBuilder.append(" (") + .append(ColorUtil.wrapWithColorTag(String.valueOf(haProfit), haColor)) + .append(" ea)"); + } + } + // Build string and reset builder final String text = itemStringBuilder.toString(); itemStringBuilder.setLength(0); return text; } + + private int calculateHAProfit(int haPrice, int gePrice) + { + int natureRunePrice = itemManager.getItemPrice(ItemID.NATURE_RUNE); + return haPrice - gePrice - natureRunePrice; + } + + private static Color haProfitColor(int haProfit) + { + return haProfit >= 0 ? Color.GREEN : Color.RED; + } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/ItemStatOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/ItemStatOverlay.java index 986321e966..73113bd0ac 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/ItemStatOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/ItemStatOverlay.java @@ -258,7 +258,7 @@ public class ItemStatOverlay extends Overlay if (config.relative()) { - b.append(c.getRelative()); + b.append(c.getFormattedRelative()); } if (config.theoretical()) @@ -267,7 +267,7 @@ public class ItemStatOverlay extends Overlay { b.append("/"); } - b.append(c.getTheoretical()); + b.append(c.getFormattedTheoretical()); } if (config.absolute() && (config.relative() || config.theoretical())) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/RangeStatBoost.java b/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/RangeStatBoost.java index abe47cea1c..13011b9f6a 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/RangeStatBoost.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/RangeStatBoost.java @@ -42,33 +42,21 @@ public class RangeStatBoost extends SingleEffect @Override public StatChange effect(Client client) { - final StatChange a = this.a.effect(client); - final StatChange b = this.b.effect(client); + final StatChange changeA = this.a.effect(client); + final StatChange changeB = this.b.effect(client); - final StatChange r = new StatChange(); - r.setAbsolute(concat(a.getAbsolute(), b.getAbsolute())); - r.setRelative(concat(a.getRelative(), b.getRelative())); - r.setTheoretical(concat(a.getTheoretical(), b.getTheoretical())); - r.setStat(a.getStat()); + final RangeStatChange r = new RangeStatChange(); + r.setMinAbsolute(Math.min(changeA.getAbsolute(), changeB.getAbsolute())); + r.setAbsolute(Math.max(changeA.getAbsolute(), changeB.getAbsolute())); + r.setMinRelative(Math.min(changeA.getRelative(), changeB.getRelative())); + r.setRelative(Math.max(changeA.getRelative(), changeB.getRelative())); + r.setMinTheoretical(Math.min(changeA.getTheoretical(), changeB.getTheoretical())); + r.setTheoretical(Math.max(changeA.getTheoretical(), changeB.getTheoretical())); + r.setStat(changeA.getStat()); - final int avg = (a.getPositivity().ordinal() + b.getPositivity().ordinal()) / 2; + final int avg = (changeA.getPositivity().ordinal() + changeB.getPositivity().ordinal()) / 2; r.setPositivity(Positivity.values()[avg]); return r; } - - private String concat(String a, String b) - { - // If they share a operator, strip b's duplicate - if (a.length() > 1 && b.length() > 1) - { - final char a0 = a.charAt(0); - if ((a0 == '+' || a0 == '-' || a0 == '±') && b.charAt(0) == a0) - { - b = b.substring(1); - } - } - - return a + "~" + b; - } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/RangeStatChange.java b/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/RangeStatChange.java new file mode 100644 index 0000000000..90b614bf10 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/RangeStatChange.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2016-2019, Jordan Atwood + * 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.itemstats; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * A stat change which can result in different magnitudes of change to the stat + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class RangeStatChange extends StatChange +{ + /** + * Minimum relative change that will occur if the stat boost is applied now. + * In this class, {@code relative} is representative of the maximum relative change that will + * occur. + */ + private int minRelative; + + /** + * Minimum theoretical change that can occur before boost cap is enforced. + * In this class, {@code theoretical} is representative of the maximum theoretical change that + * will occur. + */ + private int minTheoretical; + + /** + * Minimum absolute total of the stat after applying the boost. + * In this class, {@code absolute} is representative of the maximum absolute change that will + * occur. + */ + private int minAbsolute; + + /** + * Returns a human-readable formatted relative boost. + * Should be the boost range in the format "±N" (for minimum -N and maximum +N values), + * "+MIN~MAX" (for minimum and maximum values of the same sign), + * "-MIN~+MAX" (for negative minimum and positive maximum values), or + * "+MAX" (for equal minimum and maximum values). + * + * @return The formatted relative boost amount + */ + @Override + public String getFormattedRelative() + { + return concat(minRelative, getRelative()); + } + + /** + * Returns a human-readable formatted theoretical boost. + * Should be the boost range in the format "±N" (for minimum -N and maximum +N values), + * "+MIN~MAX" (for minimum and maximum values of the same sign), + * "-MIN~+MAX" (for negative minimum and positive maximum values), or + * "+MAX" (for equal minimum and maximum values). + * + * @return The formatted theoretical boost amount + */ + @Override + public String getFormattedTheoretical() + { + return concat(minTheoretical, getTheoretical()); + } + + private static String concat(int changeA, int changeB) + { + if (changeA == changeB) + { + return formatBoost(changeA); + } + else if (changeA * -1 == changeB) + { + return "±" + Math.abs(changeA); + } + + final StringBuilder sb = new StringBuilder(); + + sb.append(String.format("%+d", changeA)); + sb.append('~'); + + // If they share a operator, strip b's duplicate + if (changeA < 0 && changeB < 0 + || changeA >= 0 && changeB >= 0) + { + sb.append(Math.abs(changeB)); + } + else + { + sb.append(String.format("%+d", changeB)); + } + + return sb.toString(); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/StatBoost.java b/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/StatBoost.java index 1bc59d7412..cc5f7797f2 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/StatBoost.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/StatBoost.java @@ -88,10 +88,9 @@ public abstract class StatBoost extends SingleEffect { out.setPositivity(Positivity.WORSE); } - out.setAbsolute(Integer.toString(newValue)); - out.setRelative(String.format("%+d", delta)); - out.setTheoretical(String.format("%+d", calcedDelta)); + out.setAbsolute(newValue); + out.setRelative(delta); + out.setTheoretical(calcedDelta); return out; } - } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/StatChange.java b/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/StatChange.java index 3157748083..e9453984f4 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/StatChange.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/StatChange.java @@ -40,23 +40,48 @@ public class StatChange /** * Relative change that will occur if the stat boost is applied now. - * Should be a number prefixed by "+" or "-". */ - private String relative; + private int relative; /** * Theoretical change that can occur before boost cap is enforced. - * Should be a number prefixed by "+" or "-". */ - private String theoretical; + private int theoretical; /** * Absolute total of the stat after applying the boost. */ - private String absolute; + private int absolute; /** * How beneficial this stat boost will be to the player. */ private Positivity positivity; + + /** + * Returns a human-readable formatted relative boost. + * Should be the boost amount prefixed by "+" or "-". + * + * @return The formatted relative boost amount + */ + public String getFormattedRelative() + { + return formatBoost(relative); + } + + /** + * Returns a human-readable formatted theoretical boost. + * Should be the boost amount prefixed by "+" or "-". + * + * @return The formatted theoretical boost amount + */ + public String getFormattedTheoretical() + { + return formatBoost(theoretical); + } + + static String formatBoost(int boost) + { + return String.format("%+d", boost); + } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/special/SpicyStew.java b/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/special/SpicyStew.java index 8dab7fd601..7eee548751 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/special/SpicyStew.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/itemstats/special/SpicyStew.java @@ -132,11 +132,14 @@ public class SpicyStew implements Effect int currentBoost = currentValue - currentBase; // Can be negative int spiceBoostCapped = (currentBoost <= 0) ? spiceBoost : Math.max(0, spiceBoost - currentBoost); - StatChange change = new StatChange(); + final RangeStatChange change = new RangeStatChange(); change.setStat(stat); - change.setRelative("±" + spiceBoostCapped); - change.setTheoretical("±" + spiceBoost); - change.setAbsolute(String.valueOf(stat.getValue(client) + spiceBoostCapped)); + change.setMinRelative(-spiceBoost); + change.setRelative(spiceBoostCapped); + change.setMinTheoretical(-spiceBoost); + change.setTheoretical(spiceBoost); + change.setMinAbsolute(Math.max(-spiceBoost, -currentValue)); + change.setAbsolute(stat.getValue(client) + spiceBoostCapped); Positivity positivity; if (spiceBoostCapped == 0) @@ -155,5 +158,4 @@ public class SpicyStew implements Effect return change; } - } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/kingdomofmiscellania/KingdomCounter.java b/runelite-client/src/main/java/net/runelite/client/plugins/kingdomofmiscellania/KingdomCounter.java index 7dc6aabf35..8a2871125f 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/kingdomofmiscellania/KingdomCounter.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/kingdomofmiscellania/KingdomCounter.java @@ -32,9 +32,9 @@ public class KingdomCounter extends Counter { private final KingdomPlugin plugin; - public KingdomCounter(BufferedImage image, KingdomPlugin plugin) + KingdomCounter(BufferedImage image, KingdomPlugin plugin) { - super(image, plugin, String.valueOf(plugin.getFavor())); + super(image, plugin, plugin.getFavor()); this.plugin = plugin; } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/kourendlibrary/KourendLibraryConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/kourendlibrary/KourendLibraryConfig.java index b61a24c139..6ec40a00fd 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/kourendlibrary/KourendLibraryConfig.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/kourendlibrary/KourendLibraryConfig.java @@ -42,4 +42,14 @@ public interface KourendLibraryConfig extends Config { return true; } + + @ConfigItem( + keyName = "hideDuplicateBook", + name = "Hide duplicate book", + description = "Don't show the duplicate book locations in the library" + ) + default boolean hideDuplicateBook() + { + return true; + } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/kourendlibrary/KourendLibraryOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/kourendlibrary/KourendLibraryOverlay.java index 3c6863af3a..5cf7910ee6 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/kourendlibrary/KourendLibraryOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/kourendlibrary/KourendLibraryOverlay.java @@ -35,6 +35,7 @@ import java.util.List; import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; +import javax.annotation.Nullable; import lombok.AccessLevel; import lombok.Setter; import net.runelite.api.Client; @@ -54,15 +55,19 @@ class KourendLibraryOverlay extends Overlay private final static int MAXIMUM_DISTANCE = 24; private final Library library; private final Client client; + private final KourendLibraryConfig config; + private final KourendLibraryPlugin plugin; @Setter(AccessLevel.PACKAGE) private boolean hidden; @Inject - private KourendLibraryOverlay(Library library, Client client) + private KourendLibraryOverlay(Library library, Client client, KourendLibraryConfig config, KourendLibraryPlugin plugin) { this.library = library; this.client = client; + this.config = config; + this.plugin = plugin; setPosition(OverlayPosition.DYNAMIC); setLayer(OverlayLayer.ABOVE_SCENE); @@ -133,7 +138,7 @@ class KourendLibraryOverlay extends Overlay Color color = bookIsKnown ? Color.ORANGE : Color.WHITE; // Render the poly on the floor - if (!(bookIsKnown && book == null) && (library.getState() == SolvedState.NO_DATA || book != null || !possible.isEmpty())) + if (!(bookIsKnown && book == null) && (library.getState() == SolvedState.NO_DATA || book != null || !possible.isEmpty()) && !shouldHideOverlayIfDuplicateBook(book)) { Polygon poly = getCanvasTilePoly(client, localBookcase); if (poly != null) @@ -146,7 +151,7 @@ class KourendLibraryOverlay extends Overlay // If the book is singled out, render the text and the book's icon if (bookIsKnown) { - if (book != null) + if (book != null && !shouldHideOverlayIfDuplicateBook(book)) { FontMetrics fm = g.getFontMetrics(); Rectangle2D bounds = fm.getStringBounds(book.getShortName(), g); @@ -216,9 +221,10 @@ class KourendLibraryOverlay extends Overlay .forEach(n -> { Book b = library.getCustomerBook(); + boolean doesPlayerContainBook = b != null && plugin.doesPlayerContainBook(b); LocalPoint local = n.getLocalLocation(); Polygon poly = getCanvasTilePoly(client, local); - OverlayUtil.renderPolygon(g, poly, Color.WHITE); + OverlayUtil.renderPolygon(g, poly, doesPlayerContainBook ? Color.GREEN : Color.WHITE); Point screen = Perspective.localToCanvas(client, local, client.getPlane(), n.getLogicalHeight()); if (screen != null) { @@ -229,4 +235,12 @@ class KourendLibraryOverlay extends Overlay return null; } + + private boolean shouldHideOverlayIfDuplicateBook(@Nullable Book book) + { + return config.hideDuplicateBook() + && book != null + && !book.isDarkManuscript() + && plugin.doesPlayerContainBook(book); + } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/kourendlibrary/KourendLibraryPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/kourendlibrary/KourendLibraryPlugin.java index 73602299f4..649189fc47 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/kourendlibrary/KourendLibraryPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/kourendlibrary/KourendLibraryPlugin.java @@ -26,6 +26,7 @@ package net.runelite.client.plugins.kourendlibrary; import com.google.inject.Provides; import java.awt.image.BufferedImage; +import java.util.EnumSet; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.inject.Inject; @@ -34,6 +35,9 @@ import lombok.extern.slf4j.Slf4j; import net.runelite.api.AnimationID; import net.runelite.api.ChatMessageType; import net.runelite.api.Client; +import net.runelite.api.InventoryID; +import net.runelite.api.Item; +import net.runelite.api.ItemContainer; import net.runelite.api.MenuAction; import net.runelite.api.Player; import net.runelite.api.coords.WorldPoint; @@ -41,6 +45,7 @@ import net.runelite.api.events.AnimationChanged; import net.runelite.api.events.ChatMessage; import net.runelite.api.events.ConfigChanged; import net.runelite.api.events.GameTick; +import net.runelite.api.events.ItemContainerChanged; import net.runelite.api.events.MenuOptionClicked; import net.runelite.api.widgets.Widget; import net.runelite.api.widgets.WidgetInfo; @@ -94,6 +99,7 @@ public class KourendLibraryPlugin extends Plugin private boolean buttonAttached = false; private WorldPoint lastBookcaseClick = null; private WorldPoint lastBookcaseAnimatedOn = null; + private EnumSet playerBooks = null; @Provides KourendLibraryConfig provideConfig(ConfigManager configManager) @@ -120,6 +126,8 @@ public class KourendLibraryPlugin extends Plugin overlayManager.add(overlay); + updatePlayerBooks(); + if (!config.hideButton()) { clientToolbar.addNavigation(navButton); @@ -135,6 +143,7 @@ public class KourendLibraryPlugin extends Plugin buttonAttached = false; lastBookcaseClick = null; lastBookcaseAnimatedOn = null; + playerBooks = null; } @Subscribe @@ -271,4 +280,37 @@ public class KourendLibraryPlugin extends Plugin } } } + + @Subscribe + public void onItemContainerChanged(ItemContainerChanged itemContainerChangedEvent) + { + updatePlayerBooks(); + } + + boolean doesPlayerContainBook(Book book) + { + return playerBooks.contains(book); + } + + private void updatePlayerBooks() + { + ItemContainer itemContainer = client.getItemContainer(InventoryID.INVENTORY); + + if (itemContainer != null) + { + EnumSet books = EnumSet.noneOf(Book.class); + + for (Item item : itemContainer.getItems()) + { + Book book = Book.byId(item.getId()); + + if (book != null) + { + books.add(book); + } + } + + playerBooks = books; + } + } } \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerConfig.java index 98b7053117..bba5994d89 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerConfig.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerConfig.java @@ -51,11 +51,23 @@ public interface LootTrackerConfig extends Config @ConfigItem( keyName = "saveLoot", - name = "Save loot", - description = "Save loot between client sessions (requires being logged in)" + name = "Submit loot tracker data", + description = "Submit loot tracker data (requires being logged in)" ) default boolean saveLoot() { return true; } + + @ConfigItem( + keyName = "syncPanel", + name = "Synchronize panel contents", + description = "Synchronize you local loot tracker with your online (requires being logged in). This means" + + " that panel is filled with portion of your remote data on startup and deleting data in panel deletes them" + + " also on server." + ) + default boolean syncPanel() + { + return true; + } } \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPanel.java index 4aaa7e0c7a..ed850cdcbb 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPanel.java @@ -99,6 +99,7 @@ class LootTrackerPanel extends PluginPanel private final ItemManager itemManager; private final LootTrackerPlugin plugin; + private final LootTrackerConfig config; private boolean groupLoot; private boolean hideIgnoredItems; @@ -130,10 +131,11 @@ class LootTrackerPanel extends PluginPanel INVISIBLE_ICON_HOVER = new ImageIcon(ImageUtil.alphaOffset(invisibleImg, -220)); } - LootTrackerPanel(final LootTrackerPlugin plugin, final ItemManager itemManager) + LootTrackerPanel(final LootTrackerPlugin plugin, final ItemManager itemManager, final LootTrackerConfig config) { this.itemManager = itemManager; this.plugin = plugin; + this.config = config; this.hideIgnoredItems = true; setBorder(new EmptyBorder(6, 6, 6, 6)); @@ -297,7 +299,7 @@ class LootTrackerPanel extends PluginPanel // Delete all loot, or loot matching the current view LootTrackerClient client = plugin.getLootTrackerClient(); - if (client != null) + if (client != null && config.syncPanel()) { client.delete(currentView); } @@ -472,7 +474,7 @@ class LootTrackerPanel extends PluginPanel LootTrackerClient client = plugin.getLootTrackerClient(); // Without loot being grouped we have no way to identify single kills to be deleted - if (client != null && groupLoot) + if (client != null && groupLoot && config.syncPanel()) { client.delete(box.getId()); } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPlugin.java index acb725db64..055c2cc3f4 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPlugin.java @@ -28,6 +28,7 @@ package net.runelite.client.plugins.loottracker; import com.google.inject.Provides; import java.awt.image.BufferedImage; import java.io.IOException; +import java.time.Instant; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -194,7 +195,7 @@ public class LootTrackerPlugin extends Plugin protected void startUp() throws Exception { ignoredItems = Text.fromCSV(config.getIgnoredItems()); - panel = new LootTrackerPanel(this, itemManager); + panel = new LootTrackerPanel(this, itemManager, config); spriteManager.getSpriteAsync(SpriteID.TAB_INVENTORY, 0, panel::loadHeaderIcon); final BufferedImage icon = ImageUtil.getResourceStreamFromClass(getClass(), "panel_icon.png"); @@ -226,9 +227,8 @@ public class LootTrackerPlugin extends Plugin { Collection lootRecords; - if (!config.saveLoot()) + if (!config.syncPanel()) { - // don't load loot if we're not saving loot return; } @@ -274,7 +274,7 @@ public class LootTrackerPlugin extends Plugin if (lootTrackerClient != null && config.saveLoot()) { - LootRecord lootRecord = new LootRecord(name, LootRecordType.NPC, toGameItems(items)); + LootRecord lootRecord = new LootRecord(name, LootRecordType.NPC, toGameItems(items), Instant.now()); lootTrackerClient.submit(lootRecord); } } @@ -291,7 +291,7 @@ public class LootTrackerPlugin extends Plugin if (lootTrackerClient != null && config.saveLoot()) { - LootRecord lootRecord = new LootRecord(name, LootRecordType.PLAYER, toGameItems(items)); + LootRecord lootRecord = new LootRecord(name, LootRecordType.PLAYER, toGameItems(items), Instant.now()); lootTrackerClient.submit(lootRecord); } } @@ -350,7 +350,7 @@ public class LootTrackerPlugin extends Plugin if (lootTrackerClient != null && config.saveLoot()) { - LootRecord lootRecord = new LootRecord(eventType, LootRecordType.EVENT, toGameItems(items)); + LootRecord lootRecord = new LootRecord(eventType, LootRecordType.EVENT, toGameItems(items), Instant.now()); lootTrackerClient.submit(lootRecord); } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/menuentryswapper/MenuEntrySwapperPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/menuentryswapper/MenuEntrySwapperPlugin.java index e6420bdc99..ca62eca65e 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/menuentryswapper/MenuEntrySwapperPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/menuentryswapper/MenuEntrySwapperPlugin.java @@ -45,8 +45,10 @@ import net.runelite.api.events.MenuOptionClicked; import net.runelite.api.events.PostItemComposition; import net.runelite.api.events.WidgetMenuOptionClicked; import net.runelite.api.widgets.WidgetInfo; +import net.runelite.client.callback.ClientThread; import net.runelite.client.config.ConfigManager; import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.game.ItemManager; import net.runelite.client.game.ItemVariationMapping; import net.runelite.client.input.KeyManager; import net.runelite.client.menus.MenuManager; @@ -101,6 +103,9 @@ public class MenuEntrySwapperPlugin extends Plugin @Inject private Client client; + @Inject + private ClientThread clientThread; + @Inject private MenuEntrySwapperConfig config; @@ -116,6 +121,9 @@ public class MenuEntrySwapperPlugin extends Plugin @Inject private MenuManager menuManager; + @Inject + private ItemManager itemManager; + @Getter private boolean configuringShiftClick = false; @@ -146,6 +154,11 @@ public class MenuEntrySwapperPlugin extends Plugin @Subscribe public void onConfigChanged(ConfigChanged event) { + if (!CONFIG_GROUP.equals(event.getGroup())) + { + return; + } + if (event.getKey().equals("shiftClickCustomization")) { if (config.shiftClickCustomization()) @@ -157,6 +170,16 @@ public class MenuEntrySwapperPlugin extends Plugin disableCustomization(); } } + else if (event.getKey().startsWith(ITEM_KEY_PREFIX)) + { + clientThread.invoke(this::resetItemCompositionCache); + } + } + + private void resetItemCompositionCache() + { + itemManager.invalidateItemCompositionCache(); + client.getItemCompositionCache().reset(); } private Integer getSwapConfig(int itemId) @@ -179,6 +202,7 @@ public class MenuEntrySwapperPlugin extends Plugin private void unsetSwapConfig(int itemId) { + itemId = ItemVariationMapping.map(itemId); configManager.unsetConfiguration(CONFIG_GROUP, ITEM_KEY_PREFIX + itemId); } @@ -186,6 +210,7 @@ public class MenuEntrySwapperPlugin extends Plugin { keyManager.registerKeyListener(inputListener); refreshShiftClickCustomizationMenus(); + clientThread.invoke(this::resetItemCompositionCache); } private void disableCustomization() @@ -193,6 +218,7 @@ public class MenuEntrySwapperPlugin extends Plugin keyManager.unregisterKeyListener(inputListener); removeShiftClickCustomizationMenus(); configuringShiftClick = false; + clientThread.invoke(this::resetItemCompositionCache); } @Subscribe @@ -290,7 +316,6 @@ public class MenuEntrySwapperPlugin extends Plugin if (option.equals(RESET) && target.equals(MENU_TARGET)) { unsetSwapConfig(itemId); - itemComposition.resetShiftClickActionIndex(); return; } @@ -323,7 +348,6 @@ public class MenuEntrySwapperPlugin extends Plugin if (valid) { setSwapConfig(itemId, index); - itemComposition.setShiftClickActionIndex(index); } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/mta/graveyard/GraveyardCounter.java b/runelite-client/src/main/java/net/runelite/client/plugins/mta/graveyard/GraveyardCounter.java index 83e74cc6d6..ecfbafd544 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/mta/graveyard/GraveyardCounter.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/mta/graveyard/GraveyardCounter.java @@ -31,22 +31,15 @@ import net.runelite.client.ui.overlay.infobox.Counter; public class GraveyardCounter extends Counter { - private int count; - - public GraveyardCounter(BufferedImage image, Plugin plugin) + GraveyardCounter(BufferedImage image, Plugin plugin) { - super(image, plugin, "0"); - } - - public void setCount(int count) - { - this.count = count; - this.setText(String.valueOf(count)); + super(image, plugin, 0); } @Override public Color getTextColor() { + int count = getCount(); if (count >= GraveyardRoom.MIN_SCORE) { return Color.GREEN; diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/nightmarezone/AbsorptionCounter.java b/runelite-client/src/main/java/net/runelite/client/plugins/nightmarezone/AbsorptionCounter.java index 05d1498312..4671edde12 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/nightmarezone/AbsorptionCounter.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/nightmarezone/AbsorptionCounter.java @@ -26,43 +26,27 @@ package net.runelite.client.plugins.nightmarezone; import java.awt.Color; import java.awt.image.BufferedImage; -import lombok.Getter; import lombok.Setter; import net.runelite.client.plugins.Plugin; import net.runelite.client.ui.overlay.infobox.Counter; +@Setter public class AbsorptionCounter extends Counter { - private int absorption; - - @Getter - @Setter private int threshold; - - @Getter - @Setter private Color aboveThresholdColor = Color.GREEN; - - @Getter - @Setter private Color belowThresholdColor = Color.RED; - public AbsorptionCounter(BufferedImage image, Plugin plugin, int absorption, int threshold) + AbsorptionCounter(BufferedImage image, Plugin plugin, int absorption, int threshold) { - super(image, plugin, ""); + super(image, plugin, absorption); this.threshold = threshold; - setAbsorption(absorption); - } - - public void setAbsorption(int absorption) - { - this.absorption = absorption; - setText(String.valueOf(absorption)); } @Override public Color getTextColor() { + int absorption = getCount(); if (absorption >= threshold) { return aboveThresholdColor; @@ -76,6 +60,7 @@ public class AbsorptionCounter extends Counter @Override public String getTooltip() { + int absorption = getCount(); return "Absorption: " + absorption; } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/nightmarezone/NightmareZoneOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/nightmarezone/NightmareZoneOverlay.java index e2a123b1af..71ae196d3e 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/nightmarezone/NightmareZoneOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/nightmarezone/NightmareZoneOverlay.java @@ -129,7 +129,7 @@ class NightmareZoneOverlay extends Overlay } else { - absorptionCounter.setAbsorption(absorptionPoints); + absorptionCounter.setCount(absorptionPoints); } } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsOverlay.java index af9431038e..f111a6f94f 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsOverlay.java @@ -29,6 +29,7 @@ import java.awt.Graphics2D; import java.awt.Polygon; import javax.inject.Inject; import net.runelite.api.Client; +import net.runelite.api.DecorativeObject; import net.runelite.api.GameObject; import net.runelite.api.TileObject; import net.runelite.client.ui.overlay.Overlay; @@ -65,22 +66,31 @@ class ObjectIndicatorsOverlay extends Overlay } final Polygon polygon; + Polygon polygon2 = null; if (object instanceof GameObject) { polygon = ((GameObject) object).getConvexHull(); } + else if (object instanceof DecorativeObject) + { + polygon = ((DecorativeObject) object).getConvexHull(); + polygon2 = ((DecorativeObject) object).getConvexHull2(); + } else { polygon = object.getCanvasTilePoly(); } - if (polygon == null) + if (polygon != null) { - continue; + OverlayUtil.renderPolygon(graphics, polygon, config.markerColor()); } - OverlayUtil.renderPolygon(graphics, polygon, config.markerColor()); + if (polygon2 != null) + { + OverlayUtil.renderPolygon(graphics, polygon2, config.markerColor()); + } } return null; diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsPlugin.java index a352d728db..05fd84d0d8 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/objectindicators/ObjectIndicatorsPlugin.java @@ -43,6 +43,7 @@ import lombok.Getter; import lombok.extern.slf4j.Slf4j; import net.runelite.api.Client; import static net.runelite.api.Constants.REGION_SIZE; +import net.runelite.api.DecorativeObject; import net.runelite.api.GameObject; import net.runelite.api.GameState; import net.runelite.api.MenuAction; @@ -58,6 +59,8 @@ import net.runelite.api.events.GameObjectSpawned; import net.runelite.api.events.GameStateChanged; import net.runelite.api.events.MenuEntryAdded; import net.runelite.api.events.MenuOptionClicked; +import net.runelite.api.events.DecorativeObjectSpawned; +import net.runelite.api.events.DecorativeObjectDespawned; import net.runelite.client.config.ConfigManager; import net.runelite.client.eventbus.Subscribe; import net.runelite.client.input.KeyListener; @@ -158,26 +161,15 @@ public class ObjectIndicatorsPlugin extends Plugin implements KeyListener @Subscribe public void onGameObjectSpawned(GameObjectSpawned event) { - final WorldPoint worldPoint = WorldPoint.fromLocalInstance(client, event.getGameObject().getLocalLocation()); - final Set objectPoints = points.get(worldPoint.getRegionID()); + final GameObject eventObject = event.getGameObject(); + checkObjectPoints(eventObject); + } - if (objectPoints == null) - { - return; - } - - for (ObjectPoint objectPoint : objectPoints) - { - if ((worldPoint.getX() & (REGION_SIZE - 1)) == objectPoint.getRegionX() - && (worldPoint.getY() & (REGION_SIZE - 1)) == objectPoint.getRegionY()) - { - if (objectPoint.getName().equals(client.getObjectDefinition(event.getGameObject().getId()).getName())) - { - objects.add(event.getGameObject()); - break; - } - } - } + @Subscribe + public void onDecorativeObjectSpawned(DecorativeObjectSpawned event) + { + final DecorativeObject eventObject = event.getDecorativeObject(); + checkObjectPoints(eventObject); } @Subscribe @@ -186,6 +178,12 @@ public class ObjectIndicatorsPlugin extends Plugin implements KeyListener objects.remove(event.getGameObject()); } + @Subscribe + public void onDecorativeObjectDespawned(DecorativeObjectDespawned event) + { + objects.remove(event.getDecorativeObject()); + } + @Subscribe public void onGameStateChanged(GameStateChanged gameStateChanged) { @@ -263,6 +261,30 @@ public class ObjectIndicatorsPlugin extends Plugin implements KeyListener markObject(name, object); } + private void checkObjectPoints(TileObject object) + { + final WorldPoint worldPoint = WorldPoint.fromLocalInstance(client, object.getLocalLocation()); + final Set objectPoints = points.get(worldPoint.getRegionID()); + + if (objectPoints == null) + { + return; + } + + for (ObjectPoint objectPoint : objectPoints) + { + if ((worldPoint.getX() & (REGION_SIZE - 1)) == objectPoint.getRegionX() + && (worldPoint.getY() & (REGION_SIZE - 1)) == objectPoint.getRegionY()) + { + if (objectPoint.getName().equals(client.getObjectDefinition(object.getId()).getName())) + { + objects.add(object); + break; + } + } + } + } + private TileObject findTileObject(Tile tile, int id) { if (tile == null) @@ -271,6 +293,12 @@ public class ObjectIndicatorsPlugin extends Plugin implements KeyListener } final GameObject[] tileGameObjects = tile.getGameObjects(); + final DecorativeObject tileDecorativeObject = tile.getDecorativeObject(); + + if (tileDecorativeObject != null && tileDecorativeObject.getId() == id) + { + return tileDecorativeObject; + } for (GameObject object : tileGameObjects) { diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/party/PartyPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/party/PartyPlugin.java index 73abcdb172..0e910ff338 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/party/PartyPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/party/PartyPlugin.java @@ -191,7 +191,7 @@ public class PartyPlugin extends Plugin implements KeyListener .build(); chatMessageManager.queue(QueuedMessage.builder() - .type(ChatMessageType.GAME) + .type(ChatMessageType.CLANCHAT_INFO) .runeLiteFormattedMessage(leaveMessage) .build()); } @@ -381,7 +381,7 @@ public class PartyPlugin extends Plugin implements KeyListener .build(); chatMessageManager.queue(QueuedMessage.builder() - .type(ChatMessageType.GAME) + .type(ChatMessageType.CLANCHAT_INFO) .runeLiteFormattedMessage(joinMessage) .build()); @@ -430,7 +430,7 @@ public class PartyPlugin extends Plugin implements KeyListener .build(); chatMessageManager.queue(QueuedMessage.builder() - .type(ChatMessageType.GAME) + .type(ChatMessageType.CLANCHAT_INFO) .runeLiteFormattedMessage(joinMessage) .build()); } @@ -519,7 +519,7 @@ public class PartyPlugin extends Plugin implements KeyListener .build(); chatMessageManager.queue(QueuedMessage.builder() - .type(ChatMessageType.GAME) + .type(ChatMessageType.CLANCHAT_INFO) .runeLiteFormattedMessage(helpMessage) .build()); } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/party/PartyStatsOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/party/PartyStatsOverlay.java index 2b9e1820f3..693b23cb89 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/party/PartyStatsOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/party/PartyStatsOverlay.java @@ -27,6 +27,8 @@ package net.runelite.client.plugins.party; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics2D; +import java.awt.Point; +import java.awt.Rectangle; import java.util.Map; import java.util.UUID; import javax.inject.Inject; @@ -34,6 +36,7 @@ import net.runelite.api.MenuAction; import net.runelite.client.plugins.party.data.PartyData; import net.runelite.client.ui.overlay.Overlay; import net.runelite.client.ui.overlay.OverlayMenuEntry; +import net.runelite.client.ui.overlay.components.ComponentConstants; import net.runelite.client.ui.overlay.components.PanelComponent; import net.runelite.client.ui.overlay.components.ProgressBarComponent; import net.runelite.client.ui.overlay.components.TitleComponent; @@ -58,6 +61,8 @@ public class PartyStatsOverlay extends Overlay this.plugin = plugin; this.party = party; this.config = config; + body.setBorder(new Rectangle()); + body.setGap(new Point(0, ComponentConstants.STANDARD_BORDER / 2)); getMenuEntries().add(new OverlayMenuEntry(MenuAction.RUNELITE_OVERLAY, "Leave", "Party")); } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/party/messages/LocationUpdate.java b/runelite-client/src/main/java/net/runelite/client/plugins/party/messages/LocationUpdate.java index 0ff6569379..f5bf7131ce 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/party/messages/LocationUpdate.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/party/messages/LocationUpdate.java @@ -24,11 +24,13 @@ */ package net.runelite.client.plugins.party.messages; +import lombok.EqualsAndHashCode; import lombok.Value; import net.runelite.api.coords.WorldPoint; import net.runelite.http.api.ws.messages.party.PartyMemberMessage; @Value +@EqualsAndHashCode(callSuper = true) public class LocationUpdate extends PartyMemberMessage { private final WorldPoint worldPoint; diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/party/messages/TilePing.java b/runelite-client/src/main/java/net/runelite/client/plugins/party/messages/TilePing.java index 1378403333..fb4f812a81 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/party/messages/TilePing.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/party/messages/TilePing.java @@ -24,11 +24,13 @@ */ package net.runelite.client.plugins.party.messages; +import lombok.EqualsAndHashCode; import lombok.Value; import net.runelite.api.coords.WorldPoint; import net.runelite.http.api.ws.messages.party.PartyMemberMessage; @Value +@EqualsAndHashCode(callSuper = true) public class TilePing extends PartyMemberMessage { private final WorldPoint point; diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/pestcontrol/PestControlPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/pestcontrol/PestControlPlugin.java index 360e56f40d..7e711e66a0 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/pestcontrol/PestControlPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/pestcontrol/PestControlPlugin.java @@ -92,7 +92,8 @@ public class PestControlPlugin extends Plugin @Subscribe public void onGameStateChanged(GameStateChanged event) { - if (event.getGameState() == GameState.LOADING) + GameState gameState = event.getGameState(); + if (gameState == GameState.CONNECTION_LOST || gameState == GameState.LOGIN_SCREEN || gameState == GameState.HOPPING) { spinners.clear(); } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/poison/PoisonConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/poison/PoisonConfig.java index 2595009205..6c723d4f71 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/poison/PoisonConfig.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/poison/PoisonConfig.java @@ -42,4 +42,14 @@ public interface PoisonConfig extends Config { return false; } + + @ConfigItem( + keyName = "changeHealthIcon", + name = "Change HP Orb Icon", + description = "Configures whether the hp orb icon should change color to match poison/disease" + ) + default boolean changeHealthIcon() + { + return true; + } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/poison/PoisonPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/poison/PoisonPlugin.java index 68b9889a2a..84a4c5e3d6 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/poison/PoisonPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/poison/PoisonPlugin.java @@ -35,12 +35,13 @@ import java.time.Instant; import java.time.temporal.ChronoUnit; import javax.inject.Inject; import lombok.Getter; -import lombok.extern.slf4j.Slf4j; import net.runelite.api.Client; +import net.runelite.api.GameState; import net.runelite.api.SpriteID; import net.runelite.api.VarPlayer; import net.runelite.api.events.ConfigChanged; import net.runelite.api.events.VarbitChanged; +import net.runelite.client.callback.ClientThread; import net.runelite.client.config.ConfigManager; import net.runelite.client.eventbus.Subscribe; import net.runelite.client.game.SpriteManager; @@ -50,22 +51,36 @@ import net.runelite.client.ui.FontManager; import net.runelite.client.ui.overlay.OverlayManager; import net.runelite.client.ui.overlay.infobox.InfoBoxManager; import net.runelite.client.util.ColorUtil; +import net.runelite.client.util.ImageUtil; @PluginDescriptor( name = "Poison", description = "Tracks current damage values for Poison and Venom", - tags = {"combat", "poison", "venom"} + tags = {"combat", "poison", "venom", "heart", "hp"} ) -@Slf4j public class PoisonPlugin extends Plugin { static final int POISON_TICK_MILLIS = 18200; private static final int VENOM_THRESHOLD = 1000000; private static final int VENOM_MAXIUMUM_DAMAGE = 20; + private static final BufferedImage HEART_DISEASE; + private static final BufferedImage HEART_POISON; + private static final BufferedImage HEART_VENOM; + + static + { + HEART_DISEASE = ImageUtil.resizeCanvas(ImageUtil.getResourceStreamFromClass(PoisonPlugin.class, "1067-DISEASE.png"), 26, 26); + HEART_POISON = ImageUtil.resizeCanvas(ImageUtil.getResourceStreamFromClass(PoisonPlugin.class, "1067-POISON.png"), 26, 26); + HEART_VENOM = ImageUtil.resizeCanvas(ImageUtil.getResourceStreamFromClass(PoisonPlugin.class, "1067-VENOM.png"), 26, 26); + } + @Inject private Client client; + @Inject + private ClientThread clientThread; + @Inject private PoisonOverlay poisonOverlay; @@ -88,6 +103,8 @@ public class PoisonPlugin extends Plugin private Instant poisonNaturalCure; private Instant nextPoisonTick; private int lastValue = -1; + private int lastDiseaseValue = -1; + private BufferedImage heart; @Provides PoisonConfig getConfig(ConfigManager configManager) @@ -99,6 +116,11 @@ public class PoisonPlugin extends Plugin protected void startUp() throws Exception { overlayManager.add(poisonOverlay); + + if (client.getGameState() == GameState.LOGGED_IN) + { + clientThread.invoke(this::checkHealthIcon); + } } @Override @@ -117,64 +139,87 @@ public class PoisonPlugin extends Plugin poisonNaturalCure = null; nextPoisonTick = null; lastValue = -1; + lastDiseaseValue = -1; + clientThread.invoke(this::resetHealthIcon); } @Subscribe public void onVarbitChanged(VarbitChanged event) { final int poisonValue = client.getVar(VarPlayer.POISON); - if (poisonValue == lastValue) + if (poisonValue != lastValue) { - return; - } + lastValue = poisonValue; + nextPoisonTick = Instant.now().plus(Duration.of(POISON_TICK_MILLIS, ChronoUnit.MILLIS)); - lastValue = poisonValue; - nextPoisonTick = Instant.now().plus(Duration.of(POISON_TICK_MILLIS, ChronoUnit.MILLIS)); + final int damage = nextDamage(poisonValue); + this.lastDamage = damage; - final int damage = nextDamage(poisonValue); - this.lastDamage = damage; + envenomed = poisonValue >= VENOM_THRESHOLD; - envenomed = poisonValue >= VENOM_THRESHOLD; - - if (poisonValue < VENOM_THRESHOLD) - { - poisonNaturalCure = Instant.now().plus(Duration.of(POISON_TICK_MILLIS * poisonValue, ChronoUnit.MILLIS)); - } - else - { - poisonNaturalCure = null; - } - - if (config.showInfoboxes()) - { - if (infobox != null) + if (poisonValue < VENOM_THRESHOLD) { - infoBoxManager.removeInfoBox(infobox); - infobox = null; + poisonNaturalCure = Instant.now().plus(Duration.of(POISON_TICK_MILLIS * poisonValue, ChronoUnit.MILLIS)); + } + else + { + poisonNaturalCure = null; } - if (damage > 0) + if (config.showInfoboxes()) { - final BufferedImage image = getSplat(envenomed ? SpriteID.HITSPLAT_DARK_GREEN_VENOM : SpriteID.HITSPLAT_GREEN_POISON, damage); - - if (image != null) + if (infobox != null) { - infobox = new PoisonInfobox(image, this); - infoBoxManager.addInfoBox(infobox); + infoBoxManager.removeInfoBox(infobox); + infobox = null; + } + + if (damage > 0) + { + final BufferedImage image = getSplat(envenomed ? SpriteID.HITSPLAT_DARK_GREEN_VENOM : SpriteID.HITSPLAT_GREEN_POISON, damage); + + if (image != null) + { + infobox = new PoisonInfobox(image, this); + infoBoxManager.addInfoBox(infobox); + } } } + + checkHealthIcon(); + } + + final int diseaseValue = client.getVar(VarPlayer.DISEASE_VALUE); + if (diseaseValue != lastDiseaseValue) + { + lastDiseaseValue = diseaseValue; + checkHealthIcon(); } } @Subscribe public void onConfigChanged(ConfigChanged event) { - if (event.getGroup().equals(PoisonConfig.GROUP) && !config.showInfoboxes() && infobox != null) + if (!event.getGroup().equals(PoisonConfig.GROUP)) + { + return; + } + + if (!config.showInfoboxes() && infobox != null) { infoBoxManager.removeInfoBox(infobox); infobox = null; } + + if (config.changeHealthIcon()) + { + clientThread.invoke(this::checkHealthIcon); + } + else + { + clientThread.invoke(this::resetHealthIcon); + } } private static int nextDamage(int poisonValue) @@ -250,4 +295,53 @@ public class PoisonPlugin extends Plugin return line1 + line2; } + + private void checkHealthIcon() + { + if (!config.changeHealthIcon()) + { + return; + } + + final BufferedImage newHeart; + final int poison = client.getVar(VarPlayer.IS_POISONED); + + if (poison >= VENOM_THRESHOLD) + { + newHeart = HEART_VENOM; + } + else if (poison > 0) + { + newHeart = HEART_POISON; + } + else if (client.getVar(VarPlayer.DISEASE_VALUE) > 0) + { + newHeart = HEART_DISEASE; + } + else + { + resetHealthIcon(); + return; + } + + // Only update sprites when the heart icon actually changes + if (newHeart != heart) + { + heart = newHeart; + client.getWidgetSpriteCache().reset(); + client.getSpriteOverrides().put(SpriteID.MINIMAP_ORB_HITPOINTS_ICON, ImageUtil.getImageSpritePixels(heart, client)); + } + } + + private void resetHealthIcon() + { + if (heart == null) + { + return; + } + + client.getWidgetSpriteCache().reset(); + client.getSpriteOverrides().remove(SpriteID.MINIMAP_ORB_HITPOINTS_ICON); + heart = null; + } } \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/prayer/PrayerCounter.java b/runelite-client/src/main/java/net/runelite/client/plugins/prayer/PrayerCounter.java index 546c9f98a8..c510dab82e 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/prayer/PrayerCounter.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/prayer/PrayerCounter.java @@ -24,25 +24,32 @@ */ package net.runelite.client.plugins.prayer; +import java.awt.Color; import lombok.Getter; import net.runelite.client.plugins.Plugin; -import net.runelite.client.ui.overlay.infobox.Counter; +import net.runelite.client.ui.overlay.infobox.InfoBox; -class PrayerCounter extends Counter +class PrayerCounter extends InfoBox { @Getter private final PrayerType prayerType; PrayerCounter(Plugin plugin, PrayerType prayerType) { - super(null, plugin, ""); + super(null, plugin); this.prayerType = prayerType; } @Override - public String toString() + public String getText() { - return "Counter{" + "prayer=" + prayerType.getName() + '}'; + return null; + } + + @Override + public Color getTextColor() + { + return null; } @Override diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/raids/RaidsPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/raids/RaidsPlugin.java index 72a386f5c7..d0c0d7c16c 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/raids/RaidsPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/raids/RaidsPlugin.java @@ -236,7 +236,7 @@ public class RaidsPlugin extends Plugin { if (timer != null) { - timer.timeFloor(); + timer.timeOlm(); timer.setStopped(true); } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/raids/RaidsTimer.java b/runelite-client/src/main/java/net/runelite/client/plugins/raids/RaidsTimer.java index 8b40b09e10..8df3087054 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/raids/RaidsTimer.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/raids/RaidsTimer.java @@ -41,6 +41,7 @@ public class RaidsTimer extends InfoBox private LocalTime time; private LocalTime firstFloorTime; private LocalTime secondFloorTime; + private LocalTime thirdFloorTime; private LocalTime olmTime; @Setter @@ -66,14 +67,20 @@ public class RaidsTimer extends InfoBox { secondFloorTime = LocalTime.ofSecondOfDay(elapsed.getSeconds()); } - else if (olmTime == null) + else if (thirdFloorTime == null) { - olmTime = LocalTime.ofSecondOfDay(elapsed.getSeconds()); + thirdFloorTime = LocalTime.ofSecondOfDay(elapsed.getSeconds()); } floorTime = Instant.now(); } + public void timeOlm() + { + Duration elapsed = Duration.between(floorTime, Instant.now()); + olmTime = LocalTime.ofSecondOfDay(elapsed.getSeconds()); + } + @Override public String getText() { @@ -126,6 +133,12 @@ public class RaidsTimer extends InfoBox builder.append(secondFloorTime.format(DateTimeFormatter.ofPattern("mm:ss"))); } + if (thirdFloorTime != null) + { + builder.append("
Third floor: "); + builder.append(thirdFloorTime.format(DateTimeFormatter.ofPattern("mm:ss"))); + } + if (olmTime != null) { builder.append("
Olm: "); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/runecraft/RunecraftPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/runecraft/RunecraftPlugin.java index 14b53f9995..4662b95d5a 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/runecraft/RunecraftPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/runecraft/RunecraftPlugin.java @@ -229,10 +229,17 @@ public class RunecraftPlugin extends Plugin @Subscribe public void onGameStateChanged(GameStateChanged event) { - if (event.getGameState() == GameState.LOADING) + GameState gameState = event.getGameState(); + switch (gameState) { - abyssObjects.clear(); - darkMage = null; + case LOADING: + abyssObjects.clear(); + break; + case CONNECTION_LOST: + case HOPPING: + case LOGIN_SCREEN: + darkMage = null; + break; } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/screenmarkers/ScreenMarkerRenderable.java b/runelite-client/src/main/java/net/runelite/client/plugins/screenmarkers/ScreenMarkerRenderable.java index 0d44e01345..95b2da1207 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/screenmarkers/ScreenMarkerRenderable.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/screenmarkers/ScreenMarkerRenderable.java @@ -28,6 +28,7 @@ import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics2D; import java.awt.Point; +import java.awt.Rectangle; import java.awt.Stroke; import lombok.AccessLevel; import lombok.Getter; @@ -55,6 +56,9 @@ public class ScreenMarkerRenderable implements LayoutableRenderableEntity @Setter(AccessLevel.PACKAGE) private Stroke stroke; + @Getter + private final Rectangle bounds = new Rectangle(); + @Override public Dimension render(Graphics2D graphics) { @@ -72,6 +76,7 @@ public class ScreenMarkerRenderable implements LayoutableRenderableEntity graphics.setColor(color); graphics.setStroke(stroke); graphics.drawRect(offset, offset, width - thickness, height - thickness); + bounds.setSize(preferredSize); return preferredSize; } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/screenshot/ScreenshotPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/screenshot/ScreenshotPlugin.java index e4c6fd2ec9..ee78f3cc22 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/screenshot/ScreenshotPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/screenshot/ScreenshotPlugin.java @@ -145,6 +145,8 @@ public class ScreenshotPlugin extends Plugin private Integer chambersOfXericNumber; + private Integer chambersOfXericChallengeNumber; + private Integer theatreOfBloodNumber; private boolean shouldTakeScreenshot; @@ -347,6 +349,16 @@ public class ScreenshotPlugin extends Plugin } } + if (chatMessage.startsWith("Your completed Chambers of Xeric Challenge Mode count is:")) + { + Matcher m = NUMBER_PATTERN.matcher(Text.removeTags(chatMessage)); + if (m.find()) + { + chambersOfXericChallengeNumber = Integer.valueOf(m.group()); + return; + } + } + if (chatMessage.startsWith("Your completed Theatre of Blood count is:")) { Matcher m = NUMBER_PATTERN.matcher(Text.removeTags(chatMessage)); @@ -453,14 +465,22 @@ public class ScreenshotPlugin extends Plugin } case CHAMBERS_OF_XERIC_REWARD_GROUP_ID: { - if (chambersOfXericNumber == null) + if (chambersOfXericNumber != null) + { + fileName = "Chambers of Xeric(" + chambersOfXericNumber + ")"; + chambersOfXericNumber = null; + break; + } + else if (chambersOfXericChallengeNumber != null) + { + fileName = "Chambers of Xeric Challenge Mode(" + chambersOfXericChallengeNumber + ")"; + chambersOfXericChallengeNumber = null; + break; + } + else { return; } - - fileName = "Chambers of Xeric(" + chambersOfXericNumber + ")"; - chambersOfXericNumber = null; - break; } case THEATRE_OF_BLOOD_REWARD_GROUP_ID: { @@ -720,6 +740,12 @@ public class ScreenshotPlugin extends Plugin return chambersOfXericNumber; } + @VisibleForTesting + int getChambersOfXericChallengeNumber() + { + return chambersOfXericChallengeNumber; + } + @VisibleForTesting int gettheatreOfBloodNumber() { diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/slayer/SlayerPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/slayer/SlayerPlugin.java index d257fbd995..96a585e605 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/slayer/SlayerPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/slayer/SlayerPlugin.java @@ -40,7 +40,6 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.inject.Inject; -import javax.swing.SwingUtilities; import joptsimple.internal.Strings; import lombok.AccessLevel; import lombok.Getter; @@ -48,7 +47,6 @@ import lombok.Setter; import lombok.extern.slf4j.Slf4j; import net.runelite.api.ChatMessageType; import net.runelite.api.Client; -import net.runelite.api.GameState; import net.runelite.api.ItemID; import net.runelite.api.MessageNode; import net.runelite.api.NPC; @@ -623,7 +621,7 @@ public class SlayerPlugin extends Plugin // add and update counter, set timer addCounter(); - counter.setText(String.valueOf(currentTask.getAmount())); + counter.setCount(currentTask.getAmount()); infoTimer = Instant.now(); } @@ -696,7 +694,7 @@ public class SlayerPlugin extends Plugin private void setTask(String name, int amt, int initAmt, boolean isNewAssignment) { - setTask(name, amt, initAmt, isNewAssignment,null); + setTask(name, amt, initAmt, isNewAssignment, null); } private void setTask(String name, int amt, int initAmt, boolean isNewAssignment, String location) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/slayer/SlayerTaskPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/slayer/SlayerTaskPanel.java index 7d73343bf7..1c3de05230 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/slayer/SlayerTaskPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/slayer/SlayerTaskPanel.java @@ -6,7 +6,6 @@ import java.awt.GridLayout; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.image.BufferedImage; -import java.text.DecimalFormat; import java.util.ArrayList; import java.util.List; import javax.swing.Box; @@ -15,7 +14,6 @@ import javax.swing.ImageIcon; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.SwingUtilities; -import javax.swing.border.Border; import javax.swing.border.EmptyBorder; import net.runelite.client.ui.ColorScheme; import net.runelite.client.ui.FontManager; @@ -104,7 +102,8 @@ public class SlayerTaskPanel extends PluginPanel playBtn.setIcon(PLAY); playBtn.setToolTipText("Resume the current slayer task"); - playBtn.addMouseListener(new MouseAdapter() { + playBtn.addMouseListener(new MouseAdapter() + { @Override public void mousePressed(MouseEvent mouseEvent) { @@ -139,7 +138,8 @@ public class SlayerTaskPanel extends PluginPanel pauseBtn.setIcon(PAUSE); pauseBtn.setToolTipText("Pause the current slayer task"); - pauseBtn.addMouseListener(new MouseAdapter() { + pauseBtn.addMouseListener(new MouseAdapter() + { @Override public void mousePressed(MouseEvent mouseEvent) { @@ -300,7 +300,8 @@ public class SlayerTaskPanel extends PluginPanel if (tasks.isEmpty() || isNewAssignment) { // new task so append it to the front of the list - SwingUtilities.invokeLater(() -> { + SwingUtilities.invokeLater(() -> + { TaskBox newBox = buildBox(slayerPlugin, tasksContainer, newData); newBox.update(true, newData.isPaused(), newData); }); @@ -321,7 +322,8 @@ public class SlayerTaskPanel extends PluginPanel // so this previous task is invalid so delete it then add in the new actually // correct task - SwingUtilities.invokeLater(() -> { + SwingUtilities.invokeLater(() -> + { tasksContainer.remove(tasks.get(0)); tasks.remove(0); TaskBox newBox = buildBox(slayerPlugin, tasksContainer, newData); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/slayer/TargetWeaknessOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/slayer/TargetWeaknessOverlay.java index 1b8973fbce..4f60949a89 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/slayer/TargetWeaknessOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/slayer/TargetWeaknessOverlay.java @@ -59,7 +59,7 @@ class TargetWeaknessOverlay extends Overlay this.itemManager = itemManager; this.npcManager = npcManager; setPosition(OverlayPosition.DYNAMIC); - setLayer(OverlayLayer.ABOVE_SCENE); + setLayer(OverlayLayer.UNDER_WIDGETS); } @Override diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/slayer/TaskBox.java b/runelite-client/src/main/java/net/runelite/client/plugins/slayer/TaskBox.java index 79d44b2d53..443dccb453 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/slayer/TaskBox.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/slayer/TaskBox.java @@ -14,7 +14,6 @@ import javax.swing.SwingUtilities; import javax.swing.border.EmptyBorder; import lombok.AccessLevel; import lombok.Getter; -import net.runelite.client.game.AsyncBufferedImage; import net.runelite.client.ui.ColorScheme; import net.runelite.client.ui.DynamicGridLayout; import net.runelite.client.ui.FontManager; @@ -78,7 +77,8 @@ public class TaskBox extends JPanel container.setLayout(new BorderLayout()); container.setBackground(ColorScheme.DARKER_GRAY_COLOR); - SwingUtilities.invokeLater(() -> { + SwingUtilities.invokeLater(() -> + { BufferedImage taskImg = slayerPlugin.getImageForTask(Task.getTask(taskData.getTaskName())); JLabel taskIcon = new JLabel(new ImageIcon(taskImg)); taskIcon.setHorizontalAlignment(SwingConstants.CENTER); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/slayer/TaskCounter.java b/runelite-client/src/main/java/net/runelite/client/plugins/slayer/TaskCounter.java index f4ac0045b9..0d4274c063 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/slayer/TaskCounter.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/slayer/TaskCounter.java @@ -29,10 +29,10 @@ import net.runelite.client.ui.overlay.infobox.Counter; import java.awt.image.BufferedImage; -public class TaskCounter extends Counter +class TaskCounter extends Counter { - public TaskCounter(BufferedImage img, Plugin plugin, int amount) + TaskCounter(BufferedImage img, Plugin plugin, int amount) { - super(img, plugin, String.valueOf(amount)); + super(img, plugin, amount); } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/slayer/TaskData.java b/runelite-client/src/main/java/net/runelite/client/plugins/slayer/TaskData.java index 17e43d8be3..0e3784e687 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/slayer/TaskData.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/slayer/TaskData.java @@ -6,7 +6,7 @@ import lombok.Data; @Data @AllArgsConstructor -@Builder(toBuilder=true) +@Builder(toBuilder = true) public class TaskData { private long elapsedTime; diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/specialcounter/SpecialCounter.java b/runelite-client/src/main/java/net/runelite/client/plugins/specialcounter/SpecialCounter.java index 083ef5bba6..3c7b0f2135 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/specialcounter/SpecialCounter.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/specialcounter/SpecialCounter.java @@ -29,30 +29,24 @@ import net.runelite.client.ui.overlay.infobox.Counter; class SpecialCounter extends Counter { - private int hitValue; private SpecialWeapon weapon; SpecialCounter(BufferedImage image, SpecialCounterPlugin plugin, int hitValue, SpecialWeapon weapon) { - super(image, plugin, null); + super(image, plugin, hitValue); this.weapon = weapon; - this.hitValue = hitValue; } void addHits(double hit) { - this.hitValue += hit; - } - - @Override - public String getText() - { - return Integer.toString(hitValue); + int count = getCount(); + setCount(count + (int) hit); } @Override public String getTooltip() { + int hitValue = getCount(); if (!weapon.isDamage()) { if (hitValue == 1) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/statusbars/StatusBarsOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/statusbars/StatusBarsOverlay.java index 0deb55c2ab..ada42169ad 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/statusbars/StatusBarsOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/statusbars/StatusBarsOverlay.java @@ -24,8 +24,6 @@ */ package net.runelite.client.plugins.statusbars; -import com.google.common.base.Strings; -import com.google.common.primitives.Ints; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics2D; @@ -196,28 +194,16 @@ class StatusBarsOverlay extends Overlay for (final StatChange c : statsChanges.getStatChanges()) { - final String strVar = c.getTheoretical(); - - if (Strings.isNullOrEmpty(strVar)) - { - continue; - } - - final Integer value = Ints.tryParse(strVar.startsWith("+") ? strVar.substring(1) : strVar); - - if (value == null) - { - continue; - } + final int theoreticalBoost = c.getTheoretical(); if (c.getStat().getName().equals(Skill.HITPOINTS.getName())) { - foodHealValue = value; + foodHealValue = theoreticalBoost; } if (c.getStat().getName().equals(Skill.PRAYER.getName())) { - prayerHealValue = value; + prayerHealValue = theoreticalBoost; } if (foodHealValue != 0 && prayerHealValue != 0) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/tileindicators/TileIndicatorsConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/tileindicators/TileIndicatorsConfig.java index 9d9cd96e69..9a5e0bad33 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/tileindicators/TileIndicatorsConfig.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/tileindicators/TileIndicatorsConfig.java @@ -54,6 +54,17 @@ public interface TileIndicatorsConfig extends Config return true; } + @Alpha + @ConfigItem( + keyName = "highlightHoveredColor", + name = "Color of current hovered highlighting", + description = "Configures the highlight color of hovered tile" + ) + default Color highlightHoveredColor() + { + return new Color(0, 0, 0, 0); + } + @ConfigItem( keyName = "highlightHoveredTile", name = "Highlight hovered tile", diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/tileindicators/TileIndicatorsOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/tileindicators/TileIndicatorsOverlay.java index f92a76f6de..a9e983656d 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/tileindicators/TileIndicatorsOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/tileindicators/TileIndicatorsOverlay.java @@ -41,7 +41,6 @@ import net.runelite.client.ui.overlay.OverlayUtil; public class TileIndicatorsOverlay extends Overlay { - private static final Color EMPTY = new Color(0, 0, 0, 0); private final Client client; private final TileIndicatorsConfig config; @@ -75,7 +74,7 @@ public class TileIndicatorsOverlay extends Overlay // If we have tile "selected" render it if (client.getSelectedSceneTile() != null) { - renderTile(graphics, client.getSelectedSceneTile().getLocalLocation(), EMPTY); + renderTile(graphics, client.getSelectedSceneTile().getLocalLocation(), config.highlightHoveredColor()); } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/wiki/WikiPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/wiki/WikiPlugin.java new file mode 100644 index 0000000000..44146c4cb5 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/wiki/WikiPlugin.java @@ -0,0 +1,314 @@ +/* + * Copyright (c) 2018 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.client.plugins.wiki; + +import com.google.common.primitives.Ints; +import java.net.URLEncoder; +import java.util.Arrays; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.inject.Inject; +import javax.inject.Provider; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.Client; +import net.runelite.api.MenuAction; +import net.runelite.api.MenuEntry; +import net.runelite.api.NPC; +import net.runelite.api.NPCComposition; +import net.runelite.api.events.MenuEntryAdded; +import net.runelite.api.events.MenuOptionClicked; +import net.runelite.api.events.WidgetLoaded; +import net.runelite.api.widgets.JavaScriptCallback; +import net.runelite.api.widgets.Widget; +import net.runelite.api.widgets.WidgetConfig; +import net.runelite.api.widgets.WidgetID; +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.ItemManager; +import net.runelite.client.game.SpriteManager; +import net.runelite.client.game.chatbox.ChatboxPanelManager; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.util.LinkBrowser; +import net.runelite.client.util.Text; +import okhttp3.HttpUrl; + +@Slf4j +@PluginDescriptor( + name = "Wiki", + description = "Adds a Wiki button that takes you to the OSRS Wiki" +) +public class WikiPlugin extends Plugin +{ + private static final int[] QUESTLIST_WIDGET_IDS = new int[] + { + WidgetInfo.QUESTLIST_FREE_CONTAINER.getId(), + WidgetInfo.QUESTLIST_MEMBERS_CONTAINER.getId(), + WidgetInfo.QUESTLIST_MINIQUEST_CONTAINER.getId(), + }; + + static final String WIKI_BASE = "https://oldschool.runescape.wiki"; + static final HttpUrl WIKI_RSLOOKUP = HttpUrl.parse(WIKI_BASE + "/w/Special:Lookup"); + static final HttpUrl WIKI_API = HttpUrl.parse(WIKI_BASE + "/api.php"); + static final String UTM_SORUCE_KEY = "utm_source"; + static final String UTM_SORUCE_VALUE = "runelite"; + static final String UTM_PARAMS = UTM_SORUCE_KEY + "=" + UTM_SORUCE_VALUE; + + private static final String MENUOP_GUIDE = "Guide"; + private static final String MENUOP_QUICKGUIDE = "Quick Guide"; + private static final String MENUOP_WIKI_SKILL = "Wiki"; + + private static final Pattern SKILL_REGEX = Pattern.compile("([A-Za-z]+) guide"); + + @Inject + private SpriteManager spriteManager; + + @Inject + private ClientThread clientThread; + + @Inject + private Client client; + + @Inject + private ChatboxPanelManager chatboxPanelManager; + + @Inject + private ItemManager itemManager; + + @Inject + private Provider wikiSearchChatboxTextInputProvider; + + private Widget icon; + + private boolean wikiSelected = false; + + @Override + public void startUp() + { + spriteManager.addSpriteOverrides(WikiSprite.values()); + clientThread.invokeLater(this::addWidgets); + } + + @Override + public void shutDown() + { + spriteManager.removeSpriteOverrides(WikiSprite.values()); + clientThread.invokeLater(() -> + { + Widget minimapOrbs = client.getWidget(WidgetInfo.MINIMAP_ORBS); + if (minimapOrbs == null) + { + return; + } + Widget[] children = minimapOrbs.getChildren(); + if (children == null || children.length < 1) + { + return; + } + children[0] = null; + }); + } + + @Subscribe + private void onWidgetLoaded(WidgetLoaded l) + { + if (l.getGroupId() == WidgetID.MINIMAP_GROUP_ID) + { + addWidgets(); + } + } + + private void addWidgets() + { + Widget minimapOrbs = client.getWidget(WidgetInfo.MINIMAP_ORBS); + if (minimapOrbs == null) + { + return; + } + + icon = minimapOrbs.createChild(0, WidgetType.GRAPHIC); + icon.setSpriteId(WikiSprite.WIKI_ICON.getSpriteId()); + icon.setOriginalX(0); + icon.setOriginalY(2); + icon.setXPositionMode(WidgetPositionMode.ABSOLUTE_RIGHT); + icon.setYPositionMode(WidgetPositionMode.ABSOLUTE_BOTTOM); + icon.setOriginalWidth(42); + icon.setOriginalHeight(16); + icon.setTargetVerb("Lookup"); + icon.setName("Wiki"); + icon.setClickMask(WidgetConfig.USE_GROUND_ITEM | WidgetConfig.USE_ITEM | WidgetConfig.USE_NPC); + icon.setNoClickThrough(true); + icon.setOnTargetEnterListener((JavaScriptCallback) ev -> + { + wikiSelected = true; + icon.setSpriteId(WikiSprite.WIKI_SELECTED_ICON.getSpriteId()); + }); + icon.setAction(5, "Search"); // Start at option 5 so the target op is ontop + icon.setOnOpListener((JavaScriptCallback) ev -> + { + switch (ev.getOp()) + { + case 6: + openSearchInput(); + break; + } + }); + // This doesn't always run because we cancel the menuop + icon.setOnTargetLeaveListener((JavaScriptCallback) ev -> onDeselect()); + icon.revalidate(); + } + + private void onDeselect() + { + wikiSelected = false; + icon.setSpriteId(WikiSprite.WIKI_ICON.getSpriteId()); + } + + @Subscribe + private void onMenuOptionClicked(MenuOptionClicked ev) + { + if (wikiSelected) + { + onDeselect(); + client.setSpellSelected(false); + ev.consume(); + + String type; + int id; + String name; + switch (ev.getMenuAction()) + { + case CANCEL: + return; + case ITEM_USE_ON_WIDGET: + case SPELL_CAST_ON_GROUND_ITEM: + { + type = "item"; + id = itemManager.canonicalize(ev.getId()); + name = itemManager.getItemComposition(id).getName(); + break; + } + case SPELL_CAST_ON_NPC: + { + type = "npc"; + NPC npc = client.getCachedNPCs()[ev.getId()]; + NPCComposition nc = npc.getTransformedComposition(); + id = nc.getId(); + name = nc.getName(); + break; + } + default: + log.info("Unknown menu option: {} {} {}", ev, ev.getMenuAction(), ev.getMenuAction() == MenuAction.CANCEL); + return; + } + + HttpUrl url = WIKI_RSLOOKUP.newBuilder() + .addQueryParameter("type", type) + .addQueryParameter("id", "" + id) + .addQueryParameter("name", name) + .addQueryParameter(UTM_SORUCE_KEY, UTM_SORUCE_VALUE) + .build(); + + LinkBrowser.browse(url.toString()); + return; + } + + if (ev.getMenuAction() == MenuAction.RUNELITE) + { + String quickguide = ""; + switch (ev.getMenuOption()) + { + case MENUOP_QUICKGUIDE: + quickguide = "/Quick_guide"; + //fallthrough; + case MENUOP_GUIDE: + ev.consume(); + String quest = Text.removeTags(ev.getMenuTarget()); + LinkBrowser.browse(WIKI_BASE + "/w/" + URLEncoder.encode(quest.replace(' ', '_')) + quickguide + "?" + UTM_PARAMS); + break; + case MENUOP_WIKI_SKILL: + Matcher skillRegex = WikiPlugin.SKILL_REGEX.matcher(Text.removeTags(ev.getMenuTarget())); + + if (skillRegex.find()) + { + LinkBrowser.browse(WIKI_BASE + "/w/" + URLEncoder.encode(skillRegex.group(1)) + "?" + UTM_PARAMS); + } + } + } + } + + private void openSearchInput() + { + wikiSearchChatboxTextInputProvider.get() + .build(); + } + + @Subscribe + public void onMenuEntryAdded(MenuEntryAdded event) + { + int widgetIndex = event.getActionParam0(); + int widgetID = event.getActionParam1(); + MenuEntry[] menuEntries = client.getMenuEntries(); + + if (Ints.contains(QUESTLIST_WIDGET_IDS, widgetID) && "Read Journal:".equals(event.getOption())) + { + menuEntries = Arrays.copyOf(menuEntries, menuEntries.length + 2); + + MenuEntry menuEntry = menuEntries[menuEntries.length - 1] = new MenuEntry(); + menuEntry.setTarget(event.getTarget()); + menuEntry.setOption(MENUOP_GUIDE); + menuEntry.setParam0(widgetIndex); + menuEntry.setParam1(widgetID); + menuEntry.setType(MenuAction.RUNELITE.getId()); + + menuEntry = menuEntries[menuEntries.length - 2] = new MenuEntry(); + menuEntry.setTarget(event.getTarget()); + menuEntry.setOption(MENUOP_QUICKGUIDE); + menuEntry.setParam0(widgetIndex); + menuEntry.setParam1(widgetID); + menuEntry.setType(MenuAction.RUNELITE.getId()); + + client.setMenuEntries(menuEntries); + } + + if ((WidgetInfo.TO_GROUP(widgetID) == WidgetID.SKILLS_GROUP_ID) && event.getOption().startsWith("View")) + { + menuEntries = Arrays.copyOf(menuEntries, menuEntries.length + 1); + + MenuEntry menuEntry = menuEntries[menuEntries.length - 1] = new MenuEntry(); + menuEntry.setTarget(event.getOption().replace("View ", "")); + menuEntry.setOption(MENUOP_WIKI_SKILL); + menuEntry.setParam0(widgetIndex); + menuEntry.setParam1(widgetID); + menuEntry.setIdentifier(event.getIdentifier()); + menuEntry.setType(MenuAction.RUNELITE.getId()); + + client.setMenuEntries(menuEntries); + } + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/wiki/WikiSearchChatboxTextInput.java b/runelite-client/src/main/java/net/runelite/client/plugins/wiki/WikiSearchChatboxTextInput.java new file mode 100644 index 0000000000..cc68aef20b --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/wiki/WikiSearchChatboxTextInput.java @@ -0,0 +1,303 @@ +/* + * Copyright (c) 2018 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.client.plugins.wiki; + +import com.google.common.collect.ImmutableList; +import com.google.common.reflect.TypeToken; +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.google.inject.Inject; +import java.awt.event.KeyEvent; +import java.io.IOException; +import java.net.URLEncoder; +import java.util.List; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import javax.inject.Named; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.widgets.JavaScriptCallback; +import net.runelite.api.widgets.Widget; +import net.runelite.api.widgets.WidgetPositionMode; +import net.runelite.api.widgets.WidgetSizeMode; +import net.runelite.api.widgets.WidgetTextAlignment; +import net.runelite.api.widgets.WidgetType; +import net.runelite.client.callback.ClientThread; +import net.runelite.client.game.chatbox.ChatboxPanelManager; +import net.runelite.client.game.chatbox.ChatboxTextInput; +import net.runelite.client.util.LinkBrowser; +import net.runelite.http.api.RuneLiteAPI; +import okhttp3.Call; +import okhttp3.Callback; +import okhttp3.HttpUrl; +import okhttp3.Request; +import okhttp3.Response; + +@Slf4j +public class WikiSearchChatboxTextInput extends ChatboxTextInput +{ + private static final int LINE_HEIGHT = 20; + private static final int CHATBOX_HEIGHT = 120; + private static final int MAX_NUM_PREDICTIONS = (CHATBOX_HEIGHT / LINE_HEIGHT) - 2; // 1 title, 1 edit + + private static final int PREDICTION_DEBOUNCE_DELAY_MS = 200; + + private final ChatboxPanelManager chatboxPanelManager; + private final Gson gson = new Gson(); + + private Future runningRequest = null; + private List predictions = ImmutableList.of(); + + private int selectedPrediction = -1; + private String offPrediction = null; + + @Inject + public WikiSearchChatboxTextInput(ChatboxPanelManager chatboxPanelManager, ClientThread clientThread, + ScheduledExecutorService scheduledExecutorService, @Named("developerMode") final boolean developerMode) + { + super(chatboxPanelManager, clientThread); + this.chatboxPanelManager = chatboxPanelManager; + + lines(1); + prompt("OSRS Wiki Search"); + onDone(string -> + { + if (string != null && string.length() > 0) + { + search(string); + } + }); + onChanged(searchString -> + { + selectedPrediction = -1; + Future rr = runningRequest; + if (rr != null) + { + rr.cancel(false); + } + if (searchString.length() <= 1) + { + runningRequest = null; + clientThread.invokeLater(() -> + { + predictions = ImmutableList.of(); + update(); + }); + return; + } + runningRequest = scheduledExecutorService.schedule(() -> + { + HttpUrl url = WikiPlugin.WIKI_API.newBuilder() + .addQueryParameter("action", "opensearch") + .addQueryParameter("search", searchString) + .addQueryParameter("redirects", "resolve") + .addQueryParameter("format", "json") + .addQueryParameter("warningsaserror", Boolean.toString(developerMode)) + .build(); + + Request req = new Request.Builder() + .url(url) + .build(); + + RuneLiteAPI.CLIENT.newCall(req).enqueue(new Callback() + { + @Override + public void onFailure(Call call, IOException e) + { + log.warn("error searching wiki", e); + } + + @Override + public void onResponse(Call call, Response response) throws IOException + { + String body = response.body().string(); + try + { + JsonArray jar = new JsonParser().parse(body).getAsJsonArray(); + List apredictions = gson.fromJson(jar.get(1), new TypeToken>() + { + }.getType()); + + if (apredictions.size() > MAX_NUM_PREDICTIONS) + { + apredictions = apredictions.subList(0, MAX_NUM_PREDICTIONS); + } + + final List bpredictions = apredictions; + + clientThread.invokeLater(() -> + { + predictions = bpredictions; + update(); + }); + } + catch (JsonParseException | IllegalStateException | IndexOutOfBoundsException e) + { + log.warn("error parsing wiki response {}", body, e); + } + finally + { + response.close(); + } + } + }); + + runningRequest = null; + }, PREDICTION_DEBOUNCE_DELAY_MS, TimeUnit.MILLISECONDS); + }); + } + + @Override + protected void update() + { + Widget container = chatboxPanelManager.getContainerWidget(); + container.deleteAllChildren(); + + Widget promptWidget = container.createChild(-1, WidgetType.TEXT); + promptWidget.setText(getPrompt()); + promptWidget.setTextColor(0x800000); + promptWidget.setFontId(getFontID()); + promptWidget.setXPositionMode(WidgetPositionMode.ABSOLUTE_CENTER); + promptWidget.setOriginalX(0); + promptWidget.setYPositionMode(WidgetPositionMode.ABSOLUTE_TOP); + promptWidget.setOriginalY(5); + promptWidget.setOriginalHeight(LINE_HEIGHT); + promptWidget.setXTextAlignment(WidgetTextAlignment.CENTER); + promptWidget.setYTextAlignment(WidgetTextAlignment.CENTER); + promptWidget.setWidthMode(WidgetSizeMode.MINUS); + promptWidget.revalidate(); + + buildEdit(0, 5 + LINE_HEIGHT, container.getWidth(), LINE_HEIGHT); + + Widget separator = container.createChild(-1, WidgetType.LINE); + separator.setXPositionMode(WidgetPositionMode.ABSOLUTE_CENTER); + separator.setOriginalX(0); + separator.setYPositionMode(WidgetPositionMode.ABSOLUTE_TOP); + separator.setOriginalY(4 + (LINE_HEIGHT * 2)); + separator.setOriginalHeight(0); + separator.setOriginalWidth(16); + separator.setWidthMode(WidgetSizeMode.MINUS); + separator.revalidate(); + + for (int i = 0; i < predictions.size(); i++) + { + String pred = predictions.get(i); + int y = 6 + (LINE_HEIGHT * (2 + i)); + + Widget bg = container.createChild(-1, WidgetType.RECTANGLE); + bg.setTextColor(0x4444DD); + bg.setFilled(true); + bg.setXPositionMode(WidgetPositionMode.ABSOLUTE_CENTER); + bg.setOriginalX(1); + bg.setYPositionMode(WidgetPositionMode.ABSOLUTE_TOP); + bg.setOriginalY(y); + bg.setOriginalHeight(LINE_HEIGHT); + bg.setOriginalWidth(16); + bg.setWidthMode(WidgetSizeMode.MINUS); + bg.revalidate(); + bg.setName("" + pred); + bg.setAction(0, "Open"); + bg.setHasListener(true); + bg.setOnOpListener((JavaScriptCallback) ev -> search(pred)); + + Widget text = container.createChild(-1, WidgetType.TEXT); + text.setText(pred); + text.setFontId(getFontID()); + text.setXPositionMode(WidgetPositionMode.ABSOLUTE_CENTER); + text.setOriginalX(0); + text.setYPositionMode(WidgetPositionMode.ABSOLUTE_TOP); + text.setOriginalY(y); + text.setOriginalHeight(LINE_HEIGHT); + text.setXTextAlignment(WidgetTextAlignment.CENTER); + text.setYTextAlignment(WidgetTextAlignment.CENTER); + text.setWidthMode(WidgetSizeMode.MINUS); + text.revalidate(); + + if (i == selectedPrediction) + { + text.setTextColor(0xFFFFFF); + } + else + { + bg.setOpacity(255); + text.setTextColor(0x000000); + bg.setOnMouseRepeatListener((JavaScriptCallback) ev -> text.setTextColor(0xFFFFFF)); + bg.setOnMouseLeaveListener((JavaScriptCallback) ev -> text.setTextColor(0x000000)); + } + } + } + + @Override + public void keyPressed(KeyEvent ev) + { + switch (ev.getKeyCode()) + { + case KeyEvent.VK_UP: + ev.consume(); + if (selectedPrediction > -1) + { + selectedPrediction--; + if (selectedPrediction == -1) + { + value(offPrediction); + } + else + { + value(predictions.get(selectedPrediction)); + } + } + break; + case KeyEvent.VK_DOWN: + ev.consume(); + + if (selectedPrediction == -1) + { + offPrediction = getValue(); + } + + selectedPrediction++; + if (selectedPrediction >= predictions.size()) + { + selectedPrediction = predictions.size() - 1; + } + + if (selectedPrediction != -1) + { + value(predictions.get(selectedPrediction)); + } + break; + default: + super.keyPressed(ev); + } + } + + private void search(String search) + { + LinkBrowser.browse(WikiPlugin.WIKI_BASE + "?search=" + URLEncoder.encode(search) + "&" + WikiPlugin.UTM_PARAMS); + chatboxPanelManager.close(); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/wiki/WikiSprite.java b/runelite-client/src/main/java/net/runelite/client/plugins/wiki/WikiSprite.java new file mode 100644 index 0000000000..188d54837b --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/wiki/WikiSprite.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2018 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.client.plugins.wiki; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import net.runelite.api.SpriteID; +import net.runelite.client.game.SpriteOverride; + +@RequiredArgsConstructor +public enum WikiSprite implements SpriteOverride +{ + WIKI_ICON(-300, "wiki.png"), + WIKI_SELECTED_ICON(-301, "wiki_selected.png"), + FIXED_MODE_MINIMAP_CLICKMASK(SpriteID.MINIMAP_CLICK_MASK, "fixed_mode_minimap_clickmask.png"); + + @Getter + private final int spriteId; + + @Getter + private final String fileName; +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/AgilityShortcutLocation.java b/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/AgilityShortcutLocation.java deleted file mode 100644 index dd2e3771b6..0000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/AgilityShortcutLocation.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright (c) 2018, Morgan Lewis - * 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 HOLDER 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.worldmap; - -import lombok.Getter; -import net.runelite.api.coords.WorldPoint; - -@Getter -enum AgilityShortcutLocation -{ - KARAMJA_GLIDER_LOG("Log Balance", 1, new WorldPoint(2906, 3050, 0)), - FALADOR_CRUMBLING_WALL("Crumbling Wall", 5, new WorldPoint(2936, 3357, 0)), - RIVER_LUM_GRAPPLE_WEST("Grapple Broken Raft", 8, new WorldPoint(3245, 3179, 0)), - RIVER_LUM_GRAPPLE_EAST("Grapple Broken Raft", 8, new WorldPoint(3258, 3179, 0)), - CORSAIR_COVE_ROCKS("Rocks", 10, new WorldPoint(2545, 2871, 0)), - FALADOR_GRAPPLE_WALL("Grapple Wall", 11, new WorldPoint(3031, 3391, 0)), - VARROCK_SOUTH_FENCE("Fence", 13, new WorldPoint(3239, 3334, 0)), - GOBLIN_VILLAGE_WALL("Wall", 14, new WorldPoint(2925, 3523, 0)), - CORSAIR_COVE_DUNGEON_PILLAR("Pillar Jump", 15, new WorldPoint(1980, 8996, 0)), - YANILLE_UNDERWALL_TUNNEL("Underwall Tunnel", 16, new WorldPoint(2574, 3109, 0)), - COAL_TRUCKS_LOG_BALANCE("Log Balance", 20, new WorldPoint(2598, 3475, 0)), - GRAND_EXCHANGE_UNDERWALL_TUNNEL("Underwall Tunnel", 21, new WorldPoint(3139, 3515, 0)), - BRIMHAVEN_DUNGEON_PIPE("Pipe Squeeze", 22, new WorldPoint(2654, 9569, 0)), - OBSERVATORY_SCALE_CLIFF("Grapple Rocks", 23, new WorldPoint(2447, 3155, 0)), - EAGLES_PEAK_ROCK_CLIMB("Rock Climb", 25, new WorldPoint(2320, 3499, 0)), - FALADOR_UNDERWALL_TUNNEL("Underwall Tunnel", 26, new WorldPoint(2947, 3313, 0)), - MOUNT_KARUULM_LOWER("Rocks", 29, new WorldPoint(1324, 3782, 0)), - CORSAIR_COVE_RESOURCE_ROCKS("Rocks", 30, new WorldPoint(2545, 2871, 0)), - SOUTHEAST_KARAJMA_STEPPING_STONES("Stepping Stones", 30, new WorldPoint(2924, 2946, 0)), - DRAYNOR_MANOR_STEPPING_STONES("Stepping Stones", 31, new WorldPoint(3150, 3362, 0)), - CATHERBY_CLIFFSIDE_GRAPPLE("Grapple Rock", 32, new WorldPoint(2868, 3429, 0)), - ARDOUGNE_LOG_BALANCE("Log Balance", 33, new WorldPoint(2602, 3336, 0)), - GNOME_STRONGHOLD_ROCKS("Rocks", 37, new WorldPoint(2485, 3515, 0)), - AL_KHARID_MINING_PITCLIFF_SCRAMBLE("Rocks", 38, new WorldPoint(3305, 3315, 0)), - YANILLE_WALL_GRAPPLE("Grapple Wall", 39, new WorldPoint(2552, 3072, 0)), - NEITIZNOT_BRIDGE_REPAIR("Bridge Repair - Quest", 40, new WorldPoint(2315, 3828, 0)), - KOUREND_LAKE_JUMP_WEST("Stepping Stones", 40, new WorldPoint(1604, 3572, 0)), - KOUREND_LAKE_JUMP_EAST("Stepping Stones", 40, new WorldPoint(1612, 3570, 0)), - TROLLHEIM_EASY_CLIFF_SCRAMBLE("Rocks", 41, new WorldPoint(2869, 3670, 0)), - DWARVEN_MINE_NARROW_CREVICE("Narrow Crevice", 42, new WorldPoint(3034, 9806, 0)), - DRAYNOR_UNDERWALL_TUNNEL("Underwall Tunnel", 42, new WorldPoint(3068, 3261, 0)), - TROLLHEIM_MEDIUM_CLIFF_SCRAMBLE_NORTH("Rocks", 43, new WorldPoint(2886, 3684, 0)), - TROLLHEIM_MEDIUM_CLIFF_SCRAMBLE_SOUTH("Rocks", 43, new WorldPoint(2876, 3666, 0)), - TROLLHEIM_ADVANCED_CLIFF_SCRAMBLE("Rocks", 44, new WorldPoint(2907, 3686, 0)), - KOUREND_RIVER_STEPPING_STONES("Stepping Stones", 45, new WorldPoint(1721, 3509, 0)), - COSMIC_ALTAR_MEDIUM_WALKWAY("Narrow Walkway", 46, new WorldPoint(2399, 4403, 0)), - DEEP_WILDERNESS_DUNGEON_CREVICE_NORTH("Narrow Crevice", 46, new WorldPoint(3047, 10335, 0)), - DEEP_WILDERNESS_DUNGEON_CREVICE_SOUTH("Narrow Crevice", 46, new WorldPoint(3045, 10327, 0)), - TROLLHEIM_HARD_CLIFF_SCRAMBLE("Rocks", 47, new WorldPoint(2902, 3680, 0)), - FREMENNIK_LOG_BALANCE("Log Balance", 48, new WorldPoint(2721, 3591, 0)), - ARCEUUS_ESSENCE_MINE_BOULDER("Boulder", 49, new WorldPoint(1774, 3888, 0)), - MORYTANIA_STEPPING_STONE("Stepping Stone", 50, new WorldPoint(3418, 3326, 0)), - VARROCK_SEWERS_PIPE_SQUEEZE("Pipe Squeeze", 51, new WorldPoint(3152, 9905, 0)), - ARCEUUS_ESSENCE_MINE_EAST_SCRAMBLE("Rock Climb", 52, new WorldPoint(1770, 3851, 0)), - KARAMJA_VOLCANO_GRAPPLE_NORTH("Grapple Rock", 53, new WorldPoint(2873, 3143, 0)), - KARAMJA_VOLCANO_GRAPPLE_SOUTH("Grapple Rock", 53, new WorldPoint(2874, 3128, 0)), - MOTHERLODE_MINE_WALL_WEST("Wall", 54, new WorldPoint(3118, 9702, 0)), - MOTHERLODE_MINE_WALL_EAST("Wall", 54, new WorldPoint(3124, 9703, 0)), - MISCELLANIA_DOCK_STEPPING_STONE("Stepping Stone", 55, new WorldPoint(2572, 3862, 0)), - RELEKKA_EAST_FENCE("Fence", 57, new WorldPoint(2688, 3697, 0)), - ELVEN_OVERPASS_CLIFF_SCRAMBLE("Rocks", 59, new WorldPoint(2345, 3300, 0)), - WILDERNESS_GWD_CLIMB_WEST("Rocks", 60, new WorldPoint(2928, 3760, 0)), - WILDERNESS_GWD_CLIMB_EAST("Rocks", 60, new WorldPoint(2943, 3770, 0)), - MOS_LEHARMLESS_STEPPING_STONE("Stepping Stone", 60, new WorldPoint(3710, 2970, 0)), - WINTERTODT_GAP("Gap", 60, new WorldPoint(1629, 4023, 0)), - SLAYER_TOWER_MEDIUM_CHAIN_FIRST("Spiked Chain (Floor 1)", 61, new WorldPoint(3421, 3550, 0)), - SLAYER_TOWER_MEDIUM_CHAIN_SECOND("Spiked Chain (Floor 2)", 61, new WorldPoint(3420, 3551, 0)), - SLAYER_DUNGEON_CREVICE("Narrow Crevice", 62, new WorldPoint(2729, 10008, 0)), - MOUNT_KARUULM_UPPER("Rocks", 62, new WorldPoint(1322, 3791, 0)), - TAVERLEY_DUNGEON_RAILING("Loose Railing", 63, new WorldPoint(2935, 9811, 0)), - TROLLHEIM_WILDERNESS_ROCKS("Rocks", 64, new WorldPoint(2945, 3678, 0)), - FOSSIL_ISLAND_VOLCANO("Rope", 64, new WorldPoint(3780, 3822, 0)), - MORYTANIA_TEMPLE("Loose Railing", 65, new WorldPoint(3422, 3476, 0)), - REVENANT_CAVES_GREEN_DRAGONS("Jump", 65, new WorldPoint(3220, 10086, 0)), - COSMIC_ALTAR_ADVANCED_WALKWAY("Narrow Walkway", 66, new WorldPoint(2408, 4401, 0)), - LUMBRIDGE_DESERT_STEPPING_STONE("Stepping Stone", 66, new WorldPoint(3210, 3135, 0)), - HEROS_GUILD_TUNNEL_WEST("Crevice", 67, new WorldPoint(2898, 9901, 0)), - HEROS_GUILD_TUNNEL_EAST("Crevice", 67, new WorldPoint(2913, 9895, 0)), - ELVEN_OVERPASS_MEDIUM_CLIFF("Rocks", 68, new WorldPoint(2337, 3288, 0)), - ARCEUUS_ESSENSE_NORTH("Rock Climb", 69, new WorldPoint(1759, 3873, 0)), - TAVERLEY_DUNGEON_PIPE_BLUE_DRAGON("Pipe Squeeze", 70, new WorldPoint(2886, 9798, 0)), - FOSSIL_ISLAND_HARDWOOD("Hole", 70, new WorldPoint(3663, 3810, 0)), - GWD_SARADOMIN_ROPE_FIRST("Rope Descent", 70, new WorldPoint(2912, 5300, 0)), - GWD_SARADOMIN_ROPE_SECOND("Rope Descent", 70, new WorldPoint(2951, 5267, 0)), - SLAYER_TOWER_ADVANCED_CHAIN_FIRST("Spiked Chain (Floor 2)", 71, new WorldPoint(3447, 3578, 0)), - SLAYER_TOWER_ADVANCED_CHAIN_SECOND("Spiked Chain (Floor 3)", 71, new WorldPoint(3446, 3576, 0)), - SLAYER_CAVE_WALL_CLIMB("Tunnel", 72, new WorldPoint(2431, 9806, 0)), - TROLL_STRONGHOLD_WALL_CLIMB("Rocks", 73, new WorldPoint(2841, 3694, 0)), - ARCEUUS_ESSENSE_MINE_WEST("Rock Climb", 73, new WorldPoint(1742, 3853, 0)), - LAVA_DRAGON_ISLE_JUMP("Stepping Stone", 74, new WorldPoint(3200, 3807, 0)), - REVENANT_CAVES_DEMONS_JUMP("Jump", 75, new WorldPoint(3199, 10135, 0)), - REVENANT_CAVES_ANKOU_EAST("Jump", 75, new WorldPoint(3201, 10195, 0)), - REVENANT_CAVES_ANKOU_NORTH("Jump", 75, new WorldPoint(3180, 10209, 0)), - ZUL_ANDRA_ISLAND_CROSSING("Stepping Stone", 76, new WorldPoint(2156, 3073, 0)), - SHILO_VILLAGE_STEPPING_STONES("Stepping Stones", 77, new WorldPoint(2863, 2974, 0)), - KHARAZI_JUNGLE_VINE_CLIMB("Vine", 79, new WorldPoint(2897, 2939, 0)), - TAVERLEY_DUNGEON_SPIKED_BLADES("Strange Floor", 80, new WorldPoint(2877, 9813, 0)), - SLAYER_DUNGEON_CHASM_JUMP("Spiked Blades", 81, new WorldPoint(2770, 10003, 0)), - LAVA_MAZE_NORTH_JUMP("Stepping Stone", 82, new WorldPoint(3092, 3880, 0)), - BRIMHAVEN_DUNGEON_EAST_STEPPING_NORTH("Stepping Stones", 83, new WorldPoint(2685, 9547, 0)), - BRIMHAVEN_DUNGEON_EAST_STEPPING_SOUTH("Stepping Stones", 83, new WorldPoint(2693, 9529, 0)), - ELVEN_ADVANCED_CLIFF_SCRAMBLE("Rocks", 85, new WorldPoint(2337, 3253, 0)), - KALPHITE_WALL("Crevice", 86, new WorldPoint(3214, 9508, 0)), - BRIMHAVEN_DUNGEON_VINE_EAST("Vine", 87, new WorldPoint(2672, 9582, 0)), - BRIMHAVEN_DUNGEON_VINE_WEST("Vine", 87, new WorldPoint(2606, 9584, 0)), - RENEVANT_CAVES("Jump", 89, new WorldPoint(3240, 10144, 0)); - - private final String tooltip; - private final WorldPoint location; - private final int levelReq; - - AgilityShortcutLocation(String description, int level, WorldPoint location) - { - this.tooltip = description + " - Level " + level; - this.location = location; - this.levelReq = level; - } -} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/AgilityShortcutPoint.java b/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/AgilityShortcutPoint.java index 74f4673c85..c99979f103 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/AgilityShortcutPoint.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/AgilityShortcutPoint.java @@ -27,12 +27,13 @@ package net.runelite.client.plugins.worldmap; import java.awt.image.BufferedImage; import net.runelite.client.ui.overlay.worldmap.WorldMapPoint; +import net.runelite.client.game.AgilityShortcut; class AgilityShortcutPoint extends WorldMapPoint { - AgilityShortcutPoint(AgilityShortcutLocation data, BufferedImage icon, boolean showTooltip) + AgilityShortcutPoint(AgilityShortcut data, BufferedImage icon, boolean showTooltip) { - super(data.getLocation(), icon); + super(data.getWorldMapLocation(), icon); if (showTooltip) { diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/QuestStartLocation.java b/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/QuestStartLocation.java index 66dde6666b..c1d9e9fb88 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/QuestStartLocation.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/QuestStartLocation.java @@ -30,6 +30,7 @@ import net.runelite.api.coords.WorldPoint; enum QuestStartLocation { + //Free Quests COOKS_ASSISTANT_RFD("Cook's Assistant", new WorldPoint(3211, 3216, 0)), THE_CORSAIR_CURSE("The Corsair Curse", new WorldPoint(3029, 3273, 0)), DEMON_SLAYER("Demon Slayer", new WorldPoint(3204, 3424, 0)), @@ -48,8 +49,12 @@ enum QuestStartLocation SHIELD_OF_ARRAV("Shield of Arrav", new WorldPoint(3208, 3495, 0)), VAMPIRE_SLAYER("Vampire Slayer", new WorldPoint(3096, 3266, 0)), WITCHS_POTION("Witch's Potion", new WorldPoint(2967, 3203, 0)), + X_MARKS_THE_SPOT("X Marks the Spot", new WorldPoint(3227, 3242, 0)), + + //Members' Quests ANIMAL_MAGNETISM("Animal Magnetism", new WorldPoint(3094, 3360, 0)), ANOTHER_SLICE_OF_HAM("Another Slice of H.A.M.", new WorldPoint(2799, 5428, 0)), + THE_ASCENT_OF_ARCEUUS("The Ascent of Arceuus", new WorldPoint(1700, 3742, 0)), BETWEEN_A_ROCK("Between a Rock...", new WorldPoint(2823, 10168, 0)), BIG_CHOMPY_BIRD_HUNTING("Big Chompy Bird Hunting", new WorldPoint(2629, 2981, 0)), BIOHAZARD("Biohazard", new WorldPoint(2591, 3335, 0)), @@ -84,6 +89,7 @@ enum QuestStartLocation FISHING_CONTEST_1("Fishing Contest", new WorldPoint(2875, 3483, 0)), FISHING_CONTEST_2("Fishing Contest", new WorldPoint(2820, 3487, 0)), FORGETTABLE_TALE("Forgettable Tale...", new WorldPoint(2826, 10215, 0)), + THE_FORSAKEN_TOWER("The Forsaken Tower", new WorldPoint(1484, 3747, 0)), THE_FREMENNIK_ISLES("The Fremennik Isles", new WorldPoint(2645, 3711, 0)), THE_FREMENNIK_TRIALS("The Fremennik Trials", new WorldPoint(2657, 3669, 0)), GARDEN_OF_TRANQUILLITY("Garden of Tranquillity", new WorldPoint(3227, 3477, 0)), diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/RareTreeLocation.java b/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/RareTreeLocation.java index ca30b23271..d32dee4c32 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/RareTreeLocation.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/RareTreeLocation.java @@ -140,7 +140,7 @@ enum RareTreeLocation new WorldPoint(3018, 3316, 0), new WorldPoint(3041, 3320, 0), new WorldPoint(3052, 3272, 0), - new WorldPoint(2933, 3234, 0), + new WorldPoint(2931, 3231, 0), // Misthalin new WorldPoint(3085, 3481, 0), diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/WorldMapPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/WorldMapPlugin.java index fea186927b..276e2083e4 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/WorldMapPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/worldmap/WorldMapPlugin.java @@ -36,6 +36,7 @@ import net.runelite.api.events.ConfigChanged; import net.runelite.api.events.ExperienceChanged; import net.runelite.client.config.ConfigManager; import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.game.AgilityShortcut; import net.runelite.client.plugins.Plugin; import net.runelite.client.plugins.PluginDescriptor; import net.runelite.client.ui.overlay.worldmap.WorldMapPointManager; @@ -167,9 +168,10 @@ public class WorldMapPlugin extends Plugin if (config.agilityShortcutLevelIcon() || config.agilityShortcutTooltips()) { - Arrays.stream(AgilityShortcutLocation.values()) + Arrays.stream(AgilityShortcut.values()) + .filter(value -> value.getWorldMapLocation() != null) .map(value -> new AgilityShortcutPoint(value, - agilityLevel > 0 && config.agilityShortcutLevelIcon() && value.getLevelReq() > agilityLevel ? NOPE_ICON : BLANK_ICON, + agilityLevel > 0 && config.agilityShortcutLevelIcon() && value.getLevel() > agilityLevel ? NOPE_ICON : BLANK_ICON, config.agilityShortcutTooltips())) .forEach(worldMapPointManager::add); } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XpTrackerPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XpTrackerPlugin.java index 54dff5a299..bb43ffdd7a 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XpTrackerPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XpTrackerPlugin.java @@ -73,7 +73,7 @@ public class XpTrackerPlugin extends Plugin /** * Amount of EXP that must be gained for an update to be submitted. */ - private static final int XP_THRESHOLD = 1000; + private static final int XP_THRESHOLD = 10_000; static final List COMBAT = ImmutableList.of( Skill.ATTACK, diff --git a/runelite-client/src/main/java/net/runelite/client/ui/overlay/components/ImageComponent.java b/runelite-client/src/main/java/net/runelite/client/ui/overlay/components/ImageComponent.java index 05ddc35395..39ec431697 100644 --- a/runelite-client/src/main/java/net/runelite/client/ui/overlay/components/ImageComponent.java +++ b/runelite-client/src/main/java/net/runelite/client/ui/overlay/components/ImageComponent.java @@ -27,7 +27,9 @@ package net.runelite.client.ui.overlay.components; import java.awt.Dimension; import java.awt.Graphics2D; import java.awt.Point; +import java.awt.Rectangle; import java.awt.image.BufferedImage; +import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.Setter; @@ -36,6 +38,10 @@ import lombok.Setter; public class ImageComponent implements LayoutableRenderableEntity { private final BufferedImage image; + + @Getter + private final Rectangle bounds = new Rectangle(); + private Point preferredLocation = new Point(); @Override @@ -47,7 +53,10 @@ public class ImageComponent implements LayoutableRenderableEntity } graphics.drawImage(image, preferredLocation.x, preferredLocation.y, null); - return new Dimension(image.getWidth(), image.getHeight()); + final Dimension dimension = new Dimension(image.getWidth(), image.getHeight()); + bounds.setLocation(preferredLocation); + bounds.setSize(dimension); + return dimension; } @Override diff --git a/runelite-client/src/main/java/net/runelite/client/ui/overlay/components/InfoBoxComponent.java b/runelite-client/src/main/java/net/runelite/client/ui/overlay/components/InfoBoxComponent.java index ca6c55d3be..d8f7841099 100644 --- a/runelite-client/src/main/java/net/runelite/client/ui/overlay/components/InfoBoxComponent.java +++ b/runelite-client/src/main/java/net/runelite/client/ui/overlay/components/InfoBoxComponent.java @@ -24,6 +24,7 @@ */ package net.runelite.client.ui.overlay.components; +import com.google.common.base.Strings; import java.awt.Color; import java.awt.Dimension; import java.awt.FontMetrics; @@ -45,11 +46,10 @@ public class InfoBoxComponent implements LayoutableRenderableEntity private String tooltip; @Getter + private final Rectangle bounds = new Rectangle(); + private Point preferredLocation = new Point(); - - @Setter private Dimension preferredSize = new Dimension(DEFAULT_SIZE, DEFAULT_SIZE); - private String text; private Color color = Color.WHITE; private Color backgroundColor = ComponentConstants.STANDARD_BACKGROUND_COLOR; @@ -64,12 +64,14 @@ public class InfoBoxComponent implements LayoutableRenderableEntity } graphics.setFont(getSize() < DEFAULT_SIZE ? FontManager.getRunescapeSmallFont() : FontManager.getRunescapeFont()); - graphics.translate(preferredLocation.x, preferredLocation.y); + + final int baseX = preferredLocation.x; + final int baseY = preferredLocation.y; // Calculate dimensions final FontMetrics metrics = graphics.getFontMetrics(); final int size = getSize(); - final Rectangle bounds = new Rectangle(size, size); + final Rectangle bounds = new Rectangle(baseX, baseY, size, size); // Render background final BackgroundComponent backgroundComponent = new BackgroundComponent(); @@ -80,26 +82,24 @@ public class InfoBoxComponent implements LayoutableRenderableEntity // Render image graphics.drawImage( image, - (size - image.getWidth(null)) / 2, - (size - image.getHeight(null)) / 2, + baseX + (size - image.getWidth(null)) / 2, + baseY + (size - image.getHeight(null)) / 2, null); // Render caption - final TextComponent textComponent = new TextComponent(); - textComponent.setColor(color); - textComponent.setText(text); - textComponent.setPosition(new Point(((size - metrics.stringWidth(text)) / 2), size - SEPARATOR)); - textComponent.render(graphics); + if (!Strings.isNullOrEmpty(text)) + { + final TextComponent textComponent = new TextComponent(); + textComponent.setColor(color); + textComponent.setText(text); + textComponent.setPosition(new Point(baseX + ((size - metrics.stringWidth(text)) / 2), baseY + size - SEPARATOR)); + textComponent.render(graphics); + } - graphics.translate(-preferredLocation.x, -preferredLocation.y); + this.bounds.setBounds(bounds); return bounds.getSize(); } - public Dimension getPreferredSize() - { - return new Dimension(getSize(), getSize()); - } - private int getSize() { return Math.max(preferredSize.width, preferredSize.height); diff --git a/runelite-client/src/main/java/net/runelite/client/ui/overlay/components/LayoutableRenderableEntity.java b/runelite-client/src/main/java/net/runelite/client/ui/overlay/components/LayoutableRenderableEntity.java index ba7f1e7717..7a736686d0 100644 --- a/runelite-client/src/main/java/net/runelite/client/ui/overlay/components/LayoutableRenderableEntity.java +++ b/runelite-client/src/main/java/net/runelite/client/ui/overlay/components/LayoutableRenderableEntity.java @@ -26,10 +26,12 @@ package net.runelite.client.ui.overlay.components; import java.awt.Dimension; import java.awt.Point; +import java.awt.Rectangle; import net.runelite.client.ui.overlay.RenderableEntity; public interface LayoutableRenderableEntity extends RenderableEntity { + Rectangle getBounds(); void setPreferredLocation(Point position); void setPreferredSize(Dimension dimension); } diff --git a/runelite-client/src/main/java/net/runelite/client/ui/overlay/components/LineComponent.java b/runelite-client/src/main/java/net/runelite/client/ui/overlay/components/LineComponent.java index efe7f407a6..f6c2f5e77c 100644 --- a/runelite-client/src/main/java/net/runelite/client/ui/overlay/components/LineComponent.java +++ b/runelite-client/src/main/java/net/runelite/client/ui/overlay/components/LineComponent.java @@ -31,7 +31,9 @@ import java.awt.Dimension; import java.awt.FontMetrics; import java.awt.Graphics2D; import java.awt.Point; +import java.awt.Rectangle; import lombok.Builder; +import lombok.Getter; import lombok.Setter; @Setter @@ -53,17 +55,22 @@ public class LineComponent implements LayoutableRenderableEntity @Builder.Default private Dimension preferredSize = new Dimension(ComponentConstants.STANDARD_WIDTH, 0); + @Builder.Default + @Getter + private final Rectangle bounds = new Rectangle(); + @Override public Dimension render(Graphics2D graphics) { - graphics.translate(preferredLocation.x, preferredLocation.y); // Prevent NPEs final String left = MoreObjects.firstNonNull(this.left, ""); final String right = MoreObjects.firstNonNull(this.right, ""); final FontMetrics metrics = graphics.getFontMetrics(); - int x = 0; - int y = metrics.getHeight(); + final int baseX = preferredLocation.x; + final int baseY = preferredLocation.y + metrics.getHeight(); + int x = baseX; + int y = baseY; final int leftFullWidth = getLineWidth(left, metrics); final int rightFullWidth = getLineWidth(right, metrics); @@ -112,8 +119,10 @@ public class LineComponent implements LayoutableRenderableEntity y += metrics.getHeight(); } - graphics.translate(-preferredLocation.x, -preferredLocation.y); - return new Dimension(preferredSize.width, y - metrics.getHeight()); + final Dimension dimension = new Dimension(preferredSize.width, y - baseY); + bounds.setLocation(preferredLocation); + bounds.setSize(dimension); + return dimension; } final TextComponent leftLineComponent = new TextComponent(); @@ -129,8 +138,10 @@ public class LineComponent implements LayoutableRenderableEntity rightLineComponent.render(graphics); y += metrics.getHeight(); - graphics.translate(-preferredLocation.x, -preferredLocation.y); - return new Dimension(preferredSize.width, y - metrics.getHeight()); + final Dimension dimension = new Dimension(preferredSize.width, y - baseY); + bounds.setLocation(preferredLocation); + bounds.setSize(dimension); + return dimension; } private static int getLineWidth(final String line, final FontMetrics metrics) diff --git a/runelite-client/src/main/java/net/runelite/client/ui/overlay/components/PanelComponent.java b/runelite-client/src/main/java/net/runelite/client/ui/overlay/components/PanelComponent.java index 974ec37240..5ea81f2006 100644 --- a/runelite-client/src/main/java/net/runelite/client/ui/overlay/components/PanelComponent.java +++ b/runelite-client/src/main/java/net/runelite/client/ui/overlay/components/PanelComponent.java @@ -43,9 +43,8 @@ public class PanelComponent implements LayoutableRenderableEntity VERTICAL; } - @Setter - @Nullable - private Color backgroundColor = ComponentConstants.STANDARD_BACKGROUND_COLOR; + @Getter + private final Rectangle bounds = new Rectangle(); @Setter private Point preferredLocation = new Point(); @@ -53,8 +52,12 @@ public class PanelComponent implements LayoutableRenderableEntity @Setter private Dimension preferredSize = new Dimension(ComponentConstants.STANDARD_WIDTH, 0); + @Setter + @Nullable + private Color backgroundColor = ComponentConstants.STANDARD_BACKGROUND_COLOR; + @Getter - private List children = new ArrayList<>(); + private final List children = new ArrayList<>(); @Setter private Orientation orientation = Orientation.VERTICAL; @@ -82,8 +85,6 @@ public class PanelComponent implements LayoutableRenderableEntity return null; } - graphics.translate(preferredLocation.x, preferredLocation.y); - // Calculate panel dimension final Dimension dimension = new Dimension( border.x + childDimensions.width + border.width, @@ -93,14 +94,14 @@ public class PanelComponent implements LayoutableRenderableEntity if (backgroundColor != null) { final BackgroundComponent backgroundComponent = new BackgroundComponent(); - backgroundComponent.setRectangle(new Rectangle(dimension)); + backgroundComponent.setRectangle(new Rectangle(preferredLocation, dimension)); backgroundComponent.setBackgroundColor(backgroundColor); backgroundComponent.render(graphics); } // Offset children - final int baseX = border.x; - final int baseY = border.y; + final int baseX = preferredLocation.x + border.x; + final int baseY = preferredLocation.y + border.y; int width = 0; int height = 0; int x = baseX; @@ -174,7 +175,9 @@ public class PanelComponent implements LayoutableRenderableEntity // Cache children bounds childDimensions.setSize(totalWidth, totalHeight); - graphics.translate(-preferredLocation.x, -preferredLocation.y); + // Cache bounds + bounds.setLocation(preferredLocation); + bounds.setSize(dimension); return dimension; } } diff --git a/runelite-client/src/main/java/net/runelite/client/ui/overlay/components/ProgressBarComponent.java b/runelite-client/src/main/java/net/runelite/client/ui/overlay/components/ProgressBarComponent.java index 0db182739a..ba4634deec 100644 --- a/runelite-client/src/main/java/net/runelite/client/ui/overlay/components/ProgressBarComponent.java +++ b/runelite-client/src/main/java/net/runelite/client/ui/overlay/components/ProgressBarComponent.java @@ -29,7 +29,9 @@ import java.awt.Dimension; import java.awt.FontMetrics; import java.awt.Graphics2D; import java.awt.Point; +import java.awt.Rectangle; import java.text.DecimalFormat; +import lombok.Getter; import lombok.Setter; @Setter @@ -43,6 +45,7 @@ public class ProgressBarComponent implements LayoutableRenderableEntity private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("0.0"); private static final DecimalFormat DECIMAL_FORMAT_ABS = new DecimalFormat("#0"); + private long minimum; private long maximum = 100; private double value; @@ -53,14 +56,16 @@ public class ProgressBarComponent implements LayoutableRenderableEntity private Point preferredLocation = new Point(); private Dimension preferredSize = new Dimension(ComponentConstants.STANDARD_WIDTH, 16); + @Getter + private final Rectangle bounds = new Rectangle(); + @Override public Dimension render(Graphics2D graphics) { - graphics.translate(preferredLocation.x, preferredLocation.y); final FontMetrics metrics = graphics.getFontMetrics(); - final int barX = 0; - final int barY = 0; + final int barX = preferredLocation.x; + final int barY = preferredLocation.y; final long span = maximum - minimum; final double currentValue = value - minimum; @@ -82,7 +87,7 @@ public class ProgressBarComponent implements LayoutableRenderableEntity final int progressTextY = barY + ((height - metrics.getHeight()) / 2) + metrics.getHeight(); final int progressFill = (int) (width * Math.min(1, pc)); - //Draw bar + // Draw bar graphics.setColor(backgroundColor); graphics.fillRect(barX, barY, width, height); graphics.setColor(foregroundColor); @@ -94,7 +99,9 @@ public class ProgressBarComponent implements LayoutableRenderableEntity textComponent.setText(textToWrite); textComponent.render(graphics); - graphics.translate(-preferredLocation.x, -preferredLocation.y); - return new Dimension(width, height); + final Dimension dimension = new Dimension(width, height); + bounds.setLocation(preferredLocation); + bounds.setSize(dimension); + return dimension; } } diff --git a/runelite-client/src/main/java/net/runelite/client/ui/overlay/components/TitleComponent.java b/runelite-client/src/main/java/net/runelite/client/ui/overlay/components/TitleComponent.java index 093d596fe9..fa4c9bc3bb 100644 --- a/runelite-client/src/main/java/net/runelite/client/ui/overlay/components/TitleComponent.java +++ b/runelite-client/src/main/java/net/runelite/client/ui/overlay/components/TitleComponent.java @@ -29,7 +29,9 @@ import java.awt.Dimension; import java.awt.FontMetrics; import java.awt.Graphics2D; import java.awt.Point; +import java.awt.Rectangle; import lombok.Builder; +import lombok.Getter; import lombok.Setter; @Setter @@ -47,17 +49,26 @@ public class TitleComponent implements LayoutableRenderableEntity @Builder.Default private Dimension preferredSize = new Dimension(ComponentConstants.STANDARD_WIDTH, 0); + @Builder.Default + @Getter + private final Rectangle bounds = new Rectangle(); + @Override public Dimension render(Graphics2D graphics) { - graphics.translate(preferredLocation.x, preferredLocation.y); + final int baseX = preferredLocation.x; + final int baseY = preferredLocation.y; final FontMetrics metrics = graphics.getFontMetrics(); final TextComponent titleComponent = new TextComponent(); titleComponent.setText(text); titleComponent.setColor(color); - titleComponent.setPosition(new Point((preferredSize.width - metrics.stringWidth(text)) / 2, metrics.getHeight())); - final Dimension dimension = titleComponent.render(graphics); - graphics.translate(-preferredLocation.x, -preferredLocation.y); - return new Dimension(preferredSize.width, dimension.height); + titleComponent.setPosition(new Point( + baseX + ((preferredSize.width - metrics.stringWidth(text)) / 2), + baseY + metrics.getHeight())); + final Dimension rendered = titleComponent.render(graphics); + final Dimension dimension = new Dimension(preferredSize.width, rendered.height); + bounds.setLocation(preferredLocation); + bounds.setSize(dimension); + return dimension; } } diff --git a/runelite-client/src/main/java/net/runelite/client/ui/overlay/infobox/Counter.java b/runelite-client/src/main/java/net/runelite/client/ui/overlay/infobox/Counter.java index 599a178838..49614e9b61 100644 --- a/runelite-client/src/main/java/net/runelite/client/ui/overlay/infobox/Counter.java +++ b/runelite-client/src/main/java/net/runelite/client/ui/overlay/infobox/Counter.java @@ -26,33 +26,28 @@ package net.runelite.client.ui.overlay.infobox; import java.awt.Color; import java.awt.image.BufferedImage; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; import net.runelite.client.plugins.Plugin; +@ToString public class Counter extends InfoBox { - private String text; + @Getter + @Setter + private int count; - public Counter(BufferedImage image, Plugin plugin, String text) + public Counter(BufferedImage image, Plugin plugin, int count) { super(image, plugin); - this.text = text; - } - - @Override - public String toString() - { - return "Counter{" + "text=" + text + '}'; + this.count = count; } @Override public String getText() { - return text; - } - - public void setText(String text) - { - this.text = text; + return Integer.toString(count); } @Override diff --git a/runelite-client/src/main/java/net/runelite/client/ui/overlay/infobox/InfoBox.java b/runelite-client/src/main/java/net/runelite/client/ui/overlay/infobox/InfoBox.java index fc498d3635..8c2889e8e6 100644 --- a/runelite-client/src/main/java/net/runelite/client/ui/overlay/infobox/InfoBox.java +++ b/runelite-client/src/main/java/net/runelite/client/ui/overlay/infobox/InfoBox.java @@ -26,24 +26,25 @@ package net.runelite.client.ui.overlay.infobox; import java.awt.Color; import java.awt.Image; +import lombok.AccessLevel; import lombok.Getter; import lombok.Setter; import net.runelite.client.plugins.Plugin; public abstract class InfoBox { - @Getter + @Getter(AccessLevel.PACKAGE) private final Plugin plugin; @Getter @Setter private Image image; - @Getter - @Setter + @Getter(AccessLevel.PACKAGE) + @Setter(AccessLevel.PACKAGE) private Image scaledImage; - @Getter + @Getter(AccessLevel.PACKAGE) @Setter private InfoBoxPriority priority; diff --git a/runelite-client/src/main/java/net/runelite/client/ui/overlay/infobox/InfoBoxManager.java b/runelite-client/src/main/java/net/runelite/client/ui/overlay/infobox/InfoBoxManager.java index 8cea4cc94e..4fd1c1ca11 100644 --- a/runelite-client/src/main/java/net/runelite/client/ui/overlay/infobox/InfoBoxManager.java +++ b/runelite-client/src/main/java/net/runelite/client/ui/overlay/infobox/InfoBoxManager.java @@ -76,18 +76,18 @@ public class InfoBoxManager public void removeInfoBox(InfoBox infoBox) { - log.debug("Removing InfoBox {}", infoBox); if (infoBoxes.remove(infoBox)) { + log.debug("Removed InfoBox {}", infoBox); refreshInfoBoxes(); } } public void removeIf(Predicate filter) { - log.debug("Removing InfoBoxes for filter {}", filter); if (infoBoxes.removeIf(filter)) { + log.debug("Removed InfoBoxes for filter {}", filter); refreshInfoBoxes(); } } diff --git a/runelite-client/src/main/java/net/runelite/client/ui/overlay/infobox/InfoBoxOverlay.java b/runelite-client/src/main/java/net/runelite/client/ui/overlay/infobox/InfoBoxOverlay.java index 15165a4edb..58f20aae63 100644 --- a/runelite-client/src/main/java/net/runelite/client/ui/overlay/infobox/InfoBoxOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/ui/overlay/infobox/InfoBoxOverlay.java @@ -26,6 +26,7 @@ package net.runelite.client.ui.overlay.infobox; import com.google.common.base.Strings; +import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics2D; import java.awt.Point; @@ -37,7 +38,6 @@ import net.runelite.api.Client; import net.runelite.client.config.RuneLiteConfig; import net.runelite.client.ui.overlay.Overlay; import net.runelite.client.ui.overlay.OverlayPosition; -import net.runelite.client.ui.overlay.OverlayUtil; import net.runelite.client.ui.overlay.components.InfoBoxComponent; import net.runelite.client.ui.overlay.components.LayoutableRenderableEntity; import net.runelite.client.ui.overlay.components.PanelComponent; @@ -95,10 +95,16 @@ public class InfoBoxOverlay extends Overlay continue; } + final String text = box.getText(); + final Color color = box.getTextColor(); + final InfoBoxComponent infoBoxComponent = new InfoBoxComponent(); - infoBoxComponent.setColor(box.getTextColor()); + infoBoxComponent.setText(text); + if (color != null) + { + infoBoxComponent.setColor(color); + } infoBoxComponent.setImage(box.getScaledImage()); - infoBoxComponent.setText(box.getText()); infoBoxComponent.setTooltip(box.getTooltip()); panelComponent.getChildren().add(infoBoxComponent); } @@ -116,15 +122,10 @@ public class InfoBoxOverlay extends Overlay if (!Strings.isNullOrEmpty(component.getTooltip())) { - final Rectangle intersectionRectangle = new Rectangle(component.getPreferredLocation(), component.getPreferredSize()); - - // Move the intersection based on overlay position + // Create intersection rectangle + final Rectangle intersectionRectangle = new Rectangle(component.getBounds()); intersectionRectangle.translate(getBounds().x, getBounds().y); - // Move the intersection based on overlay "orientation" - final Point transformed = OverlayUtil.transformPosition(getPosition(), intersectionRectangle.getSize()); - intersectionRectangle.translate(transformed.x, transformed.y); - if (intersectionRectangle.contains(mouse)) { tooltipManager.add(new Tooltip(component.getTooltip())); diff --git a/runelite-client/src/main/java/net/runelite/client/ui/overlay/infobox/Timer.java b/runelite-client/src/main/java/net/runelite/client/ui/overlay/infobox/Timer.java index 3219b487b2..4d6c2b7be4 100644 --- a/runelite-client/src/main/java/net/runelite/client/ui/overlay/infobox/Timer.java +++ b/runelite-client/src/main/java/net/runelite/client/ui/overlay/infobox/Timer.java @@ -31,9 +31,11 @@ import java.time.Duration; import java.time.Instant; import java.time.temporal.ChronoUnit; import lombok.Getter; +import lombok.ToString; import net.runelite.client.plugins.Plugin; @Getter +@ToString public class Timer extends InfoBox { private final Instant startTime; @@ -51,12 +53,6 @@ public class Timer extends InfoBox endTime = startTime.plus(duration); } - @Override - public String toString() - { - return "Timer{" + "startTime=" + startTime + ", endTime=" + endTime + ", duration=" + duration + '}'; - } - @Override public String getText() { diff --git a/runelite-client/src/main/java/net/runelite/client/util/ImageUtil.java b/runelite-client/src/main/java/net/runelite/client/util/ImageUtil.java index 1fa63d815e..16a6b1c78a 100644 --- a/runelite-client/src/main/java/net/runelite/client/util/ImageUtil.java +++ b/runelite-client/src/main/java/net/runelite/client/util/ImageUtil.java @@ -30,6 +30,7 @@ import java.awt.Image; import java.awt.geom.AffineTransform; import java.awt.image.AffineTransformOp; import java.awt.image.BufferedImage; +import java.awt.image.DirectColorModel; import java.awt.image.PixelGrabber; import java.awt.image.RescaleOp; import java.io.IOException; @@ -426,8 +427,19 @@ public class ImageUtil try { - new PixelGrabber(image, 0, 0, image.getWidth(), image.getHeight(), pixels, 0, image.getWidth()) - .grabPixels(); + PixelGrabber g = new PixelGrabber(image, 0, 0, image.getWidth(), image.getHeight(), pixels, 0, image.getWidth()); + g.setColorModel(new DirectColorModel(32, 0xff0000, 0xff00, 0xff, 0xff000000)); + g.grabPixels(); + + // Make any fully transparent pixels fully black, because the sprite draw routines + // check for == 0, not actual transparency + for (int i = 0; i < pixels.length; i++) + { + if ((pixels[i] & 0xFF000000) == 0) + { + pixels[i] = 0; + } + } } catch (InterruptedException ex) { diff --git a/runelite-client/src/main/java/net/runelite/client/util/LinkBrowser.java b/runelite-client/src/main/java/net/runelite/client/util/LinkBrowser.java index 173a383bbe..194b1974b2 100644 --- a/runelite-client/src/main/java/net/runelite/client/util/LinkBrowser.java +++ b/runelite-client/src/main/java/net/runelite/client/util/LinkBrowser.java @@ -43,6 +43,8 @@ import lombok.extern.slf4j.Slf4j; @Slf4j public class LinkBrowser { + private static boolean shouldAttemptXdg = OSType.getOSType() == OSType.Linux; + /** * Tries to navigate to specified URL in browser. In case operation fails, displays message box with message * and copies link to clipboard to navigate to. @@ -56,9 +58,55 @@ public class LinkBrowser return false; } + if (attemptDesktopBrowse(url)) + { + log.debug("Opened browser through Desktop#browse to {}", url); + return true; + } + + if (shouldAttemptXdg && attemptXdgOpen(url)) + { + log.debug("Opened browser through xdg-open to {}", url); + return true; + } + + showMessageBox("Unable to open link. Press 'OK' and link will be copied to your clipboard.", url); + return false; + } + + private static boolean attemptXdgOpen(String url) + { + try + { + final Process exec = Runtime.getRuntime().exec(new String[]{"xdg-open", url}); + exec.waitFor(); + + final int ret = exec.exitValue(); + if (ret == 0) + { + return true; + } + + log.warn("xdg-open {} returned with error code {}", url, ret); + return false; + } + catch (IOException ex) + { + // xdg-open not found + shouldAttemptXdg = false; + return false; + } + catch (InterruptedException ex) + { + log.warn("Interrupted while waiting for xdg-open {} to execute", url); + return false; + } + } + + private static boolean attemptDesktopBrowse(String url) + { if (!Desktop.isDesktopSupported()) { - showMessageBox("Desktop is not supported. Press 'OK' and link will be copied to your clipboard.", url); return false; } @@ -66,20 +114,17 @@ public class LinkBrowser if (!desktop.isSupported(Desktop.Action.BROWSE)) { - showMessageBox("Desktop browser is not supported. Press 'OK' and link will be copied to your clipboard.", url); return false; } try { desktop.browse(new URI(url)); - log.debug("Opened browser to {}", url); return true; } catch (IOException | URISyntaxException ex) { - log.warn("Unable to open URL {}. Error: {}", url, ex); - showMessageBox("Unable to open a URL. Press 'OK' and link will be copied to your clipboard.", url); + log.warn("Failed to open Desktop#browser {}", url, ex); return false; } } diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/info/import_icon.png b/runelite-client/src/main/resources/net/runelite/client/plugins/info/import_icon.png new file mode 100644 index 0000000000..32263bf159 Binary files /dev/null and b/runelite-client/src/main/resources/net/runelite/client/plugins/info/import_icon.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/poison/1067-DISEASE.png b/runelite-client/src/main/resources/net/runelite/client/plugins/poison/1067-DISEASE.png new file mode 100644 index 0000000000..f2ff5ac911 Binary files /dev/null and b/runelite-client/src/main/resources/net/runelite/client/plugins/poison/1067-DISEASE.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/poison/1067-POISON.png b/runelite-client/src/main/resources/net/runelite/client/plugins/poison/1067-POISON.png new file mode 100644 index 0000000000..a4b05c1ca1 Binary files /dev/null and b/runelite-client/src/main/resources/net/runelite/client/plugins/poison/1067-POISON.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/poison/1067-VENOM.png b/runelite-client/src/main/resources/net/runelite/client/plugins/poison/1067-VENOM.png new file mode 100644 index 0000000000..df6816bb74 Binary files /dev/null and b/runelite-client/src/main/resources/net/runelite/client/plugins/poison/1067-VENOM.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/skillcalculator/skill_magic.json b/runelite-client/src/main/resources/net/runelite/client/plugins/skillcalculator/skill_magic.json index 38719b3d00..15905e0a0c 100644 --- a/runelite-client/src/main/resources/net/runelite/client/plugins/skillcalculator/skill_magic.json +++ b/runelite-client/src/main/resources/net/runelite/client/plugins/skillcalculator/skill_magic.json @@ -238,7 +238,7 @@ "level": 37, "sprite": 33, "name": "Falador Teleport", - "xp": 47 + "xp": 48 }, { "level": 39, diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/wiki/fixed_mode_minimap_clickmask.png b/runelite-client/src/main/resources/net/runelite/client/plugins/wiki/fixed_mode_minimap_clickmask.png new file mode 100644 index 0000000000..e4f5fcbe48 Binary files /dev/null and b/runelite-client/src/main/resources/net/runelite/client/plugins/wiki/fixed_mode_minimap_clickmask.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/wiki/wiki.png b/runelite-client/src/main/resources/net/runelite/client/plugins/wiki/wiki.png new file mode 100644 index 0000000000..ef2ff20ff0 Binary files /dev/null and b/runelite-client/src/main/resources/net/runelite/client/plugins/wiki/wiki.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/wiki/wiki_selected.png b/runelite-client/src/main/resources/net/runelite/client/plugins/wiki/wiki_selected.png new file mode 100644 index 0000000000..80838fb5c4 Binary files /dev/null and b/runelite-client/src/main/resources/net/runelite/client/plugins/wiki/wiki_selected.png differ diff --git a/runelite-client/src/main/scripts/BankSearchFilter.rs2asm b/runelite-client/src/main/scripts/BankSearchFilter.rs2asm index 60b9f12aa6..80a0db4021 100644 --- a/runelite-client/src/main/scripts/BankSearchFilter.rs2asm +++ b/runelite-client/src/main/scripts/BankSearchFilter.rs2asm @@ -67,4 +67,4 @@ LABEL34: load_int 1 return load_int -1 - return \ No newline at end of file + return diff --git a/runelite-client/src/main/scripts/SendPrivateMessage.rs2asm b/runelite-client/src/main/scripts/SendPrivateMessage.rs2asm index f2102c1328..3e1e3a8d47 100644 --- a/runelite-client/src/main/scripts/SendPrivateMessage.rs2asm +++ b/runelite-client/src/main/scripts/SendPrivateMessage.rs2asm @@ -31,4 +31,4 @@ sload 0 sload 1 privmsg - return \ No newline at end of file + return diff --git a/runelite-client/src/test/java/net/runelite/client/plugins/itemcharges/ItemChargePluginTest.java b/runelite-client/src/test/java/net/runelite/client/plugins/itemcharges/ItemChargePluginTest.java index b8fc81a16a..165c7d50a7 100644 --- a/runelite-client/src/test/java/net/runelite/client/plugins/itemcharges/ItemChargePluginTest.java +++ b/runelite-client/src/test/java/net/runelite/client/plugins/itemcharges/ItemChargePluginTest.java @@ -28,16 +28,20 @@ import com.google.inject.Guice; import com.google.inject.Inject; import com.google.inject.testing.fieldbinder.Bind; import com.google.inject.testing.fieldbinder.BoundFieldModule; +import java.util.concurrent.ScheduledExecutorService; import net.runelite.api.ChatMessageType; import net.runelite.api.Client; import net.runelite.api.events.ChatMessage; import net.runelite.client.Notifier; +import net.runelite.client.config.RuneLiteConfig; import net.runelite.client.ui.overlay.OverlayManager; -import static org.junit.Assert.assertEquals; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import static org.mockito.Matchers.eq; import org.mockito.Mock; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; import org.mockito.runners.MockitoJUnitRunner; @RunWith(MockitoJUnitRunner.class) @@ -52,6 +56,14 @@ public class ItemChargePluginTest @Bind private Client client; + @Mock + @Bind + private ScheduledExecutorService scheduledExecutorService; + + @Mock + @Bind + private RuneLiteConfig runeLiteConfig; + @Mock @Bind private OverlayManager overlayManager; @@ -78,18 +90,22 @@ public class ItemChargePluginTest { ChatMessage chatMessage = new ChatMessage(null, ChatMessageType.SERVER, "", CHECK, "", 0); itemChargePlugin.onChatMessage(chatMessage); - assertEquals(10, itemChargePlugin.getDodgyCharges()); + verify(config).dodgyNecklace(eq(10)); + reset(config); chatMessage = new ChatMessage(null, ChatMessageType.SERVER, "", PROTECT, "", 0); itemChargePlugin.onChatMessage(chatMessage); - assertEquals(9, itemChargePlugin.getDodgyCharges()); + verify(config).dodgyNecklace(eq(9)); + reset(config); chatMessage = new ChatMessage(null, ChatMessageType.SERVER, "", PROTECT_1, "", 0); itemChargePlugin.onChatMessage(chatMessage); - assertEquals(1, itemChargePlugin.getDodgyCharges()); + verify(config).dodgyNecklace(eq(1)); + reset(config); chatMessage = new ChatMessage(null, ChatMessageType.SERVER, "", BREAK, "", 0); itemChargePlugin.onChatMessage(chatMessage); - assertEquals(10, itemChargePlugin.getDodgyCharges()); + verify(config).dodgyNecklace(eq(10)); + reset(config); } } \ No newline at end of file diff --git a/runelite-mixins/pom.xml b/runelite-mixins/pom.xml index 2bbd0bb214..8b90c2bec0 100644 --- a/runelite-mixins/pom.xml +++ b/runelite-mixins/pom.xml @@ -29,7 +29,7 @@ net.runelite runelite-parent - 1.5.12-SNAPSHOT + 1.5.14-SNAPSHOT mixins diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/ProcessClientErrorMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/ProcessClientErrorMixin.java index a4498ea814..eb3e971a3a 100644 --- a/runelite-mixins/src/main/java/net/runelite/mixins/ProcessClientErrorMixin.java +++ b/runelite-mixins/src/main/java/net/runelite/mixins/ProcessClientErrorMixin.java @@ -55,7 +55,7 @@ public abstract class ProcessClientErrorMixin implements RSClient throwableToScan = ((RSRunException) throwable).getParent(); } - client.getLogger().error("Game crash", throwableToScan); + client.getLogger().error("Game crash: {}", string, throwableToScan); StackTraceElement[] stackTrace = throwableToScan.getStackTrace(); diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/RSClientMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/RSClientMixin.java index b67eea80ab..d162081476 100644 --- a/runelite-mixins/src/main/java/net/runelite/mixins/RSClientMixin.java +++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSClientMixin.java @@ -24,6 +24,8 @@ */ package net.runelite.mixins; +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; import java.util.ArrayList; import java.util.EnumSet; import java.util.List; @@ -32,6 +34,7 @@ import javax.annotation.Nullable; import javax.inject.Named; import net.runelite.api.ChatMessageType; import net.runelite.api.ClanMember; +import net.runelite.api.EnumComposition; import net.runelite.api.Friend; import net.runelite.api.GameState; import net.runelite.api.GrandExchangeOffer; @@ -106,6 +109,7 @@ import net.runelite.rs.api.RSChatLineBuffer; import net.runelite.rs.api.RSClanMemberManager; import net.runelite.rs.api.RSClient; import net.runelite.rs.api.RSDeque; +import net.runelite.rs.api.RSEnum; import net.runelite.rs.api.RSFriendContainer; import net.runelite.rs.api.RSFriendManager; import net.runelite.rs.api.RSHashTable; @@ -176,6 +180,11 @@ public abstract class RSClientMixin implements RSClient @Inject static int skyboxColor; + @Inject + private final Cache enumCache = CacheBuilder.newBuilder() + .maximumSize(64) + .build(); + @Inject @Override public Callbacks getCallbacks() @@ -1265,9 +1274,9 @@ public abstract class RSClientMixin implements RSClient callbacks.clientMainLoop(); } - @MethodHook("gameDraw") + @MethodHook("renderWidgetLayer") @Inject - public static void gameDraw(Widget[] widgets, int parentId, int var2, int var3, int var4, int var5, int x, int y, int var8) + public static void renderWidgetLayer(Widget[] widgets, int parentId, int var2, int var3, int var4, int var5, int x, int y, int var8) { for (Widget rlWidget : widgets) { @@ -1442,4 +1451,21 @@ public abstract class RSClientMixin implements RSClient return false; } + + @Inject + @Override + public EnumComposition getEnum(int id) + { + assert isClientThread() : "getEnum must be called on client thread"; + + RSEnum rsEnum = enumCache.getIfPresent(id); + if (rsEnum != null) + { + return rsEnum; + } + + rsEnum = getRsEnum(id); + enumCache.put(id, rsEnum); + return rsEnum; + } } \ No newline at end of file diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/RSDecorativeObjectMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/RSDecorativeObjectMixin.java index f278609c9b..7ae4685ef3 100644 --- a/runelite-mixins/src/main/java/net/runelite/mixins/RSDecorativeObjectMixin.java +++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSDecorativeObjectMixin.java @@ -82,6 +82,29 @@ public abstract class RSDecorativeObjectMixin implements RSDecorativeObject return model; } + @Inject + private RSModel getModel2() + { + RSRenderable renderable = getRenderable2(); + if (renderable == null) + { + return null; + } + + RSModel model; + + if (renderable instanceof Model) + { + model = (RSModel) renderable; + } + else + { + model = renderable.getModel(); + } + + return model; + } + @Inject @Override public Area getClickbox() @@ -100,6 +123,20 @@ public abstract class RSDecorativeObjectMixin implements RSDecorativeObject return null; } - return model.getConvexHull(getX(), getY(), getOrientation()); + return model.getConvexHull(getX() + getXOffset(), getY() + getYOffset(), 0); + } + + @Inject + @Override + public Polygon getConvexHull2() + { + RSModel model = getModel2(); + + if (model == null) + { + return null; + } + + return model.getConvexHull(getX(), getY(), 0); } } diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/RSEnumMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/RSEnumMixin.java new file mode 100644 index 0000000000..06dd3ab2e8 --- /dev/null +++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSEnumMixin.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2019, Adam + * 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.mixins; + +import net.runelite.api.mixins.Inject; +import net.runelite.api.mixins.Mixin; +import net.runelite.rs.api.RSEnum; + +@Mixin(RSEnum.class) +public abstract class RSEnumMixin implements RSEnum +{ + @Inject + @Override + public int getIntValue(int key) + { + final int[] keys = getKeys(); + if (keys == null) + { + return getDefaultInt(); + } + + for (int i = 0; i < keys.length; ++i) + { + if (keys[i] == key) + { + final int[] values = getIntVals(); + return values[i]; + } + } + return getDefaultInt(); + } + + @Inject + @Override + public String getStringValue(int key) + { + final int[] keys = getKeys(); + if (keys == null) + { + return getDefaultString(); + } + + for (int i = 0; i < keys.length; ++i) + { + if (keys[i] == key) + { + final String[] values = getStringVals(); + return values[i]; + } + } + return getDefaultString(); + } +} diff --git a/runelite-script-assembler-plugin/pom.xml b/runelite-script-assembler-plugin/pom.xml index 5ce6ad3a0a..57ce1ecb31 100644 --- a/runelite-script-assembler-plugin/pom.xml +++ b/runelite-script-assembler-plugin/pom.xml @@ -29,7 +29,7 @@ net.runelite runelite-parent - 1.5.12-SNAPSHOT + 1.5.14-SNAPSHOT script-assembler-plugin diff --git a/runescape-api/pom.xml b/runescape-api/pom.xml index 10f3c0e190..4986a1d5fa 100644 --- a/runescape-api/pom.xml +++ b/runescape-api/pom.xml @@ -29,7 +29,7 @@ net.runelite runelite-parent - 1.5.12-SNAPSHOT + 1.5.14-SNAPSHOT net.runelite.rs diff --git a/runescape-api/src/main/java/net/runelite/rs/api/RSClient.java b/runescape-api/src/main/java/net/runelite/rs/api/RSClient.java index 04ac90d398..654e0048ea 100644 --- a/runescape-api/src/main/java/net/runelite/rs/api/RSClient.java +++ b/runescape-api/src/main/java/net/runelite/rs/api/RSClient.java @@ -785,6 +785,10 @@ public interface RSClient extends RSGameEngine, Client @Override RSNodeCache getWidgetSpriteCache(); + @Import("items") + @Override + RSNodeCache getItemCompositionCache(); + @Import("oculusOrbState") @Override int getOculusOrbState(); @@ -921,4 +925,11 @@ public interface RSClient extends RSGameEngine, Client @Import("endY") int getEndY(); + + @Import("spellSelected") + @Override + void setSpellSelected(boolean selected); + + @Import("getEnum") + RSEnum getRsEnum(int id); } diff --git a/runescape-api/src/main/java/net/runelite/rs/api/RSDecorativeObject.java b/runescape-api/src/main/java/net/runelite/rs/api/RSDecorativeObject.java index 1a53a49cb9..910c5acc1b 100644 --- a/runescape-api/src/main/java/net/runelite/rs/api/RSDecorativeObject.java +++ b/runescape-api/src/main/java/net/runelite/rs/api/RSDecorativeObject.java @@ -39,6 +39,12 @@ public interface RSDecorativeObject extends DecorativeObject @Import("y") int getY(); + @Import("offsetX") + int getXOffset(); + + @Import("offsetY") + int getYOffset(); + @Import("rotation") int getOrientation(); diff --git a/runescape-api/src/main/java/net/runelite/rs/api/RSEnum.java b/runescape-api/src/main/java/net/runelite/rs/api/RSEnum.java new file mode 100644 index 0000000000..d1a5273e5b --- /dev/null +++ b/runescape-api/src/main/java/net/runelite/rs/api/RSEnum.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2019, Adam + * 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.rs.api; + +import net.runelite.api.EnumComposition; +import net.runelite.mapping.Import; + +public interface RSEnum extends EnumComposition, RSCacheableNode +{ + @Import("keys") + int[] getKeys(); + + @Import("intVals") + @Override + int[] getIntVals(); + + @Import("stringVals") + @Override + String[] getStringVals(); + + @Import("defaultInt") + int getDefaultInt(); + + @Import("defaultString") + String getDefaultString(); +} diff --git a/runescape-api/src/main/java/net/runelite/rs/api/RSWidget.java b/runescape-api/src/main/java/net/runelite/rs/api/RSWidget.java index 75315b0514..0dd5b24d4f 100644 --- a/runescape-api/src/main/java/net/runelite/rs/api/RSWidget.java +++ b/runescape-api/src/main/java/net/runelite/rs/api/RSWidget.java @@ -328,6 +328,10 @@ public interface RSWidget extends Widget @Override void setOnMouseOverListener(Object... args); + @Import("onMouseRepeatListener") + @Override + void setOnMouseRepeatListener(Object... args); + @Import("onMouseLeaveListener") @Override void setOnMouseLeaveListener(Object... args); @@ -336,6 +340,14 @@ public interface RSWidget extends Widget @Override void setOnTimerListener(Object... args); + @Import("onTargetEnterListener") + @Override + void setOnTargetEnterListener(Object... args); + + @Import("onTargetLeaveListener") + @Override + void setOnTargetLeaveListener(Object... args); + @Import("fontId") @Override int getFontId(); @@ -435,4 +447,28 @@ public interface RSWidget extends Widget @Import("filled") @Override void setFilled(boolean filled); + + @Import("targetVerb") + @Override + String getTargetVerb(); + + @Import("targetVerb") + @Override + void setTargetVerb(String targetVerb); + + @Import("noClickThrough") + @Override + boolean getNoClickThrough(); + + @Import("noClickThrough") + @Override + void setNoClickThrough(boolean noClickThrough); + + @Import("noScrollThrough") + @Override + boolean getNoScrollThrough(); + + @Import("noScrollThrough") + @Override + void setNoScrollThrough(boolean noScrollThrough); }