update to latest and fix style

This commit is contained in:
Davis Cook
2019-02-25 18:51:20 -05:00
199 changed files with 4794 additions and 1310 deletions

View File

@@ -29,7 +29,7 @@
<parent> <parent>
<groupId>net.runelite</groupId> <groupId>net.runelite</groupId>
<artifactId>runelite-parent</artifactId> <artifactId>runelite-parent</artifactId>
<version>1.5.12-SNAPSHOT</version> <version>1.5.14-SNAPSHOT</version>
</parent> </parent>
<artifactId>cache-client</artifactId> <artifactId>cache-client</artifactId>

View File

@@ -28,7 +28,7 @@
<parent> <parent>
<groupId>net.runelite</groupId> <groupId>net.runelite</groupId>
<artifactId>runelite-parent</artifactId> <artifactId>runelite-parent</artifactId>
<version>1.5.12-SNAPSHOT</version> <version>1.5.14-SNAPSHOT</version>
</parent> </parent>
<name>Cache Updater</name> <name>Cache Updater</name>

2
cache/pom.xml vendored
View File

@@ -29,7 +29,7 @@
<parent> <parent>
<groupId>net.runelite</groupId> <groupId>net.runelite</groupId>
<artifactId>runelite-parent</artifactId> <artifactId>runelite-parent</artifactId>
<version>1.5.12-SNAPSHOT</version> <version>1.5.14-SNAPSHOT</version>
</parent> </parent>
<artifactId>cache</artifactId> <artifactId>cache</artifactId>

View File

@@ -28,7 +28,7 @@
<parent> <parent>
<groupId>net.runelite</groupId> <groupId>net.runelite</groupId>
<artifactId>runelite-parent</artifactId> <artifactId>runelite-parent</artifactId>
<version>1.5.12-SNAPSHOT</version> <version>1.5.14-SNAPSHOT</version>
</parent> </parent>
<name>Web API</name> <name>Web API</name>

View File

@@ -0,0 +1,78 @@
/*
* Copyright (c) 2019, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.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();
}
});
}
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright (c) 2019, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.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;
}

View File

@@ -24,6 +24,7 @@
*/ */
package net.runelite.http.api.loottracker; package net.runelite.http.api.loottracker;
import java.time.Instant;
import java.util.Collection; import java.util.Collection;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
@@ -37,4 +38,5 @@ public class LootRecord
private String eventId; private String eventId;
private LootRecordType type; private LootRecordType type;
private Collection<GameItem> drops; private Collection<GameItem> drops;
private Instant time;
} }

View File

@@ -35,9 +35,9 @@ import okhttp3.Request;
import okhttp3.Response; import okhttp3.Response;
@Slf4j @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() final HttpUrl url = RuneLiteAPI.getApiBase().newBuilder()
.addPathSegment("osb") .addPathSegment("osb")
@@ -59,7 +59,7 @@ public class GrandExchangeClient
} }
final InputStream in = response.body().byteStream(); 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) catch (JsonParseException ex)
{ {

View File

@@ -28,7 +28,7 @@ import java.time.Instant;
import lombok.Data; import lombok.Data;
@Data @Data
public class GrandExchangeResult public class OSBGrandExchangeResult
{ {
private int item_id; private int item_id;
private int buy_average; private int buy_average;

View File

@@ -25,10 +25,12 @@
package net.runelite.http.api.ws.messages.party; package net.runelite.http.api.ws.messages.party;
import java.util.UUID; import java.util.UUID;
import lombok.EqualsAndHashCode;
import lombok.Value; import lombok.Value;
import net.runelite.http.api.ws.WebsocketMessage; import net.runelite.http.api.ws.WebsocketMessage;
@Value @Value
@EqualsAndHashCode(callSuper = true)
public class Join extends WebsocketMessage public class Join extends WebsocketMessage
{ {
private final UUID partyId; private final UUID partyId;

View File

@@ -25,10 +25,12 @@
package net.runelite.http.api.ws.messages.party; package net.runelite.http.api.ws.messages.party;
import java.util.UUID; import java.util.UUID;
import lombok.EqualsAndHashCode;
import lombok.Value; import lombok.Value;
import net.runelite.http.api.ws.WebsocketMessage; import net.runelite.http.api.ws.WebsocketMessage;
@Value @Value
@EqualsAndHashCode(callSuper = true)
public class UserJoin extends WebsocketMessage public class UserJoin extends WebsocketMessage
{ {
private final UUID memberId; private final UUID memberId;

View File

@@ -25,10 +25,12 @@
package net.runelite.http.api.ws.messages.party; package net.runelite.http.api.ws.messages.party;
import java.util.UUID; import java.util.UUID;
import lombok.EqualsAndHashCode;
import lombok.Value; import lombok.Value;
import net.runelite.http.api.ws.WebsocketMessage; import net.runelite.http.api.ws.WebsocketMessage;
@Value @Value
@EqualsAndHashCode(callSuper = true)
public class UserPart extends WebsocketMessage public class UserPart extends WebsocketMessage
{ {
private final UUID memberId; private final UUID memberId;

View File

@@ -24,9 +24,11 @@
*/ */
package net.runelite.http.api.ws.messages.party; package net.runelite.http.api.ws.messages.party;
import lombok.EqualsAndHashCode;
import lombok.Value; import lombok.Value;
@Value @Value
@EqualsAndHashCode(callSuper = true)
public class UserSync extends PartyMemberMessage public class UserSync extends PartyMemberMessage
{ {
} }

View File

@@ -28,7 +28,7 @@
<parent> <parent>
<groupId>net.runelite</groupId> <groupId>net.runelite</groupId>
<artifactId>runelite-parent</artifactId> <artifactId>runelite-parent</artifactId>
<version>1.5.12-SNAPSHOT</version> <version>1.5.14-SNAPSHOT</version>
</parent> </parent>
<name>Web Service</name> <name>Web Service</name>
@@ -55,6 +55,10 @@
<artifactId>spring-boot-devtools</artifactId> <artifactId>spring-boot-devtools</artifactId>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.mapstruct</groupId> <groupId>org.mapstruct</groupId>
@@ -130,6 +134,11 @@
<version>3.7.0</version> <version>3.7.0</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
<dependencyManagement> <dependencyManagement>

View File

@@ -25,13 +25,11 @@
package net.runelite.http.service; package net.runelite.http.service;
import ch.qos.logback.classic.LoggerContext; import ch.qos.logback.classic.LoggerContext;
import com.google.common.base.Strings;
import java.io.IOException; import java.io.IOException;
import java.time.Instant; import java.time.Instant;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.servlet.ServletContext; import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener; import javax.servlet.ServletContextListener;
@@ -44,11 +42,17 @@ import okhttp3.Cache;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
import org.slf4j.ILoggerFactory; import org.slf4j.ILoggerFactory;
import org.slf4j.impl.StaticLoggerBinder; import org.slf4j.impl.StaticLoggerBinder;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication; 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.builder.SpringApplicationBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.support.SpringBootServletInitializer; import org.springframework.boot.web.support.SpringBootServletInitializer;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.jdbc.datasource.lookup.JndiDataSourceLookup;
import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.EnableScheduling;
import org.sql2o.Sql2o; import org.sql2o.Sql2o;
import org.sql2o.converters.Converter; import org.sql2o.converters.Converter;
@@ -56,6 +60,7 @@ import org.sql2o.quirks.NoQuirks;
@SpringBootApplication @SpringBootApplication
@EnableScheduling @EnableScheduling
@EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class})
@Slf4j @Slf4j
public class SpringBootWebApplication extends SpringBootServletInitializer 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 new DataSourceProperties();
return (Context) initCtx.lookup("java:comp/env"); }
@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") @Bean("Runelite SQL2O")
Sql2o sql2o() throws NamingException public Sql2o sql2o(@Qualifier("runelite") DataSource dataSource)
{ {
DataSource dataSource = (DataSource) getContext().lookup("jdbc/runelite"); return createSql2oFromDataSource(dataSource);
Map<Class, Converter> converters = new HashMap<>();
converters.put(Instant.class, new InstantConverter());
return new Sql2o(dataSource, new NoQuirks(converters));
} }
@Bean("Runelite Cache SQL2O") @Bean("Runelite Cache SQL2O")
Sql2o cacheSql2o() throws NamingException public Sql2o cacheSql2o(@Qualifier("runelite-cache") DataSource dataSource)
{ {
DataSource dataSource = (DataSource) getContext().lookup("jdbc/runelite-cache2"); return createSql2oFromDataSource(dataSource);
Map<Class, Converter> converters = new HashMap<>();
converters.put(Instant.class, new InstantConverter());
return new Sql2o(dataSource, new NoQuirks(converters));
} }
@Bean("Runelite XP Tracker SQL2O") @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"); return createSql2oFromDataSource(dataSource);
Map<Class, Converter> converters = new HashMap<>(); }
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<Class, Converter> converters = new HashMap<>();
converters.put(Instant.class, new InstantConverter()); converters.put(Instant.class, new InstantConverter());
return new Sql2o(dataSource, new NoQuirks(converters)); return new Sql2o(dataSource, new NoQuirks(converters));
} }

View File

@@ -24,22 +24,42 @@
*/ */
package net.runelite.http.service; package net.runelite.http.service;
import java.util.List;
import net.runelite.http.api.RuneLiteAPI;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType; 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.ContentNegotiationConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
/**
* Configure .js as application/json to trick Cloudflare into caching json responses
*/
@Configuration @Configuration
@EnableWebMvc @EnableWebMvc
public class SpringContentNegotiationConfigurer extends WebMvcConfigurerAdapter public class SpringWebMvcConfigurer extends WebMvcConfigurerAdapter
{ {
/**
* Configure .js as application/json to trick Cloudflare into caching json responses
*/
@Override @Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) public void configureContentNegotiation(ContentNegotiationConfigurer configurer)
{ {
configurer.mediaType("js", MediaType.APPLICATION_JSON); configurer.mediaType("js", MediaType.APPLICATION_JSON);
} }
/**
* Use GSON instead of Jackson for JSON serialization
* @param converters
*/
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> 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);
}
} }

View File

@@ -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.SessionEntry;
import net.runelite.http.service.account.beans.UserEntry; import net.runelite.http.service.account.beans.UserEntry;
import net.runelite.http.service.util.redis.RedisPool; 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.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@@ -241,12 +239,6 @@ public class AccountService
LoginResponse response = new LoginResponse(); LoginResponse response = new LoginResponse();
response.setUsername(username); response.setUsername(username);
WSService service = SessionManager.findSession(uuid);
if (service != null)
{
service.send(response);
}
try (Jedis jedis = jedisPool.getResource()) try (Jedis jedis = jedisPool.getResource())
{ {
jedis.publish("session." + uuid, websocketGson.toJson(response, WebsocketMessage.class)); jedis.publish("session." + uuid, websocketGson.toJson(response, WebsocketMessage.class));
@@ -276,10 +268,4 @@ public class AccountService
{ {
auth.handle(request, response); auth.handle(request, response);
} }
@RequestMapping("/wscount")
public int wscount()
{
return SessionManager.getCount();
}
} }

View File

@@ -41,6 +41,7 @@ import java.io.InputStream;
import java.security.InvalidKeyException; import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import net.runelite.cache.ConfigType; import net.runelite.cache.ConfigType;
@@ -233,6 +234,11 @@ public class CacheService
public List<ItemDefinition> getItems() throws IOException public List<ItemDefinition> getItems() throws IOException
{ {
CacheEntry cache = findMostRecent(); CacheEntry cache = findMostRecent();
if (cache == null)
{
return Collections.emptyList();
}
IndexEntry indexEntry = findIndexForCache(cache, IndexType.CONFIGS.getNumber()); IndexEntry indexEntry = findIndexForCache(cache, IndexType.CONFIGS.getNumber());
ArchiveEntry archiveEntry = findArchiveForIndex(indexEntry, ConfigType.ITEM.getId()); ArchiveEntry archiveEntry = findArchiveForIndex(indexEntry, ConfigType.ITEM.getId());
ArchiveFiles archiveFiles = getArchiveFiles(archiveEntry); ArchiveFiles archiveFiles = getArchiveFiles(archiveEntry);

View File

@@ -0,0 +1,102 @@
/*
* Copyright (c) 2019, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.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);
}
}

View File

@@ -24,28 +24,18 @@
*/ */
package net.runelite.http.service.config; package net.runelite.http.service.config;
import java.io.IOException;
import java.util.List; import java.util.List;
import javax.servlet.http.HttpServletRequest; import javax.annotation.Nullable;
import javax.servlet.http.HttpServletResponse;
import net.runelite.http.api.config.ConfigEntry; import net.runelite.http.api.config.ConfigEntry;
import net.runelite.http.api.config.Configuration; 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.Autowired;
import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.stereotype.Service;
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.sql2o.Connection; import org.sql2o.Connection;
import org.sql2o.Sql2o; import org.sql2o.Sql2o;
import org.sql2o.Sql2oException; import org.sql2o.Sql2oException;
@RestController @Service
@RequestMapping("/config")
public class ConfigService public class ConfigService
{ {
private static final String CREATE_CONFIG = "CREATE TABLE IF NOT EXISTS `config` (\n" 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;"; + " ADD CONSTRAINT `user_fk` FOREIGN KEY (`user`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;";
private final Sql2o sql2o; private final Sql2o sql2o;
private final AuthFilter auth;
@Autowired @Autowired
public ConfigService( public ConfigService(
@Qualifier("Runelite SQL2O") Sql2o sql2o, @Qualifier("Runelite SQL2O") Sql2o sql2o
AuthFilter auth
) )
{ {
this.sql2o = sql2o; this.sql2o = sql2o;
this.auth = auth;
try (Connection con = sql2o.open()) try (Connection con = sql2o.open())
{ {
@@ -87,71 +74,45 @@ public class ConfigService
} }
} }
@RequestMapping public Configuration get(int userId)
public Configuration get(HttpServletRequest request, HttpServletResponse response) throws IOException
{ {
SessionEntry session = auth.handle(request, response);
if (session == null)
{
return null;
}
List<ConfigEntry> config; List<ConfigEntry> config;
try (Connection con = sql2o.open()) try (Connection con = sql2o.open())
{ {
config = con.createQuery("select `key`, value from config where user = :user") config = con.createQuery("select `key`, value from config where user = :user")
.addParameter("user", session.getUser()) .addParameter("user", userId)
.executeAndFetch(ConfigEntry.class); .executeAndFetch(ConfigEntry.class);
} }
return new Configuration(config); return new Configuration(config);
} }
@RequestMapping(path = "/{key:.+}", method = PUT)
public void setKey( public void setKey(
HttpServletRequest request, int userId,
HttpServletResponse response, String key,
@PathVariable String key, @Nullable String value
@RequestBody(required = false) String value )
) throws IOException
{ {
SessionEntry session = auth.handle(request, response);
if (session == null)
{
return;
}
try (Connection con = sql2o.open()) 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") 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("key", key)
.addParameter("value", value != null ? value : "") .addParameter("value", value != null ? value : "")
.executeUpdate(); .executeUpdate();
} }
} }
@RequestMapping(path = "/{key:.+}", method = DELETE)
public void unsetKey( public void unsetKey(
HttpServletRequest request, int userId,
HttpServletResponse response, String key
@PathVariable String key )
) throws IOException
{ {
SessionEntry session = auth.handle(request, response);
if (session == null)
{
return;
}
try (Connection con = sql2o.open()) try (Connection con = sql2o.open())
{ {
con.createQuery("delete from config where user = :user and `key` = :key") con.createQuery("delete from config where user = :user and `key` = :key")
.addParameter("user", session.getUser()) .addParameter("user", userId)
.addParameter("key", key) .addParameter("key", key)
.executeUpdate(); .executeUpdate();
} }

View File

@@ -71,7 +71,7 @@ public class FeedController
} }
catch (IOException e) catch (IOException e)
{ {
log.warn(null, e); log.warn(e.getMessage());
} }
try try
@@ -80,7 +80,7 @@ public class FeedController
} }
catch (IOException e) catch (IOException e)
{ {
log.warn(null, e); log.warn(e.getMessage());
} }
try try
@@ -89,7 +89,7 @@ public class FeedController
} }
catch (IOException e) catch (IOException e)
{ {
log.warn(null, e); log.warn(e.getMessage());
} }
feedResult = new FeedResult(items); feedResult = new FeedResult(items);

View File

@@ -0,0 +1,111 @@
/*
* Copyright (c) 2019, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.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<GrandExchangeTrade> 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());
}
}

View File

@@ -0,0 +1,113 @@
/*
* Copyright (c) 2019, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.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<TradeEntry> 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();
}
}
}

View File

@@ -0,0 +1,31 @@
/*
* Copyright (c) 2019, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.http.service.ge;
enum TradeAction
{
BUY,
SELL;
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright (c) 2019, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.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;
}

View File

@@ -489,10 +489,16 @@ public class ItemService
public void reloadItems() throws IOException public void reloadItems() throws IOException
{ {
List<ItemDefinition> items = cacheService.getItems(); List<ItemDefinition> items = cacheService.getItems();
if (items.isEmpty())
{
log.warn("Failed to load any items from cache, item price updating will be disabled");
}
tradeableItems = items.stream() tradeableItems = items.stream()
.filter(item -> item.isTradeable) .filter(item -> item.isTradeable)
.mapToInt(item -> item.id) .mapToInt(item -> item.id)
.toArray(); .toArray();
log.debug("Loaded {} tradeable items", tradeableItems.length); log.debug("Loaded {} tradeable items", tradeableItems.length);
} }

View File

@@ -66,7 +66,7 @@ public class LootTrackerController
} }
@RequestMapping @RequestMapping
public Collection<LootRecord> getLootRecords(HttpServletRequest request, HttpServletResponse response, @RequestParam(value = "count", defaultValue = "1024") int count) throws IOException public Collection<LootRecord> 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); SessionEntry e = auth.handle(request, response);
if (e == null) if (e == null)
@@ -75,7 +75,7 @@ public class LootTrackerController
return null; return null;
} }
return service.get(e.getUser(), count); return service.get(e.getUser(), count, start);
} }
@DeleteMapping @DeleteMapping

View File

@@ -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_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 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 = "DELETE FROM kills WHERE accountId = :accountId";
private static final String DELETE_LOOT_ACCOUNT_EVENTID = "DELETE FROM kills WHERE accountId = :accountId AND eventId = :eventId"; 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<LootRecord> get(int accountId, int limit) public Collection<LootRecord> get(int accountId, int limit, int offset)
{ {
List<LootResult> lootResults; List<LootResult> lootResults;
@@ -128,6 +128,7 @@ public class LootTrackerService
lootResults = con.createQuery(SELECT_LOOT_QUERY) lootResults = con.createQuery(SELECT_LOOT_QUERY)
.addParameter("accountId", accountId) .addParameter("accountId", accountId)
.addParameter("limit", limit) .addParameter("limit", limit)
.addParameter("offset", offset)
.executeAndFetch(LootResult.class); .executeAndFetch(LootResult.class);
} }
@@ -141,7 +142,7 @@ public class LootTrackerService
{ {
if (!gameItems.isEmpty()) 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); lootRecords.add(lootRecord);
gameItems = new ArrayList<>(); gameItems = new ArrayList<>();
@@ -156,7 +157,7 @@ public class LootTrackerService
if (!gameItems.isEmpty()) 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); lootRecords.add(lootRecord);
} }

View File

@@ -35,12 +35,12 @@ import org.springframework.web.bind.annotation.RestController;
@RestController @RestController
@RequestMapping("/osb/ge") @RequestMapping("/osb/ge")
public class GrandExchangeController public class OSBGrandExchangeController
{ {
private final GrandExchangeService grandExchangeService; private final OSBGrandExchangeService grandExchangeService;
@Autowired @Autowired
public GrandExchangeController(GrandExchangeService grandExchangeService) public OSBGrandExchangeController(OSBGrandExchangeService grandExchangeService)
{ {
this.grandExchangeService = grandExchangeService; this.grandExchangeService = grandExchangeService;
} }

View File

@@ -40,7 +40,7 @@ import org.sql2o.Sql2o;
@Service @Service
@Slf4j @Slf4j
public class GrandExchangeService public class OSBGrandExchangeService
{ {
private static final String CREATE_GRAND_EXCHANGE_PRICES = "CREATE TABLE IF NOT EXISTS `osb_ge` (\n" private static final String CREATE_GRAND_EXCHANGE_PRICES = "CREATE TABLE IF NOT EXISTS `osb_ge` (\n"
+ " `item_id` int(11) NOT NULL,\n" + " `item_id` int(11) NOT NULL,\n"
@@ -56,7 +56,7 @@ public class GrandExchangeService
private final Sql2o sql2o; private final Sql2o sql2o;
@Autowired @Autowired
public GrandExchangeService(@Qualifier("Runelite SQL2O") Sql2o sql2o) public OSBGrandExchangeService(@Qualifier("Runelite SQL2O") Sql2o sql2o)
{ {
this.sql2o = sql2o; this.sql2o = sql2o;

View File

@@ -1,100 +0,0 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.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());
}
}
}

View File

@@ -38,6 +38,8 @@ public interface XpMapper
XpData xpEntityToXpData(XpEntity xpEntity); XpData xpEntityToXpData(XpEntity xpEntity);
@Mapping(target = "time", ignore = true)
@Mapping(source = "attack.experience", target = "attack_xp") @Mapping(source = "attack.experience", target = "attack_xp")
@Mapping(source = "defence.experience", target = "defence_xp") @Mapping(source = "defence.experience", target = "defence_xp")
@Mapping(source = "strength.experience", target = "strength_xp") @Mapping(source = "strength.experience", target = "strength_xp")

View File

@@ -29,8 +29,8 @@ import com.google.common.hash.Funnels;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.time.Duration; import java.time.Duration;
import java.time.Instant; import java.time.Instant;
import java.util.ArrayDeque;
import java.util.Queue; import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import net.runelite.http.api.hiscore.HiscoreEndpoint; import net.runelite.http.api.hiscore.HiscoreEndpoint;
@@ -50,8 +50,8 @@ import org.sql2o.Sql2o;
@Slf4j @Slf4j
public class XpTrackerService public class XpTrackerService
{ {
private static final int QUEUE_LIMIT = 100_000; private static final int QUEUE_LIMIT = 32768;
private static final Duration UPDATE_TIME = Duration.ofMinutes(5); private static final int BLOOMFILTER_EXPECTED_INSERTIONS = 100_000;
@Autowired @Autowired
@Qualifier("Runelite XP Tracker SQL2O") @Qualifier("Runelite XP Tracker SQL2O")
@@ -60,7 +60,7 @@ public class XpTrackerService
@Autowired @Autowired
private HiscoreService hiscoreService; private HiscoreService hiscoreService;
private final Queue<String> usernameUpdateQueue = new ConcurrentLinkedDeque<>(); private final Queue<String> usernameUpdateQueue = new ArrayDeque<>();
private BloomFilter<String> usernameFilter = createFilter(); private BloomFilter<String> usernameFilter = createFilter();
public void update(String username) throws ExecutionException public void update(String username) throws ExecutionException
@@ -76,13 +76,31 @@ public class XpTrackerService
return; return;
} }
if (usernameUpdateQueue.size() >= QUEUE_LIMIT) try (Connection con = sql2o.open())
{ {
log.warn("Username update queue is full ({})", QUEUE_LIMIT); PlayerEntity playerEntity = findOrCreatePlayer(con, username);
return; 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); usernameFilter.put(username);
} }
@@ -104,13 +122,6 @@ public class XpTrackerService
log.debug("Hiscore for {} already up to date", username); log.debug("Hiscore for {} already up to date", username);
return; 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," 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("construction_rank", hiscoreResult.getConstruction().getRank())
.addParameter("overall_rank", hiscoreResult.getOverall().getRank()) .addParameter("overall_rank", hiscoreResult.getOverall().getRank())
.executeUpdate(); .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.setId(id);
playerEntity.setName(username); playerEntity.setName(username);
playerEntity.setTracked_since(now); playerEntity.setTracked_since(now);
playerEntity.setLast_updated(now);
return playerEntity; return playerEntity;
} }
@@ -220,18 +237,21 @@ public class XpTrackerService
@Scheduled(fixedDelay = 1000) @Scheduled(fixedDelay = 1000)
public void update() throws ExecutionException public void update() throws ExecutionException
{ {
String next = usernameUpdateQueue.poll(); String next;
synchronized (usernameUpdateQueue)
{
next = usernameUpdateQueue.poll();
}
if (next == null) if (next == null)
{ {
return; return;
} }
HiscoreResult hiscoreResult = hiscoreService.lookupUsername(next, HiscoreEndpoint.NORMAL); update(next);
update(next, hiscoreResult);
} }
@Scheduled(fixedDelay = 3 * 60 * 60 * 1000) // 3 hours @Scheduled(fixedDelay = 6 * 60 * 60 * 1000) // 6 hours
public void clearFilter() public void clearFilter()
{ {
usernameFilter = createFilter(); usernameFilter = createFilter();
@@ -241,14 +261,47 @@ public class XpTrackerService
{ {
final BloomFilter<String> filter = BloomFilter.create( final BloomFilter<String> filter = BloomFilter.create(
Funnels.stringFunnel(Charset.defaultCharset()), 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; 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);
}
}
} }

View File

@@ -33,4 +33,6 @@ public class PlayerEntity
private Integer id; private Integer id;
private String name; private String name;
private Instant tracked_since; private Instant tracked_since;
private Instant last_updated;
private Integer rank;
} }

View File

@@ -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

View File

@@ -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 -- 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_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
@@ -26,6 +26,8 @@ CREATE TABLE `player` (
`id` int(11) NOT NULL AUTO_INCREMENT, `id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(32) NOT NULL, `name` varchar(32) NOT NULL,
`tracked_since` timestamp NOT NULL DEFAULT current_timestamp(), `tracked_since` timestamp NOT NULL DEFAULT current_timestamp(),
`last_updated` timestamp NOT NULL DEFAULT current_timestamp(),
`rank` int(11) DEFAULT NULL,
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
UNIQUE KEY `name` (`name`) UNIQUE KEY `name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1; ) ENGINE=InnoDB DEFAULT CHARSET=latin1;
@@ -116,7 +118,7 @@ CREATE TABLE `xp` (
`overall_rank` int(11) NOT NULL, `overall_rank` int(11) NOT NULL,
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
UNIQUE KEY `player_time` (`player`,`time`), UNIQUE KEY `player_time` (`player`,`time`),
INDEX `idx_time` (`time`), KEY `idx_time` (`time`),
CONSTRAINT `fk_player` FOREIGN KEY (`player`) REFERENCES `player` (`id`) CONSTRAINT `fk_player` FOREIGN KEY (`player`) REFERENCES `player` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1; ) ENGINE=InnoDB DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */; /*!40101 SET character_set_client = @saved_cs_client */;
@@ -130,4 +132,4 @@ CREATE TABLE `xp` (
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; /*!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

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2017, Adam <Adam@sigterm.info> * Copyright (c) 2019, Adam <Adam@sigterm.info>
* All rights reserved. * All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
@@ -24,54 +24,20 @@
*/ */
package net.runelite.http.service; 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.Ignore;
import org.junit.Test; import org.junit.Test;
import org.springframework.boot.SpringApplication; 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 public class SpringBootWebApplicationTest
{ {
@Bean("Runelite SQL2O")
Sql2o sql2o()
{
Map<Class, Converter> 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<Class, Converter> 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<Class, Converter> 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 @Test
@Ignore @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 (;;) for (;;)
{ {
Thread.sleep(100L); Thread.sleep(100L);

View File

@@ -0,0 +1,82 @@
/*
* Copyright (c) 2019, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.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"));
}
}

View File

@@ -0,0 +1,92 @@
/*
* Copyright (c) 2019, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.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());
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -28,7 +28,7 @@
<groupId>net.runelite</groupId> <groupId>net.runelite</groupId>
<artifactId>runelite-parent</artifactId> <artifactId>runelite-parent</artifactId>
<version>1.5.12-SNAPSHOT</version> <version>1.5.14-SNAPSHOT</version>
<packaging>pom</packaging> <packaging>pom</packaging>
<name>RuneLite</name> <name>RuneLite</name>

View File

@@ -29,7 +29,7 @@
<parent> <parent>
<groupId>net.runelite</groupId> <groupId>net.runelite</groupId>
<artifactId>runelite-parent</artifactId> <artifactId>runelite-parent</artifactId>
<version>1.5.12-SNAPSHOT</version> <version>1.5.14-SNAPSHOT</version>
</parent> </parent>
<artifactId>protocol-api</artifactId> <artifactId>protocol-api</artifactId>

View File

@@ -25,8 +25,10 @@
package net.runelite.protocol.api.handshake; package net.runelite.protocol.api.handshake;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode;
@Data @Data
@EqualsAndHashCode(callSuper = true)
public class LoginHandshakePacket extends HandshakePacket public class LoginHandshakePacket extends HandshakePacket
{ {

View File

@@ -26,9 +26,11 @@ package net.runelite.protocol.api.handshake;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
@Data @Data
@EqualsAndHashCode(callSuper = true)
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
public class UpdateHandshakePacket extends HandshakePacket public class UpdateHandshakePacket extends HandshakePacket

View File

@@ -29,7 +29,7 @@
<parent> <parent>
<groupId>net.runelite</groupId> <groupId>net.runelite</groupId>
<artifactId>runelite-parent</artifactId> <artifactId>runelite-parent</artifactId>
<version>1.5.12-SNAPSHOT</version> <version>1.5.14-SNAPSHOT</version>
</parent> </parent>
<artifactId>protocol</artifactId> <artifactId>protocol</artifactId>

View File

@@ -29,7 +29,7 @@
<parent> <parent>
<groupId>net.runelite</groupId> <groupId>net.runelite</groupId>
<artifactId>runelite-parent</artifactId> <artifactId>runelite-parent</artifactId>
<version>1.5.12-SNAPSHOT</version> <version>1.5.14-SNAPSHOT</version>
</parent> </parent>
<artifactId>runelite-api</artifactId> <artifactId>runelite-api</artifactId>

View File

@@ -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 HERBLORE_POTIONMAKING = 363; //used for both herb and secondary
public static final int MAGIC_CHARGING_ORBS = 726; public static final int MAGIC_CHARGING_ORBS = 726;
public static final int MAGIC_MAKE_TABLET = 4068; 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 BURYING_BONES = 827;
public static final int USING_GILDED_ALTAR = 3705; public static final int USING_GILDED_ALTAR = 3705;
public static final int LOOKING_INTO = 832; public static final int LOOKING_INTO = 832;

View File

@@ -1569,4 +1569,16 @@ public interface Client extends GameEngine
int getRasterizer3D_clipMidY2(); 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); 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);
} }

View File

@@ -38,6 +38,7 @@ public interface DecorativeObject extends TileObject
* @see net.runelite.api.model.Jarvis * @see net.runelite.api.model.Jarvis
*/ */
Polygon getConvexHull(); Polygon getConvexHull();
Polygon getConvexHull2();
Renderable getRenderable(); Renderable getRenderable();
Renderable getRenderable2(); Renderable getRenderable2();

View File

@@ -0,0 +1,36 @@
/*
* Copyright (c) 2019, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.api;
public interface EnumComposition
{
int[] getIntVals();
String[] getStringVals();
int getIntValue(int key);
String getStringValue(int key);
}

View File

@@ -45,6 +45,24 @@ public final class ScriptID
*/ */
public static final int CHATBOX_INPUT = 96; public static final int CHATBOX_INPUT = 96;
/**
* Opens the Private Message chat interface
*
* Jagex refers to this script as {@code meslayer_mode6}
* <ul>
* <li> String Player to send private message to</li>
* </ul>
*/
public static final int OPEN_PRIVATE_MESSAGE_INTERFACE = 107;
/**
* Rebuilds the text input widget inside the chat interface
* <ul>
* <li> String Message Prefix. Only used inside the GE search interfaces
* </ul>
*/
public static final int CHAT_TEXT_INPUT_REBUILD = 222;
/** /**
* Layouts the bank widgets * Layouts the bank widgets
* *

View File

@@ -1172,7 +1172,7 @@ public final class SpriteID
public static final int MINIMAP_ORB_XP_ACTIVATED = 1197; 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_HOVERED = 1198;
public static final int MINIMAP_ORB_XP_ACTIVATED_HOVERED = 1199; 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 OPTIONS_ZOOM_SLIDER_THUMB = 1201;
public static final int EMOTE_SIT_UP = 1202; public static final int EMOTE_SIT_UP = 1202;
public static final int EMOTE_STAR_JUMP = 1203; public static final int EMOTE_STAR_JUMP = 1203;

View File

@@ -36,6 +36,7 @@ public enum VarClientStr
{ {
CHATBOX_TYPED_TEXT(1), CHATBOX_TYPED_TEXT(1),
INPUT_TEXT(22), INPUT_TEXT(22),
PRIVATE_MESSAGE_TARGET(23),
RECENT_CLAN_CHAT(129); RECENT_CLAN_CHAT(129);
private final int index; private final int index;

View File

@@ -37,6 +37,10 @@ public enum VarPlayer
ATTACK_STYLE(43), ATTACK_STYLE(43),
QUEST_POINTS(101), QUEST_POINTS(101),
IS_POISONED(102), IS_POISONED(102),
/**
* Seems to start at 50(10 splash) and goes down by 1 every 30 seconds
*/
DISEASE_VALUE(456),
BANK_TAB(115), BANK_TAB(115),

View File

@@ -25,6 +25,10 @@
*/ */
package net.runelite.api.coords; 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 lombok.Value;
import net.runelite.api.Client; import net.runelite.api.Client;
import static net.runelite.api.Constants.CHUNK_SIZE; 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 client the client
* @param localPoint the local coordinate * @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<WorldPoint> 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<WorldPoint> 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 * Rotate the coordinates in the chunk according to chunk rotation
* *

View File

@@ -38,6 +38,7 @@ public enum InputType
RUNELITE_CHATBOX_PANEL(-3), RUNELITE_CHATBOX_PANEL(-3),
RUNELITE(-2), RUNELITE(-2),
NONE(0), NONE(0),
PRIVATE_MESSAGE(6),
SEARCH(11); SEARCH(11);
private final int type; private final int type;

View File

@@ -79,6 +79,7 @@ public interface Widget
/** /**
* Gets the current click configuration of the widget. * Gets the current click configuration of the widget.
* @see WidgetConfig
* *
* @see WidgetConfig * @see WidgetConfig
*/ */
@@ -551,6 +552,13 @@ public interface Widget
*/ */
void setOnMouseOverListener(Object... args); 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 * Sets a script to be ran when the mouse leaves the widget bounds
* *
@@ -565,6 +573,20 @@ public interface Widget
*/ */
void setOnTimerListener(Object... args); 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 * If this widget has any listeners on it
*/ */
@@ -769,4 +791,34 @@ public interface Widget
* Sets if the rectangle is filled or just stroked * Sets if the rectangle is filled or just stroked
*/ */
void setFilled(boolean filled); 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);
} }

View File

@@ -24,6 +24,8 @@
*/ */
package net.runelite.api.widgets; package net.runelite.api.widgets;
import net.runelite.api.MenuAction;
/** /**
* Utility class used for defining options to be used on the click mask * Utility class used for defining options to be used on the click mask
* of a {@link Widget}. * of a {@link Widget}.
@@ -36,12 +38,61 @@ public class WidgetConfig
* Enables displaying a ninth option on a menu. * Enables displaying a ninth option on a menu.
*/ */
public static final int SHOW_MENU_OPTION_NINE = 1 << 9; 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. * Controls whether or not a widget can have another dragged onto it.
*/ */
public static final int DRAG_ON = 1 << 17; public static final int DRAG_ON = 1 << 17;
/** /**
* Controls whether or not a widget can be dragged around. * Controls whether or not a widget can be dragged around.
*/ */
public static final int DRAG = 1 << 20; 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;
} }

View File

@@ -125,6 +125,8 @@ public class WidgetID
public static final int SKOTIZO_GROUP_ID = 308; public static final int SKOTIZO_GROUP_ID = 308;
public static final int ENTERING_HOUSE_GROUP_ID = 71; public static final int ENTERING_HOUSE_GROUP_ID = 71;
public static final int FULLSCREEN_MAP_GROUP_ID = 165; 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 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 TOGGLE_RUN_ORB = 22; // Has the "Toggle run" name
static final int RUN_ORB_TEXT = 23; static final int RUN_ORB_TEXT = 23;
static final int SPEC_ORB = 28; static final int SPEC_ORB = 28;
static final int WORLDMAP_ORB = 40;
} }
static class LoginClickToPlayScreen static class LoginClickToPlayScreen
@@ -747,4 +750,11 @@ public class WidgetID
{ {
static final int ROOT = 25; 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;
}
} }

View File

@@ -159,6 +159,7 @@ public enum WidgetInfo
MINIMAP_RUN_ORB_TEXT(WidgetID.MINIMAP_GROUP_ID, WidgetID.Minimap.RUN_ORB_TEXT), 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_HEALTH_ORB(WidgetID.MINIMAP_GROUP_ID, WidgetID.Minimap.HEALTH_ORB),
MINIMAP_SPEC_ORB(WidgetID.MINIMAP_GROUP_ID, WidgetID.Minimap.SPEC_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(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), 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), 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 groupId;
private final int childId; private final int childId;

View File

@@ -27,9 +27,12 @@ package net.runelite.api.widgets;
public final class WidgetType public final class WidgetType
{ {
public static final int LAYER = 0; public static final int LAYER = 0;
public static final int INVENTORY = 2;
public static final int RECTANGLE = 3; public static final int RECTANGLE = 3;
public static final int TEXT = 4; public static final int TEXT = 4;
public static final int GRAPHIC = 5; public static final int GRAPHIC = 5;
public static final int MODEL = 6; 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; public static final int LINE = 9;
} }

View File

@@ -29,7 +29,7 @@
<parent> <parent>
<groupId>net.runelite</groupId> <groupId>net.runelite</groupId>
<artifactId>runelite-parent</artifactId> <artifactId>runelite-parent</artifactId>
<version>1.5.12-SNAPSHOT</version> <version>1.5.14-SNAPSHOT</version>
</parent> </parent>
<artifactId>client</artifactId> <artifactId>client</artifactId>

View File

@@ -183,9 +183,9 @@ public class RuneLite
System.exit(0); 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; boolean assertions = false;
assert assertions = true; assert assertions = true;

View File

@@ -43,8 +43,11 @@ import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy; import java.lang.reflect.Proxy;
import java.nio.channels.FileLock; import java.nio.channels.FileLock;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.time.Instant; import java.time.Instant;
import java.util.Arrays; import java.util.Arrays;
import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -70,6 +73,7 @@ import net.runelite.http.api.config.Configuration;
public class ConfigManager public class ConfigManager
{ {
private static final String SETTINGS_FILE_NAME = "settings.properties"; private static final String SETTINGS_FILE_NAME = "settings.properties";
private static final DateFormat TIME_FORMAT = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss");
@Inject @Inject
EventBus eventBus; EventBus eventBus;
@@ -111,12 +115,17 @@ public class ConfigManager
load(); // load profile specific config load(); // load profile specific config
} }
private File getLocalPropertiesFile()
{
return new File(RuneLite.RUNELITE_DIR, SETTINGS_FILE_NAME);
}
private File getPropertiesFile() private File getPropertiesFile()
{ {
// Sessions that aren't logged in have no username // Sessions that aren't logged in have no username
if (session == null || session.getUsername() == null) if (session == null || session.getUsername() == null)
{ {
return new File(RuneLite.RUNELITE_DIR, SETTINGS_FILE_NAME); return getLocalPropertiesFile();
} }
else else
{ {
@@ -158,7 +167,13 @@ public class ConfigManager
for (ConfigEntry entry : configuration.getConfig()) for (ConfigEntry entry : configuration.getConfig())
{ {
log.debug("Loading configuration value from client {}: {}", entry.getKey(), entry.getValue()); 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 groupName = split[0];
final String key = split[1]; final String key = split[1];
final String value = entry.getValue(); final String value = entry.getValue();
@@ -174,7 +189,7 @@ public class ConfigManager
try try
{ {
saveToFile(); saveToFile(propertiesFile);
log.debug("Updated configuration on disk with the latest version"); 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<String, String> 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() private synchronized void loadFromFile()
{ {
properties.clear(); 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(); propertiesFile.getParentFile().mkdirs();
@@ -294,8 +378,6 @@ public class ConfigManager
public void setConfiguration(String groupName, String key, String value) 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); String oldValue = (String) properties.setProperty(groupName + "." + key, value);
if (Objects.equals(oldValue, value)) if (Objects.equals(oldValue, value))
@@ -303,6 +385,8 @@ public class ConfigManager
return; return;
} }
log.debug("Setting configuration value for {}.{} to {}", groupName, key, value);
synchronized (pendingChanges) synchronized (pendingChanges)
{ {
pendingChanges.put(groupName + "." + key, value); pendingChanges.put(groupName + "." + key, value);
@@ -312,7 +396,7 @@ public class ConfigManager
{ {
try try
{ {
saveToFile(); saveToFile(propertiesFile);
} }
catch (IOException ex) catch (IOException ex)
{ {
@@ -337,8 +421,6 @@ public class ConfigManager
public void unsetConfiguration(String groupName, String key) public void unsetConfiguration(String groupName, String key)
{ {
log.debug("Unsetting configuration value for {}.{}", groupName, key);
String oldValue = (String) properties.remove(groupName + "." + key); String oldValue = (String) properties.remove(groupName + "." + key);
if (oldValue == null) if (oldValue == null)
@@ -346,6 +428,8 @@ public class ConfigManager
return; return;
} }
log.debug("Unsetting configuration value for {}.{}", groupName, key);
synchronized (pendingChanges) synchronized (pendingChanges)
{ {
pendingChanges.put(groupName + "." + key, null); pendingChanges.put(groupName + "." + key, null);
@@ -355,7 +439,7 @@ public class ConfigManager
{ {
try try
{ {
saveToFile(); saveToFile(propertiesFile);
} }
catch (IOException ex) catch (IOException ex)
{ {

View File

@@ -78,7 +78,7 @@ public class DiscordService implements AutoCloseable
discordRPC = DiscordRPC.INSTANCE; discordRPC = DiscordRPC.INSTANCE;
discordEventHandlers = new DiscordEventHandlers(); discordEventHandlers = new DiscordEventHandlers();
} }
catch (UnsatisfiedLinkError e) catch (Error e)
{ {
log.warn("Failed to load Discord library, Discord support will be disabled."); log.warn("Failed to load Discord library, Discord support will be disabled.");
} }
@@ -150,9 +150,12 @@ public class DiscordService implements AutoCloseable
? "default" ? "default"
: discordPresence.getLargeImageKey(); : discordPresence.getLargeImageKey();
discordRichPresence.largeImageText = discordPresence.getLargeImageText(); discordRichPresence.largeImageText = discordPresence.getLargeImageText();
discordRichPresence.smallImageKey = Strings.isNullOrEmpty(discordPresence.getSmallImageKey())
? "default" if (!Strings.isNullOrEmpty(discordPresence.getSmallImageKey()))
: discordPresence.getSmallImageKey(); {
discordRichPresence.smallImageKey = discordPresence.getSmallImageKey();
}
discordRichPresence.smallImageText = discordPresence.getSmallImageText(); discordRichPresence.smallImageText = discordPresence.getSmallImageText();
discordRichPresence.partyId = discordPresence.getPartyId(); discordRichPresence.partyId = discordPresence.getPartyId();
discordRichPresence.partySize = discordPresence.getPartySize(); discordRichPresence.partySize = discordPresence.getPartySize();

View File

@@ -25,8 +25,10 @@
package net.runelite.client.events; package net.runelite.client.events;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode;
@Data @Data
@EqualsAndHashCode(callSuper = true)
public abstract class ChatboxInput extends ChatInput public abstract class ChatboxInput extends ChatInput
{ {
private final String value; private final String value;

View File

@@ -25,8 +25,10 @@
package net.runelite.client.events; package net.runelite.client.events;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode;
@Data @Data
@EqualsAndHashCode(callSuper = true)
public abstract class PrivateMessageInput extends ChatInput public abstract class PrivateMessageInput extends ChatInput
{ {
private final String target; private final String target;

View File

@@ -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;
}
}

View File

@@ -256,6 +256,15 @@ public class ItemManager
itemCompositions.put(event.getItemComposition().getId(), event.getItemComposition()); 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 * Look up an item's price
* *

View File

@@ -28,6 +28,7 @@ import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheBuilder;
import com.google.inject.Inject; import com.google.inject.Inject;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.util.Map;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.function.Consumer; import java.util.function.Consumer;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@@ -36,11 +37,14 @@ import javax.swing.ImageIcon;
import javax.swing.JButton; import javax.swing.JButton;
import javax.swing.JLabel; import javax.swing.JLabel;
import javax.swing.SwingUtilities; import javax.swing.SwingUtilities;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.Client; import net.runelite.api.Client;
import net.runelite.api.GameState; import net.runelite.api.GameState;
import net.runelite.api.SpritePixels; import net.runelite.api.SpritePixels;
import net.runelite.client.callback.ClientThread; import net.runelite.client.callback.ClientThread;
import net.runelite.client.util.ImageUtil;
@Slf4j
@Singleton @Singleton
public class SpriteManager public class SpriteManager
{ {
@@ -127,4 +131,36 @@ public class SpriteManager
}); });
}); });
} }
public void addSpriteOverrides(SpriteOverride[] add)
{
if (add.length <= 0)
{
return;
}
clientThread.invokeLater(() ->
{
Map<Integer, SpritePixels> 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<Integer, SpritePixels> overrides = client.getSpriteOverrides();
for (SpriteOverride o : remove)
{
overrides.remove(o.getSpriteId());
}
});
}
} }

View File

@@ -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();
}

View File

@@ -24,7 +24,10 @@
*/ */
package net.runelite.client.game.chatbox; package net.runelite.client.game.chatbox;
import com.google.common.base.Strings;
import com.google.common.primitives.Ints;
import com.google.inject.Inject; import com.google.inject.Inject;
import java.awt.Point;
import java.awt.Rectangle; import java.awt.Rectangle;
import java.awt.Toolkit; import java.awt.Toolkit;
import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.DataFlavor;
@@ -33,21 +36,25 @@ import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.event.KeyEvent; import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent; import java.awt.event.MouseEvent;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.IntPredicate; import java.util.function.IntPredicate;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.function.ToIntFunction; import java.util.function.ToIntFunction;
import java.util.regex.Pattern;
import javax.swing.SwingUtilities; import javax.swing.SwingUtilities;
import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import net.runelite.api.FontTypeFace;
import net.runelite.api.FontID; 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.JavaScriptCallback;
import net.runelite.api.widgets.Widget; import net.runelite.api.widgets.Widget;
import net.runelite.api.widgets.WidgetPositionMode; import net.runelite.api.widgets.WidgetPositionMode;
import net.runelite.api.widgets.WidgetSizeMode; import net.runelite.api.widgets.WidgetSizeMode;
import net.runelite.api.widgets.WidgetTextAlignment; import net.runelite.api.widgets.WidgetTextAlignment;
import net.runelite.api.widgets.WidgetType;
import net.runelite.client.callback.ClientThread; import net.runelite.client.callback.ClientThread;
import net.runelite.client.input.KeyListener; import net.runelite.client.input.KeyListener;
import net.runelite.client.input.MouseListener; import net.runelite.client.input.MouseListener;
@@ -57,6 +64,7 @@ import net.runelite.client.util.Text;
public class ChatboxTextInput extends ChatboxInput implements KeyListener, MouseListener public class ChatboxTextInput extends ChatboxInput implements KeyListener, MouseListener
{ {
private static final int CURSOR_FLASH_RATE_MILLIS = 1000; 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 ChatboxPanelManager chatboxPanelManager;
private final ClientThread clientThread; private final ClientThread clientThread;
@@ -66,13 +74,24 @@ public class ChatboxTextInput extends ChatboxInput implements KeyListener, Mouse
return i -> i >= 32 && i < 127; return i -> i >= 32 && i < 127;
} }
@AllArgsConstructor
private static class Line
{
private final int start;
private final int end;
private final String text;
}
@Getter @Getter
private String prompt; private String prompt;
@Getter
private int lines;
private StringBuffer value = new StringBuffer(); private StringBuffer value = new StringBuffer();
@Getter @Getter
private int cursor = 0; private int cursorStart = 0;
@Getter @Getter
private int cursorEnd = 0; private int cursorEnd = 0;
@@ -95,12 +114,14 @@ public class ChatboxTextInput extends ChatboxInput implements KeyListener, Mouse
@Getter @Getter
private int fontID = FontID.QUILL_8; private int fontID = FontID.QUILL_8;
// This is a lambda so I can have atomic updates for it's captures @Getter
private ToIntFunction<MouseEvent> getCharOffset = null;
private Predicate<MouseEvent> isInBounds = null;
private boolean built = false; private boolean built = false;
// These are lambdas for atomic updates
private Predicate<MouseEvent> isInBounds = null;
private ToIntFunction<Integer> getLineOffset = null;
private ToIntFunction<Point> getPointCharOffset = null;
@Inject @Inject
protected ChatboxTextInput(ChatboxPanelManager chatboxPanelManager, ClientThread clientThread) protected ChatboxTextInput(ChatboxPanelManager chatboxPanelManager, ClientThread clientThread)
{ {
@@ -108,6 +129,16 @@ public class ChatboxTextInput extends ChatboxInput implements KeyListener, Mouse
this.clientThread = clientThread; this.clientThread = clientThread;
} }
public ChatboxTextInput lines(int lines)
{
this.lines = lines;
if (built)
{
clientThread.invoke(this::update);
}
return this;
}
public ChatboxTextInput prompt(String prompt) public ChatboxTextInput prompt(String prompt)
{ {
this.prompt = prompt; this.prompt = prompt;
@@ -157,7 +188,7 @@ public class ChatboxTextInput extends ChatboxInput implements KeyListener, Mouse
end = v; end = v;
} }
this.cursor = start; this.cursorStart = start;
this.cursorEnd = end; this.cursorEnd = end;
if (built) if (built)
@@ -209,7 +240,6 @@ public class ChatboxTextInput extends ChatboxInput implements KeyListener, Mouse
protected void update() protected void update()
{ {
this.built = true;
Widget container = chatboxPanelManager.getContainerWidget(); Widget container = chatboxPanelManager.getContainerWidget();
container.deleteAllChildren(); 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) protected void buildEdit(int x, int y, int w, int h)
{ {
final List<Line> editLines = new ArrayList<>();
Widget container = chatboxPanelManager.getContainerWidget(); Widget container = chatboxPanelManager.getContainerWidget();
String lt = Text.escapeJagex(value.substring(0, this.cursor)); final Widget cursor = container.createChild(-1, WidgetType.RECTANGLE);
String mt = Text.escapeJagex(value.substring(this.cursor, this.cursorEnd)); long start = System.currentTimeMillis();
String rt = Text.escapeJagex(value.substring(this.cursorEnd)); cursor.setOnTimerListener((JavaScriptCallback) ev ->
{
Widget leftText = container.createChild(-1, WidgetType.TEXT); boolean on = (System.currentTimeMillis() - start) % CURSOR_FLASH_RATE_MILLIS > (CURSOR_FLASH_RATE_MILLIS / 2);
Widget cursor = container.createChild(-1, WidgetType.RECTANGLE); cursor.setOpacity(on ? 255 : 0);
Widget middleText = container.createChild(-1, WidgetType.TEXT); });
Widget rightText = container.createChild(-1, WidgetType.TEXT); cursor.setTextColor(0xFFFFFF);
cursor.setHasListener(true);
leftText.setFontId(fontID); cursor.setFilled(true);
FontTypeFace font = leftText.getFont(); cursor.setFontId(fontID);
FontTypeFace font = cursor.getFont();
if (h <= 0) if (h <= 0)
{ {
h = font.getBaseline(); h = font.getBaseline();
} }
int ltw = font.getTextWidth(lt); final int oy = y;
int mtw = font.getTextWidth(mt); final int ox = x;
int rtw = font.getTextWidth(rt); final int oh = h;
int fullWidth = ltw + mtw + rtw; int breakIndex = -1;
final StringBuilder sb = new StringBuilder();
int ox = x; for (int i = 0; i < value.length(); i++)
if (w > 0)
{ {
x += (w - fullWidth) / 2; int count = i - sb.length();
} final String c = value.charAt(i) + "";
sb.append(c);
int ltx = x; if (BREAK_MATCHER.matcher(c).matches())
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 ->
{ {
boolean on = (System.currentTimeMillis() - start) % CURSOR_FLASH_RATE_MILLIS > (CURSOR_FLASH_RATE_MILLIS / 2); breakIndex = sb.length();
cursor.setOpacity(on ? 255 : 0); }
});
cursor.setHasListener(true); 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); Rectangle bounds = new Rectangle(container.getCanvasLocation().getX() + container.getWidth(), y, 0, editLines.size() * oh);
middleText.setFontId(fontID); for (int i = 0; i < editLines.size() || i == 0; i++)
middleText.setOriginalX(mtx); {
middleText.setOriginalY(y); final Line line = editLines.size() > 0 ? editLines.get(i) : new Line(0, 0, "");
middleText.setOriginalWidth(mtw); final String text = line.text;
middleText.setOriginalHeight(h); final int len = text.length();
middleText.setTextColor(0xFFFFFF);
middleText.revalidate();
rightText.setText(rt); String lt = Text.escapeJagex(text);
rightText.setFontId(fontID); String mt = "";
rightText.setOriginalX(rtx); String rt = "";
rightText.setOriginalY(y);
rightText.setOriginalWidth(rtw); final boolean isStartLine = cursorOnLine(cursorStart, line.start, line.end)
rightText.setOriginalHeight(h); || (cursorOnLine(cursorStart, line.start, line.end + 1) && i == editLines.size() - 1);
rightText.revalidate();
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(); 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(new Point(ev.getX() - ccl.getX(), ev.getY() - ccl.getY()));
isInBounds = ev -> bounds.contains(ev.getPoint()); getPointCharOffset = p ->
getCharOffset = ev ->
{ {
if (fullWidth <= 0) if (bounds.width <= 0)
{ {
return 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 // `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--) 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; break;
} }
if (charIndex < 0) charIndex = Ints.constrainToRange(charIndex, 0, tsValue.length());
return line.start + charIndex;
};
getLineOffset = code ->
{
if (editLines.size() < 2)
{ {
charIndex = 0; return cursorStart;
}
if (charIndex > tsValue.length())
{
charIndex = tsValue.length();
} }
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 @Override
protected void open() protected void open()
{ {
this.built = true;
update(); update();
} }
@@ -398,12 +584,12 @@ public class ChatboxTextInput extends ChatboxInput implements KeyListener, Mouse
char c = e.getKeyChar(); char c = e.getKeyChar();
if (charValidator.test(c)) if (charValidator.test(c))
{ {
if (cursor != cursorEnd) if (cursorStart != cursorEnd)
{ {
value.delete(cursor, cursorEnd); value.delete(cursorStart, cursorEnd);
} }
value.insert(cursor, c); value.insert(cursorStart, c);
cursorAt(cursor + 1); cursorAt(cursorStart + 1);
if (onChanged != null) if (onChanged != null)
{ {
onChanged.accept(getValue()); onChanged.accept(getValue());
@@ -421,13 +607,13 @@ public class ChatboxTextInput extends ChatboxInput implements KeyListener, Mouse
{ {
case KeyEvent.VK_X: case KeyEvent.VK_X:
case KeyEvent.VK_C: 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) if (code == KeyEvent.VK_X)
{ {
value.delete(cursor, cursorEnd); value.delete(cursorStart, cursorEnd);
cursorAt(cursor); cursorAt(cursorStart);
} }
Toolkit.getDefaultToolkit() Toolkit.getDefaultToolkit()
.getSystemClipboard() .getSystemClipboard()
@@ -441,20 +627,20 @@ public class ChatboxTextInput extends ChatboxInput implements KeyListener, Mouse
.getSystemClipboard() .getSystemClipboard()
.getData(DataFlavor.stringFlavor) .getData(DataFlavor.stringFlavor)
.toString(); .toString();
if (cursor != cursorEnd) if (cursorStart != cursorEnd)
{ {
value.delete(cursor, cursorEnd); value.delete(cursorStart, cursorEnd);
} }
for (int i = 0; i < s.length(); i++) for (int i = 0; i < s.length(); i++)
{ {
char ch = s.charAt(i); char ch = s.charAt(i);
if (charValidator.test(ch)) if (charValidator.test(ch))
{ {
value.insert(cursor, ch); value.insert(cursorStart, ch);
cursor++; cursorStart++;
} }
} }
cursorAt(cursor); cursorAt(cursorStart);
if (onChanged != null) if (onChanged != null)
{ {
onChanged.accept(getValue()); onChanged.accept(getValue());
@@ -468,13 +654,13 @@ public class ChatboxTextInput extends ChatboxInput implements KeyListener, Mouse
} }
return; return;
} }
int newPos = cursor; int newPos = cursorStart;
if (ev.isShiftDown()) if (ev.isShiftDown())
{ {
if (selectionEnd == -1 || selectionStart == -1) if (selectionEnd == -1 || selectionStart == -1)
{ {
selectionStart = cursor; selectionStart = cursorStart;
selectionEnd = cursor; selectionEnd = cursorStart;
} }
newPos = selectionEnd; newPos = selectionEnd;
} }
@@ -486,20 +672,20 @@ public class ChatboxTextInput extends ChatboxInput implements KeyListener, Mouse
switch (code) switch (code)
{ {
case KeyEvent.VK_DELETE: case KeyEvent.VK_DELETE:
if (cursor != cursorEnd) if (cursorStart != cursorEnd)
{ {
value.delete(cursor, cursorEnd); value.delete(cursorStart, cursorEnd);
cursorAt(cursor); cursorAt(cursorStart);
if (onChanged != null) if (onChanged != null)
{ {
onChanged.accept(getValue()); onChanged.accept(getValue());
} }
return; return;
} }
if (cursor < value.length()) if (cursorStart < value.length())
{ {
value.deleteCharAt(cursor); value.deleteCharAt(cursorStart);
cursorAt(cursor); cursorAt(cursorStart);
if (onChanged != null) if (onChanged != null)
{ {
onChanged.accept(getValue()); onChanged.accept(getValue());
@@ -507,20 +693,20 @@ public class ChatboxTextInput extends ChatboxInput implements KeyListener, Mouse
} }
return; return;
case KeyEvent.VK_BACK_SPACE: case KeyEvent.VK_BACK_SPACE:
if (cursor != cursorEnd) if (cursorStart != cursorEnd)
{ {
value.delete(cursor, cursorEnd); value.delete(cursorStart, cursorEnd);
cursorAt(cursor); cursorAt(cursorStart);
if (onChanged != null) if (onChanged != null)
{ {
onChanged.accept(getValue()); onChanged.accept(getValue());
} }
return; return;
} }
if (cursor > 0) if (cursorStart > 0)
{ {
value.deleteCharAt(cursor - 1); value.deleteCharAt(cursorStart - 1);
cursorAt(cursor - 1); cursorAt(cursorStart - 1);
if (onChanged != null) if (onChanged != null)
{ {
onChanged.accept(getValue()); onChanged.accept(getValue());
@@ -535,6 +721,14 @@ public class ChatboxTextInput extends ChatboxInput implements KeyListener, Mouse
ev.consume(); ev.consume();
newPos++; newPos++;
break; 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: case KeyEvent.VK_HOME:
ev.consume(); ev.consume();
newPos = 0; newPos = 0;
@@ -553,9 +747,9 @@ public class ChatboxTextInput extends ChatboxInput implements KeyListener, Mouse
return; return;
case KeyEvent.VK_ESCAPE: case KeyEvent.VK_ESCAPE:
ev.consume(); ev.consume();
if (cursor != cursorEnd) if (cursorStart != cursorEnd)
{ {
cursorAt(cursor); cursorAt(cursorStart);
return; return;
} }
chatboxPanelManager.close(); chatboxPanelManager.close();
@@ -602,16 +796,16 @@ public class ChatboxTextInput extends ChatboxInput implements KeyListener, Mouse
} }
if (isInBounds == null || !isInBounds.test(mouseEvent)) if (isInBounds == null || !isInBounds.test(mouseEvent))
{ {
if (cursor != cursorEnd) if (cursorStart != cursorEnd)
{ {
selectionStart = -1; selectionStart = -1;
selectionEnd = -1; selectionEnd = -1;
cursorAt(getCharOffset.applyAsInt(mouseEvent)); cursorAt(getCharOffset(mouseEvent));
} }
return mouseEvent; return mouseEvent;
} }
int nco = getCharOffset.applyAsInt(mouseEvent); int nco = getCharOffset(mouseEvent);
if (mouseEvent.isShiftDown() && selectionEnd != -1) if (mouseEvent.isShiftDown() && selectionEnd != -1)
{ {
@@ -653,7 +847,7 @@ public class ChatboxTextInput extends ChatboxInput implements KeyListener, Mouse
return mouseEvent; return mouseEvent;
} }
int nco = getCharOffset.applyAsInt(mouseEvent); int nco = getCharOffset(mouseEvent);
if (selectionStart != -1) if (selectionStart != -1)
{ {
selectionEnd = nco; selectionEnd = nco;

View File

@@ -36,6 +36,7 @@ import net.runelite.api.Client;
import net.runelite.api.Point; import net.runelite.api.Point;
import net.runelite.api.Tile; import net.runelite.api.Tile;
import net.runelite.api.coords.LocalPoint; 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.Overlay;
import net.runelite.client.ui.overlay.OverlayLayer; import net.runelite.client.ui.overlay.OverlayLayer;
import net.runelite.client.ui.overlay.OverlayPosition; import net.runelite.client.ui.overlay.OverlayPosition;
@@ -44,6 +45,7 @@ import net.runelite.client.ui.overlay.OverlayUtil;
class AgilityOverlay extends Overlay class AgilityOverlay extends Overlay
{ {
private static final int MAX_DISTANCE = 2350; private static final int MAX_DISTANCE = 2350;
private static final Color SHORTCUT_HIGH_LEVEL_COLOR = Color.ORANGE;
private final Client client; private final Client client;
private final AgilityPlugin plugin; private final AgilityPlugin plugin;
@@ -66,14 +68,15 @@ class AgilityOverlay extends Overlay
LocalPoint playerLocation = client.getLocalPlayer().getLocalLocation(); LocalPoint playerLocation = client.getLocalPlayer().getLocalLocation();
Point mousePosition = client.getMouseCanvasPosition(); Point mousePosition = client.getMouseCanvasPosition();
final List<Tile> marksOfGrace = plugin.getMarksOfGrace(); final List<Tile> 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()) Obstacles.TRAP_OBSTACLE_IDS.contains(object.getId()) && !config.showTrapOverlay())
{ {
return; return;
} }
Tile tile = obstacle.getTile();
if (tile.getPlane() == client.getPlane() if (tile.getPlane() == client.getPlane()
&& object.getLocalLocation().distanceTo(playerLocation) < MAX_DISTANCE) && object.getLocalLocation().distanceTo(playerLocation) < MAX_DISTANCE)
{ {
@@ -87,11 +90,11 @@ class AgilityOverlay extends Overlay
} }
return; return;
} }
Area objectClickbox = object.getClickbox(); Area objectClickbox = object.getClickbox();
if (objectClickbox != null) 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()) if (config.highlightMarks() && !marksOfGrace.isEmpty())
{ {
configColor = config.getMarkColor(); configColor = config.getMarkColor();

View File

@@ -38,10 +38,12 @@ import net.runelite.api.Item;
import net.runelite.api.ItemID; import net.runelite.api.ItemID;
import static net.runelite.api.ItemID.AGILITY_ARENA_TICKET; import static net.runelite.api.ItemID.AGILITY_ARENA_TICKET;
import net.runelite.api.Player; import net.runelite.api.Player;
import net.runelite.api.Skill;
import static net.runelite.api.Skill.AGILITY; import static net.runelite.api.Skill.AGILITY;
import net.runelite.api.Tile; import net.runelite.api.Tile;
import net.runelite.api.TileObject; import net.runelite.api.TileObject;
import net.runelite.api.coords.WorldPoint; import net.runelite.api.coords.WorldPoint;
import net.runelite.api.events.BoostedLevelChanged;
import net.runelite.api.events.ConfigChanged; import net.runelite.api.events.ConfigChanged;
import net.runelite.api.events.DecorativeObjectChanged; import net.runelite.api.events.DecorativeObjectChanged;
import net.runelite.api.events.DecorativeObjectDespawned; 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.Notifier;
import net.runelite.client.config.ConfigManager; import net.runelite.client.config.ConfigManager;
import net.runelite.client.eventbus.Subscribe; import net.runelite.client.eventbus.Subscribe;
import net.runelite.client.game.AgilityShortcut;
import net.runelite.client.game.ItemManager; import net.runelite.client.game.ItemManager;
import net.runelite.client.plugins.Plugin; import net.runelite.client.plugins.Plugin;
import net.runelite.client.plugins.PluginDescriptor; import net.runelite.client.plugins.PluginDescriptor;
@@ -80,7 +83,7 @@ public class AgilityPlugin extends Plugin
private static final int AGILITY_ARENA_REGION_ID = 11157; private static final int AGILITY_ARENA_REGION_ID = 11157;
@Getter @Getter
private final Map<TileObject, Tile> obstacles = new HashMap<>(); private final Map<TileObject, Obstacle> obstacles = new HashMap<>();
@Getter @Getter
private final List<Tile> marksOfGrace = new ArrayList<>(); private final List<Tile> marksOfGrace = new ArrayList<>();
@@ -115,6 +118,9 @@ public class AgilityPlugin extends Plugin
private int lastAgilityXp; private int lastAgilityXp;
private WorldPoint lastArenaTicketPosition; private WorldPoint lastArenaTicketPosition;
@Getter
private int agilityLevel;
@Provides @Provides
AgilityConfig getConfig(ConfigManager configManager) AgilityConfig getConfig(ConfigManager configManager)
{ {
@@ -126,6 +132,7 @@ public class AgilityPlugin extends Plugin
{ {
overlayManager.add(agilityOverlay); overlayManager.add(agilityOverlay);
overlayManager.add(lapCounterOverlay); overlayManager.add(lapCounterOverlay);
agilityLevel = client.getBoostedSkillLevel(Skill.AGILITY);
} }
@Override @Override
@@ -136,6 +143,7 @@ public class AgilityPlugin extends Plugin
marksOfGrace.clear(); marksOfGrace.clear();
obstacles.clear(); obstacles.clear();
session = null; session = null;
agilityLevel = 0;
} }
@Subscribe @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 @Subscribe
public void onItemSpawned(ItemSpawned itemSpawned) public void onItemSpawned(ItemSpawned itemSpawned)
{ {
@@ -366,11 +385,40 @@ public class AgilityPlugin extends Plugin
} }
if (Obstacles.COURSE_OBSTACLE_IDS.contains(newObject.getId()) || 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_IDS.contains(newObject.getId())
&& Obstacles.TRAP_OBSTACLE_REGIONS.contains(newObject.getWorldLocation().getRegionID()))) && 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));
}
} }
} }
} }

View File

@@ -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;
}

View File

@@ -25,11 +25,27 @@
package net.runelite.client.plugins.agility; package net.runelite.client.plugins.agility;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multimap;
import java.util.List; import java.util.List;
import java.util.Set; 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 static net.runelite.api.ObjectID.*;
import net.runelite.client.game.AgilityShortcut;
class Obstacles class Obstacles
{ {
@@ -62,7 +78,7 @@ class Obstacles
STEPPING_STONE_15412, TROPICAL_TREE_15414, MONKEYBARS_15417, SKULL_SLOPE_15483, ROPE_15487, TROPICAL_TREE_16062, STEPPING_STONE_15412, TROPICAL_TREE_15414, MONKEYBARS_15417, SKULL_SLOPE_15483, ROPE_15487, TROPICAL_TREE_16062,
// Falador // Falador
ROUGH_WALL_10833, TIGHTROPE_10834, HAND_HOLDS_10836, GAP_11161, GAP_11360, TIGHTROPE_11361, 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 // Wilderness
OBSTACLE_PIPE_23137, ROPESWING_23132, STEPPING_STONE_23556, LOG_BALANCE_23542, ROCKS_23640, OBSTACLE_PIPE_23137, ROPESWING_23132, STEPPING_STONE_23556, LOG_BALANCE_23542, ROCKS_23640,
// Seers // Seers
@@ -91,144 +107,7 @@ class Obstacles
ZIP_LINE_11645, ZIP_LINE_11646 ZIP_LINE_11645, ZIP_LINE_11646
); );
static final Set<Integer> SHORTCUT_OBSTACLE_IDS = ImmutableSet.of( static final Multimap<Integer, AgilityShortcut> SHORTCUT_OBSTACLE_IDS;
// 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 Set<Integer> TRAP_OBSTACLE_IDS = ImmutableSet.of( static final Set<Integer> TRAP_OBSTACLE_IDS = ImmutableSet.of(
// Agility pyramid // Agility pyramid
@@ -236,4 +115,17 @@ class Obstacles
); );
static final List<Integer> TRAP_OBSTACLE_REGIONS = ImmutableList.of(12105, 13356); static final List<Integer> TRAP_OBSTACLE_REGIONS = ImmutableList.of(12105, 13356);
static
{
final ImmutableMultimap.Builder<Integer, AgilityShortcut> builder = ImmutableMultimap.builder();
for (final AgilityShortcut item : AgilityShortcut.values())
{
for (int obstacle : item.getObstacleIds())
{
builder.put(obstacle, item);
}
}
SHORTCUT_OBSTACLE_IDS = builder.build();
}
} }

View File

@@ -60,6 +60,7 @@ import net.runelite.client.callback.ClientThread;
import net.runelite.client.config.ConfigManager; import net.runelite.client.config.ConfigManager;
import net.runelite.client.eventbus.Subscribe; import net.runelite.client.eventbus.Subscribe;
import net.runelite.client.game.ItemManager; import net.runelite.client.game.ItemManager;
import net.runelite.client.game.SpriteManager;
import net.runelite.client.game.chatbox.ChatboxPanelManager; import net.runelite.client.game.chatbox.ChatboxPanelManager;
import net.runelite.client.input.KeyListener; import net.runelite.client.input.KeyListener;
import net.runelite.client.input.KeyManager; import net.runelite.client.input.KeyManager;
@@ -125,6 +126,9 @@ public class BankTagsPlugin extends Plugin implements MouseWheelListener, KeyLis
@Inject @Inject
private KeyManager keyManager; private KeyManager keyManager;
@Inject
private SpriteManager spriteManager;
private boolean shiftPressed = false; private boolean shiftPressed = false;
@Provides @Provides
@@ -139,7 +143,7 @@ public class BankTagsPlugin extends Plugin implements MouseWheelListener, KeyLis
keyManager.registerKeyListener(this); keyManager.registerKeyListener(this);
mouseManager.registerMouseWheelListener(this); mouseManager.registerMouseWheelListener(this);
clientThread.invokeLater(tabInterface::init); clientThread.invokeLater(tabInterface::init);
client.getSpriteOverrides().putAll(TabSprites.toMap(client)); spriteManager.addSpriteOverrides(TabSprites.values());
} }
@Override @Override
@@ -148,11 +152,7 @@ public class BankTagsPlugin extends Plugin implements MouseWheelListener, KeyLis
keyManager.unregisterKeyListener(this); keyManager.unregisterKeyListener(this);
mouseManager.unregisterMouseWheelListener(this); mouseManager.unregisterMouseWheelListener(this);
clientThread.invokeLater(tabInterface::destroy); clientThread.invokeLater(tabInterface::destroy);
spriteManager.removeSpriteOverrides(TabSprites.values());
for (TabSprites value : TabSprites.values())
{
client.getSpriteOverrides().remove(value.getSpriteId());
}
shiftPressed = false; shiftPressed = false;
} }

View File

@@ -167,6 +167,20 @@ public class TagManager
} }
} }
public void renameTag(String oldTag, String newTag)
{
List<Integer> items = getItemsForTag(Text.standardize(oldTag));
items.forEach(id ->
{
Collection<String> 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) private int getItemId(int itemId, boolean variation)
{ {
itemId = Math.abs(itemId); itemId = Math.abs(itemId);

View File

@@ -39,5 +39,6 @@ class MenuIndexes
static final int CHANGE_ICON = 3; static final int CHANGE_ICON = 3;
static final int DELETE_TAB = 4; static final int DELETE_TAB = 4;
static final int EXPORT_TAB = 5; static final int EXPORT_TAB = 5;
static final int RENAME_TAB = 6;
} }
} }

View File

@@ -64,7 +64,6 @@ import net.runelite.api.SpriteID;
import net.runelite.api.VarClientInt; import net.runelite.api.VarClientInt;
import net.runelite.api.VarClientStr; import net.runelite.api.VarClientStr;
import net.runelite.api.Varbits; import net.runelite.api.Varbits;
import net.runelite.api.widgets.WidgetType;
import net.runelite.api.events.MenuEntryAdded; import net.runelite.api.events.MenuEntryAdded;
import net.runelite.api.events.MenuOptionClicked; import net.runelite.api.events.MenuOptionClicked;
import net.runelite.api.vars.InputType; 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.Widget;
import net.runelite.api.widgets.WidgetConfig; import net.runelite.api.widgets.WidgetConfig;
import net.runelite.api.widgets.WidgetInfo; 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.Notifier;
import net.runelite.client.callback.ClientThread; import net.runelite.client.callback.ClientThread;
import net.runelite.client.config.ConfigManager;
import net.runelite.client.game.ItemManager; import net.runelite.client.game.ItemManager;
import net.runelite.client.game.chatbox.ChatboxPanelManager; import net.runelite.client.game.chatbox.ChatboxPanelManager;
import net.runelite.client.plugins.banktags.BankTagsConfig; import net.runelite.client.plugins.banktags.BankTagsConfig;
import net.runelite.client.plugins.banktags.BankTagsPlugin; 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.TAG_SEARCH;
import static net.runelite.client.plugins.banktags.BankTagsPlugin.VAR_TAG_SUFFIX; import static net.runelite.client.plugins.banktags.BankTagsPlugin.VAR_TAG_SUFFIX;
import net.runelite.client.plugins.banktags.TagManager; 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 EXPORT_TAB = "Export tag tab";
private static final String IMPORT_TAB = "Import tag tab"; private static final String IMPORT_TAB = "Import tag tab";
private static final String VIEW_TAB = "View 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 CHANGE_ICON = "Change icon";
private static final String REMOVE_TAG = "Remove-tag"; private static final String REMOVE_TAG = "Remove-tag";
private static final String TAG_GEAR = "Tag-equipment"; 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 BUTTON_HEIGHT = 20;
private static final int MARGIN = 1; private static final int MARGIN = 1;
private static final int SCROLL_TICK = 500; 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 Client client;
private final ClientThread clientThread; private final ClientThread clientThread;
private final ItemManager itemManager; private final ItemManager itemManager;
private final ConfigManager configManager;
private final TagManager tagManager; private final TagManager tagManager;
private final TabManager tabManager; private final TabManager tabManager;
private final ChatboxPanelManager chatboxPanelManager; private final ChatboxPanelManager chatboxPanelManager;
@@ -148,7 +148,6 @@ public class TabInterface
final Client client, final Client client,
final ClientThread clientThread, final ClientThread clientThread,
final ItemManager itemManager, final ItemManager itemManager,
final ConfigManager configManager,
final TagManager tagManager, final TagManager tagManager,
final TabManager tabManager, final TabManager tabManager,
final ChatboxPanelManager chatboxPanelManager, final ChatboxPanelManager chatboxPanelManager,
@@ -159,7 +158,6 @@ public class TabInterface
this.client = client; this.client = client;
this.clientThread = clientThread; this.clientThread = clientThread;
this.itemManager = itemManager; this.itemManager = itemManager;
this.configManager = configManager;
this.tagManager = tagManager; this.tagManager = tagManager;
this.tabManager = tabManager; this.tabManager = tabManager;
this.chatboxPanelManager = chatboxPanelManager; this.chatboxPanelManager = chatboxPanelManager;
@@ -211,7 +209,7 @@ public class TabInterface
if (config.rememberTab() && !Strings.isNullOrEmpty(config.tab())) 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); tagManager.addTag(item, activeTab.getTag(), false);
} }
openTag(TAG_SEARCH + activeTab.getTag()); openTag(activeTab.getTag());
} }
return; return;
@@ -292,7 +290,7 @@ public class TabInterface
final Iterator<String> dataIter = Text.fromCSV(dataString).iterator(); final Iterator<String> dataIter = Text.fromCSV(dataString).iterator();
final String name = dataIter.next(); final String name = dataIter.next();
final String icon = dataIter.next(); final String icon = dataIter.next();
configManager.setConfiguration(CONFIG_GROUP, ICON_SEARCH + name, icon); tabManager.setIcon(name, icon);
while (dataIter.hasNext()) while (dataIter.hasNext())
{ {
@@ -306,7 +304,7 @@ public class TabInterface
if (activeTab != null && name.equals(activeTab.getTag())) 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!"); notifier.notify("Tag tab " + name + " has been imported from your clipboard!");
@@ -336,7 +334,7 @@ public class TabInterface
} }
else else
{ {
openTag(TAG_SEARCH + Text.removeTags(clicked.getName())); openTag(Text.removeTags(clicked.getName()));
} }
client.playSoundEffect(SoundEffectID.UI_BOOP); client.playSoundEffect(SoundEffectID.UI_BOOP);
@@ -373,6 +371,10 @@ public class TabInterface
Toolkit.getDefaultToolkit().getSystemClipboard().setContents(stringSelection, null); Toolkit.getDefaultToolkit().getSystemClipboard().setContents(stringSelection, null);
notifier.notify("Tag tab " + tagTab.getTag() + " has been copied to your clipboard!"); notifier.notify("Tag tab " + tagTab.getTag() + " has been copied to your clipboard!");
break; 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()); int itemId = itemManager.canonicalize(item.getId());
iconToSet.setIconItemId(itemId); iconToSet.setIconItemId(itemId);
iconToSet.getIcon().setItemId(itemId); iconToSet.getIcon().setItemId(itemId);
configManager.setConfiguration(CONFIG_GROUP, ICON_SEARCH + iconToSet.getTag(), itemId + ""); tabManager.setIcon(iconToSet.getTag(), itemId + "");
event.consume(); event.consume();
} }
@@ -597,7 +599,7 @@ public class TabInterface
{ {
if (activeTab != null && tags.contains(activeTab.getTag())) 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(2, CHANGE_ICON);
btn.setAction(3, REMOVE_TAB); btn.setAction(3, REMOVE_TAB);
btn.setAction(4, EXPORT_TAB); btn.setAction(4, EXPORT_TAB);
btn.setAction(5, RENAME_TAB);
btn.setOnOpListener((JavaScriptCallback) this::handleTagTab); btn.setOnOpListener((JavaScriptCallback) this::handleTagTab);
tagTab.setBackground(btn); tagTab.setBackground(btn);
} }
@@ -712,13 +715,66 @@ public class TabInterface
} }
tabManager.remove(tag); tabManager.remove(tag);
configManager.unsetConfiguration(CONFIG_GROUP, ICON_SEARCH + tag);
tabManager.save(); tabManager.save();
updateBounds(); updateBounds();
scrollTab(0); 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) private void scrollTick(int direction)
{ {
// This ensures that dragging on scroll buttons do not scrolls too fast // This ensures that dragging on scroll buttons do not scrolls too fast
@@ -805,17 +861,18 @@ public class TabInterface
if (incinerator != null && !incinerator.isHidden()) if (incinerator != null && !incinerator.isHidden())
{ {
// This is the required way to move incinerator, don't change it! incinerator.setOriginalHeight(INCINERATOR_HEIGHT);
incinerator.setOriginalHeight(39); incinerator.setOriginalWidth(INCINERATOR_WIDTH);
incinerator.setOriginalWidth(48); incinerator.setOriginalY(INCINERATOR_HEIGHT);
incinerator.setRelativeY(itemContainer.getHeight());
incinerator.revalidate();
Widget child = incinerator.getDynamicChildren()[0]; Widget child = incinerator.getDynamicChildren()[0];
child.setHeight(39); child.setOriginalHeight(INCINERATOR_HEIGHT);
child.setWidth(48); child.setOriginalWidth(INCINERATOR_WIDTH);
child.setWidthMode(WidgetSizeMode.ABSOLUTE);
child.setHeightMode(WidgetSizeMode.ABSOLUTE);
child.setType(WidgetType.GRAPHIC); child.setType(WidgetType.GRAPHIC);
child.setSpriteId(TabSprites.INCINERATOR.getSpriteId()); child.setSpriteId(TabSprites.INCINERATOR.getSpriteId());
incinerator.revalidate();
bounds.setSize(TAB_WIDTH + MARGIN * 2, height - incinerator.getHeight()); bounds.setSize(TAB_WIDTH + MARGIN * 2, height - incinerator.getHeight());
} }
@@ -900,7 +957,6 @@ public class TabInterface
private void updateWidget(Widget t, int y) private void updateWidget(Widget t, int y)
{ {
t.setOriginalY(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.setHidden(y < (bounds.y + BUTTON_HEIGHT + MARGIN) || y > (bounds.y + bounds.height - TAB_HEIGHT - MARGIN - BUTTON_HEIGHT));
t.revalidate(); t.revalidate();
} }
@@ -913,10 +969,10 @@ public class TabInterface
return itemManager.getItemComposition(item.getId()); return itemManager.getItemComposition(item.getId());
} }
private void openTag(String tag) private void openTag(final String tag)
{ {
bankSearch.search(InputType.SEARCH, tag, true); bankSearch.search(InputType.SEARCH, TAG_SEARCH + tag, true);
activateTab(tabManager.find(tag.substring(TAG_SEARCH.length()))); activateTab(tabManager.find(tag));
// When tab is selected with search window open, the search window closes but the search button // When tab is selected with search window open, the search window closes but the search button
// stays highlighted, this solves that issue // stays highlighted, this solves that issue

View File

@@ -115,6 +115,7 @@ class TabManager
{ {
tagTab.setHidden(true); tagTab.setHidden(true);
tabs.remove(tagTab); tabs.remove(tagTab);
removeIcon(tag);
} }
} }
@@ -124,6 +125,16 @@ class TabManager
configManager.setConfiguration(CONFIG_GROUP, TAG_TABS_CONFIG, tags); 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() int size()
{ {
return tabs.size(); return tabs.size();

View File

@@ -25,17 +25,12 @@
*/ */
package net.runelite.client.plugins.banktags.tabs; 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.Getter;
import lombok.extern.slf4j.Slf4j; import lombok.RequiredArgsConstructor;
import net.runelite.api.Client; import net.runelite.client.game.SpriteOverride;
import net.runelite.api.SpritePixels;
import net.runelite.client.util.ImageUtil;
@Slf4j @RequiredArgsConstructor
public enum TabSprites public enum TabSprites implements SpriteOverride
{ {
INCINERATOR(-200, "incinerator.png"), INCINERATOR(-200, "incinerator.png"),
TAB_BACKGROUND(-201, "tag-tab.png"), TAB_BACKGROUND(-201, "tag-tab.png"),
@@ -46,23 +41,7 @@ public enum TabSprites
@Getter @Getter
private final int spriteId; private final int spriteId;
private final BufferedImage image;
TabSprites(final int spriteId, final String imageName) @Getter
{ private final String fileName;
this.spriteId = spriteId;
this.image = ImageUtil.getResourceStreamFromClass(this.getClass(), imageName);
}
public static Map<Integer, SpritePixels> toMap(Client client)
{
final Map<Integer, SpritePixels> map = new HashMap<>();
for (TabSprites value : values())
{
map.put(value.spriteId, ImageUtil.getImageSpritePixels(value.image, client));
}
return map;
}
} }

View File

@@ -33,7 +33,7 @@ import net.runelite.api.widgets.Widget;
@EqualsAndHashCode(of = "tag") @EqualsAndHashCode(of = "tag")
class TagTab class TagTab
{ {
private final String tag; private String tag;
private int iconItemId; private int iconItemId;
private Widget background; private Widget background;
private Widget icon; private Widget icon;

View File

@@ -26,15 +26,15 @@ package net.runelite.client.plugins.cannon;
import java.awt.Color; import java.awt.Color;
import java.awt.image.BufferedImage; 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; 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; this.plugin = plugin;
} }

View File

@@ -75,7 +75,8 @@ public class CerberusPlugin extends Plugin
@Subscribe @Subscribe
public void onGameStateChanged(GameStateChanged event) 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(); ghosts.clear();
} }

View File

@@ -299,7 +299,7 @@ public class ChatCommandsPlugin extends Plugin
Widget boss = bossChildren[i]; Widget boss = bossChildren[i];
Widget kill = killsChildren[i]; Widget kill = killsChildren[i];
String bossName = boss.getText(); String bossName = boss.getText().replace(":", "");
int kc = Integer.parseInt(kill.getText().replace(",", "")); int kc = Integer.parseInt(kill.getText().replace(",", ""));
if (kc != getKc(bossName)) if (kc != getKc(bossName))
{ {
@@ -1089,6 +1089,7 @@ public class ChatCommandsPlugin extends Plugin
case "barrows": case "barrows":
return "Barrows Chests"; return "Barrows Chests";
// cox
case "cox": case "cox":
case "xeric": case "xeric":
case "chambers": case "chambers":
@@ -1096,6 +1097,15 @@ public class ChatCommandsPlugin extends Plugin
case "raids": case "raids":
return "Chambers of Xeric"; 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 "tob":
case "theatre": case "theatre":
case "verzik": case "verzik":

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2017, Adam <Adam@sigterm.info> * Copyright (c) 2018, TheStonedTurtle <https://github.com/TheStonedTurtle>
* All rights reserved. * All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without * 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 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * 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 net.runelite.client.config.Config;
import java.util.concurrent.ConcurrentHashMap; import net.runelite.client.config.ConfigGroup;
import java.util.concurrent.ConcurrentMap; import net.runelite.client.config.ConfigItem;
public class SessionManager @ConfigGroup("chathistory")
public interface ChatHistoryConfig extends Config
{ {
private static final ConcurrentMap<UUID, WSService> sessions = new ConcurrentHashMap<>(); @ConfigItem(
keyName = "retainChatHistory",
public static void changeSessionUID(WSService service, UUID uuid) name = "Retain Chat History",
description = "Retains chat history when logging in/out or world hopping",
position = 0
)
default boolean retainChatHistory()
{ {
synchronized (service) return true;
{
remove(service);
service.setUuid(uuid);
sessions.put(uuid, service);
}
} }
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) return true;
{
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();
} }
} }

View File

@@ -25,47 +25,74 @@
package net.runelite.client.plugins.chathistory; package net.runelite.client.plugins.chathistory;
import com.google.common.collect.EvictingQueue; 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.Queue;
import java.util.Set;
import javax.inject.Inject; import javax.inject.Inject;
import net.runelite.api.ChatMessageType; 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.ChatMessage;
import net.runelite.api.events.MenuOptionClicked; 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.ChatMessageManager;
import net.runelite.client.chat.QueuedMessage; import net.runelite.client.chat.QueuedMessage;
import net.runelite.client.config.ConfigManager;
import net.runelite.client.eventbus.Subscribe; 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.Plugin;
import net.runelite.client.plugins.PluginDescriptor; import net.runelite.client.plugins.PluginDescriptor;
import net.runelite.client.util.Text;
@PluginDescriptor( @PluginDescriptor(
name = "Chat History", 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 WELCOME_MESSAGE = "Welcome to Old School RuneScape.";
private static final String CLEAR_HISTORY = "Clear history"; private static final String CLEAR_HISTORY = "Clear history";
private static final String CLEAR_PRIVATE = "<col=ffff00>Private:"; private static final String CLEAR_PRIVATE = "<col=ffff00>Private:";
private static final Set<ChatMessageType> ALLOWED_HISTORY = Sets.newHashSet( private static final int CYCLE_HOTKEY = KeyEvent.VK_TAB;
ChatMessageType.PUBLIC,
ChatMessageType.PUBLIC_MOD,
ChatMessageType.CLANCHAT,
ChatMessageType.PRIVATE_MESSAGE_RECEIVED,
ChatMessageType.PRIVATE_MESSAGE_SENT,
ChatMessageType.PRIVATE_MESSAGE_RECEIVED_MOD,
ChatMessageType.GAME
);
private Queue<QueuedMessage> messageQueue; private Queue<QueuedMessage> messageQueue;
private Deque<String> friends;
@Inject
private Client client;
@Inject
private ClientThread clientThread;
@Inject
private ChatHistoryConfig config;
@Inject
private KeyManager keyManager;
@Inject @Inject
private ChatMessageManager chatMessageManager; private ChatMessageManager chatMessageManager;
@Provides
ChatHistoryConfig getConfig(ConfigManager configManager)
{
return configManager.getConfig(ChatHistoryConfig.class);
}
@Override @Override
protected void startUp() protected void startUp()
{ {
messageQueue = EvictingQueue.create(100); messageQueue = EvictingQueue.create(100);
friends = new ArrayDeque<>(5);
keyManager.registerKeyListener(this);
} }
@Override @Override
@@ -73,6 +100,9 @@ public class ChatHistoryPlugin extends Plugin
{ {
messageQueue.clear(); messageQueue.clear();
messageQueue = null; messageQueue = null;
friends.clear();
friends = null;
keyManager.unregisterKeyListener(this);
} }
@Subscribe @Subscribe
@@ -82,6 +112,11 @@ public class ChatHistoryPlugin extends Plugin
// of information that chat history was reset // of information that chat history was reset
if (chatMessage.getMessage().equals(WELCOME_MESSAGE)) if (chatMessage.getMessage().equals(WELCOME_MESSAGE))
{ {
if (!config.retainChatHistory())
{
return;
}
QueuedMessage queuedMessage; QueuedMessage queuedMessage;
while ((queuedMessage = messageQueue.poll()) != null) while ((queuedMessage = messageQueue.poll()) != null)
@@ -92,21 +127,33 @@ public class ChatHistoryPlugin extends Plugin
return; return;
} }
if (ALLOWED_HISTORY.contains(chatMessage.getType())) switch (chatMessage.getType())
{ {
final QueuedMessage queuedMessage = QueuedMessage.builder() case PRIVATE_MESSAGE_SENT:
.type(chatMessage.getType()) case PRIVATE_MESSAGE_RECEIVED:
.name(chatMessage.getName()) case PRIVATE_MESSAGE_RECEIVED_MOD:
.sender(chatMessage.getSender()) final String name = Text.removeTags(chatMessage.getName());
.value(nbsp(chatMessage.getMessage())) // Remove to ensure uniqueness & its place in history
.runeLiteFormattedMessage(nbsp(chatMessage.getMessageNode().getRuneLiteFormatMessage())) friends.remove(name);
.timestamp(chatMessage.getTimestamp()) friends.add(name);
.build(); // 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)) if (!messageQueue.contains(queuedMessage))
{ {
messageQueue.offer(queuedMessage); messageQueue.offer(queuedMessage);
} }
} }
} }
@@ -143,4 +190,64 @@ public class ChatHistoryPlugin extends Plugin
return null; 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<String> it = friends.descendingIterator(); it.hasNext(); )
{
String friend = it.next();
if (friend.equals(currentTarget))
{
return it.hasNext() ? it.next() : friends.getLast();
}
}
return friends.getLast();
}
} }

View File

@@ -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 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 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("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("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))), 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 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 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)), 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)),

View File

@@ -78,7 +78,6 @@ class CookingOverlay extends Overlay
return null; return null;
} }
panelComponent.setPreferredSize(new Dimension(145, 0));
panelComponent.getChildren().clear(); panelComponent.getChildren().clear();
if (isCooking() || Duration.between(session.getLastCookingAction(), Instant.now()).getSeconds() < COOK_TIMEOUT) if (isCooking() || Duration.between(session.getLastCookingAction(), Instant.now()).getSeconds() < COOK_TIMEOUT)

View File

@@ -347,6 +347,12 @@ class DevToolsOverlay extends Overlay
{ {
graphics.drawPolygon(p); graphics.drawPolygon(p);
} }
p = decorObject.getConvexHull2();
if (p != null)
{
graphics.drawPolygon(p);
}
} }
} }

View File

@@ -183,6 +183,9 @@ public class WidgetInfoTableModel extends AbstractTableModel
out.add(new WidgetField<>("ScrollHeight", Widget::getScrollHeight, Widget::setScrollHeight, Integer.class)); 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<>("DragDeadZone", Widget::getDragDeadZone, Widget::setDragDeadZone, Integer.class));
out.add(new WidgetField<>("DragDeadTime", Widget::getDragDeadTime, Widget::setDragDeadTime, 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; return out;
} }

View File

@@ -130,6 +130,7 @@ public class DiscordPlugin extends Plugin
clientToolbar.addNavigation(discordButton); clientToolbar.addNavigation(discordButton);
checkForGameStateUpdate(); checkForGameStateUpdate();
checkForAreaUpdate();
if (discordService.getCurrentUser() != null) if (discordService.getCurrentUser() != null)
{ {

View File

@@ -34,6 +34,7 @@ import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import javax.inject.Inject; import javax.inject.Inject;
import lombok.Data; import lombok.Data;
import net.runelite.client.RuneLiteProperties;
import net.runelite.client.discord.DiscordPresence; import net.runelite.client.discord.DiscordPresence;
import net.runelite.client.discord.DiscordService; import net.runelite.client.discord.DiscordService;
import net.runelite.client.ws.PartyService; import net.runelite.client.ws.PartyService;
@@ -57,14 +58,16 @@ class DiscordState
private final DiscordService discordService; private final DiscordService discordService;
private final DiscordConfig config; private final DiscordConfig config;
private PartyService party; private PartyService party;
private final RuneLiteProperties properties;
private DiscordPresence lastPresence; private DiscordPresence lastPresence;
@Inject @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.discordService = discordService;
this.config = config; this.config = config;
this.party = party; this.party = party;
this.properties = properties;
} }
/** /**
@@ -90,6 +93,7 @@ class DiscordState
final DiscordPresence.DiscordPresenceBuilder presenceBuilder = DiscordPresence.builder() final DiscordPresence.DiscordPresenceBuilder presenceBuilder = DiscordPresence.builder()
.state(lastPresence.getState()) .state(lastPresence.getState())
.details(lastPresence.getDetails()) .details(lastPresence.getDetails())
.largeImageText(lastPresence.getLargeImageText())
.startTimestamp(lastPresence.getStartTimestamp()) .startTimestamp(lastPresence.getStartTimestamp())
.smallImageKey(lastPresence.getSmallImageKey()) .smallImageKey(lastPresence.getSmallImageKey())
.partyMax(lastPresence.getPartyMax()) .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() final DiscordPresence.DiscordPresenceBuilder presenceBuilder = DiscordPresence.builder()
.state(MoreObjects.firstNonNull(state, "")) .state(MoreObjects.firstNonNull(state, ""))
.details(MoreObjects.firstNonNull(details, "")) .details(MoreObjects.firstNonNull(details, ""))
.largeImageText(properties.getTitle() + " v" + versionShortHand)
.startTimestamp(event.getStart()) .startTimestamp(event.getStart())
.smallImageKey(MoreObjects.firstNonNull(imageKey, "default")) .smallImageKey(imageKey)
.partyMax(PARTY_MAX) .partyMax(PARTY_MAX)
.partySize(party.getMembers().size()); .partySize(party.getMembers().size());

View File

@@ -24,10 +24,12 @@
*/ */
package net.runelite.client.plugins.discord; package net.runelite.client.plugins.discord;
import lombok.EqualsAndHashCode;
import lombok.Value; import lombok.Value;
import net.runelite.http.api.ws.messages.party.PartyMemberMessage; import net.runelite.http.api.ws.messages.party.PartyMemberMessage;
@Value @Value
@EqualsAndHashCode(callSuper = true)
class DiscordUserInfo extends PartyMemberMessage class DiscordUserInfo extends PartyMemberMessage
{ {
private final String userId; private final String userId;

View File

@@ -147,7 +147,8 @@ public class FishingPlugin extends Plugin
@Subscribe @Subscribe
public void onGameStateChanged(GameStateChanged gameStateChanged) 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(); fishingSpots.clear();
minnowSpots.clear(); minnowSpots.clear();

View File

@@ -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.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()) if (client.isStretchedEnabled())
{ {
Dimension dim = client.getStretchedDimensions(); Dimension dim = client.getStretchedDimensions();

View File

@@ -1,5 +1,5 @@
/* /*
* * Copyright (c) 2019, Adam <Adam@sigterm.info>
* Copyright (c) 2017, Robbie <https://github.com/rbbi> * Copyright (c) 2017, Robbie <https://github.com/rbbi>
* Copyright (c) 2018, SomeoneWithAnInternetConnection * Copyright (c) 2018, SomeoneWithAnInternetConnection
* All rights reserved. * All rights reserved.
@@ -46,6 +46,7 @@ import net.runelite.api.ChatMessageType;
import net.runelite.api.Client; import net.runelite.api.Client;
import net.runelite.api.GameState; import net.runelite.api.GameState;
import net.runelite.api.GrandExchangeOffer; import net.runelite.api.GrandExchangeOffer;
import net.runelite.api.GrandExchangeOfferState;
import net.runelite.api.ItemComposition; import net.runelite.api.ItemComposition;
import net.runelite.api.MenuAction; import net.runelite.api.MenuAction;
import net.runelite.api.MenuEntry; 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.GameTick;
import net.runelite.api.events.GrandExchangeOfferChanged; import net.runelite.api.events.GrandExchangeOfferChanged;
import net.runelite.api.events.MenuEntryAdded; 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.events.WidgetLoaded;
import net.runelite.api.widgets.Widget; import net.runelite.api.widgets.Widget;
import net.runelite.api.widgets.WidgetID; import net.runelite.api.widgets.WidgetID;
import net.runelite.api.widgets.WidgetInfo; import net.runelite.api.widgets.WidgetInfo;
import net.runelite.client.Notifier; 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.config.ConfigManager;
import net.runelite.client.eventbus.Subscribe; import net.runelite.client.eventbus.Subscribe;
import net.runelite.client.game.ItemManager; 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.ImageUtil;
import net.runelite.client.util.StackFormatter; import net.runelite.client.util.StackFormatter;
import net.runelite.client.util.Text; import net.runelite.client.util.Text;
import net.runelite.http.api.osbuddy.GrandExchangeClient; import net.runelite.http.api.ge.GrandExchangeClient;
import net.runelite.http.api.osbuddy.GrandExchangeResult; import net.runelite.http.api.ge.GrandExchangeTrade;
import net.runelite.http.api.osbuddy.OSBGrandExchangeClient;
import net.runelite.http.api.osbuddy.OSBGrandExchangeResult;
@PluginDescriptor( @PluginDescriptor(
name = "Grand Exchange", 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_CONTAINER_ITEM = 21;
private static final int OFFER_DEFAULT_ITEM_ID = 6512; 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 = "<br>OSBuddy Actively traded price: "; private static final String OSB_GE_TEXT = "<br>OSBuddy Actively traded price: ";
private static final String BUY_LIMIT_GE_TEXT = "<br>Buy limit: "; private static final String BUY_LIMIT_GE_TEXT = "<br>Buy limit: ";
@@ -134,10 +141,38 @@ public class GrandExchangePlugin extends Plugin
@Inject @Inject
private ScheduledExecutorService executorService; private ScheduledExecutorService executorService;
@Inject
private SessionManager sessionManager;
@Inject
private ConfigManager configManager;
private Widget grandExchangeText; private Widget grandExchangeText;
private Widget grandExchangeItem; private Widget grandExchangeItem;
private Map<Integer, Integer> itemGELimits; private Map<Integer, Integer> 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 @Provides
GrandExchangeConfig provideConfig(ConfigManager configManager) GrandExchangeConfig provideConfig(ConfigManager configManager)
{ {
@@ -167,6 +202,12 @@ public class GrandExchangePlugin extends Plugin
mouseManager.registerMouseListener(inputListener); mouseManager.registerMouseListener(inputListener);
keyManager.registerKeyListener(inputListener); keyManager.registerKeyListener(inputListener);
} }
AccountSession accountSession = sessionManager.getAccountSession();
if (accountSession != null)
{
grandExchangeClient = new GrandExchangeClient(accountSession.getUuid());
}
} }
@Override @Override
@@ -178,6 +219,27 @@ public class GrandExchangePlugin extends Plugin
grandExchangeText = null; grandExchangeText = null;
grandExchangeItem = null; grandExchangeItem = null;
itemGELimits = 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 @Subscribe
@@ -204,11 +266,79 @@ public class GrandExchangePlugin extends Plugin
@Subscribe @Subscribe
public void onGrandExchangeOfferChanged(GrandExchangeOfferChanged offerEvent) 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()); ItemComposition offerItem = itemManager.getItemComposition(offer.getItemId());
boolean shouldStack = offerItem.isStackable() || offer.getTotalQuantity() > 1; boolean shouldStack = offerItem.isStackable() || offer.getTotalQuantity() > 1;
BufferedImage itemImage = itemManager.getImage(offer.getItemId(), offer.getTotalQuantity(), shouldStack); 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 @Subscribe
@@ -346,7 +476,7 @@ public class GrandExchangePlugin extends Plugin
try 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()); final String text = geText.getText() + OSB_GE_TEXT + StackFormatter.formatNumber(result.getOverall_average());
geText.setText(text); geText.setText(text);
} }

Some files were not shown because too many files have changed in this diff Show More