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>
<groupId>net.runelite</groupId>
<artifactId>runelite-parent</artifactId>
<version>1.5.12-SNAPSHOT</version>
<version>1.5.14-SNAPSHOT</version>
</parent>
<artifactId>cache-client</artifactId>

View File

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

2
cache/pom.xml vendored
View File

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

View File

@@ -28,7 +28,7 @@
<parent>
<groupId>net.runelite</groupId>
<artifactId>runelite-parent</artifactId>
<version>1.5.12-SNAPSHOT</version>
<version>1.5.14-SNAPSHOT</version>
</parent>
<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;
import java.time.Instant;
import java.util.Collection;
import lombok.AllArgsConstructor;
import lombok.Data;
@@ -37,4 +38,5 @@ public class LootRecord
private String eventId;
private LootRecordType type;
private Collection<GameItem> drops;
private Instant time;
}

View File

@@ -35,9 +35,9 @@ import okhttp3.Request;
import okhttp3.Response;
@Slf4j
public class GrandExchangeClient
public class OSBGrandExchangeClient
{
public GrandExchangeResult lookupItem(int itemId) throws IOException
public OSBGrandExchangeResult lookupItem(int itemId) throws IOException
{
final HttpUrl url = RuneLiteAPI.getApiBase().newBuilder()
.addPathSegment("osb")
@@ -59,7 +59,7 @@ public class GrandExchangeClient
}
final InputStream in = response.body().byteStream();
return RuneLiteAPI.GSON.fromJson(new InputStreamReader(in), GrandExchangeResult.class);
return RuneLiteAPI.GSON.fromJson(new InputStreamReader(in), OSBGrandExchangeResult.class);
}
catch (JsonParseException ex)
{

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -25,13 +25,11 @@
package net.runelite.http.service;
import ch.qos.logback.classic.LoggerContext;
import com.google.common.base.Strings;
import java.io.IOException;
import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
@@ -44,11 +42,17 @@ import okhttp3.Cache;
import okhttp3.OkHttpClient;
import org.slf4j.ILoggerFactory;
import org.slf4j.impl.StaticLoggerBinder;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.support.SpringBootServletInitializer;
import org.springframework.context.annotation.Bean;
import org.springframework.jdbc.datasource.lookup.JndiDataSourceLookup;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.sql2o.Sql2o;
import org.sql2o.converters.Converter;
@@ -56,6 +60,7 @@ import org.sql2o.quirks.NoQuirks;
@SpringBootApplication
@EnableScheduling
@EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class})
@Slf4j
public class SpringBootWebApplication extends SpringBootServletInitializer
{
@@ -96,35 +101,80 @@ public class SpringBootWebApplication extends SpringBootServletInitializer
};
}
private Context getContext() throws NamingException
@ConfigurationProperties(prefix = "datasource.runelite")
@Bean("dataSourceRuneLite")
public DataSourceProperties dataSourceProperties()
{
Context initCtx = new InitialContext();
return (Context) initCtx.lookup("java:comp/env");
return new DataSourceProperties();
}
@ConfigurationProperties(prefix = "datasource.runelite-cache")
@Bean("dataSourceRuneLiteCache")
public DataSourceProperties dataSourcePropertiesCache()
{
return new DataSourceProperties();
}
@ConfigurationProperties(prefix = "datasource.runelite-tracker")
@Bean("dataSourceRuneLiteTracker")
public DataSourceProperties dataSourcePropertiesTracker()
{
return new DataSourceProperties();
}
@Bean(value = "runelite", destroyMethod = "")
public DataSource runeliteDataSource(@Qualifier("dataSourceRuneLite") DataSourceProperties dataSourceProperties)
{
return getDataSource(dataSourceProperties);
}
@Bean(value = "runelite-cache", destroyMethod = "")
public DataSource runeliteCache2DataSource(@Qualifier("dataSourceRuneLiteCache") DataSourceProperties dataSourceProperties)
{
return getDataSource(dataSourceProperties);
}
@Bean(value = "runelite-tracker", destroyMethod = "")
public DataSource runeliteTrackerDataSource(@Qualifier("dataSourceRuneLiteTracker") DataSourceProperties dataSourceProperties)
{
return getDataSource(dataSourceProperties);
}
@Bean("Runelite SQL2O")
Sql2o sql2o() throws NamingException
public Sql2o sql2o(@Qualifier("runelite") DataSource dataSource)
{
DataSource dataSource = (DataSource) getContext().lookup("jdbc/runelite");
Map<Class, Converter> converters = new HashMap<>();
converters.put(Instant.class, new InstantConverter());
return new Sql2o(dataSource, new NoQuirks(converters));
return createSql2oFromDataSource(dataSource);
}
@Bean("Runelite Cache SQL2O")
Sql2o cacheSql2o() throws NamingException
public Sql2o cacheSql2o(@Qualifier("runelite-cache") DataSource dataSource)
{
DataSource dataSource = (DataSource) getContext().lookup("jdbc/runelite-cache2");
Map<Class, Converter> converters = new HashMap<>();
converters.put(Instant.class, new InstantConverter());
return new Sql2o(dataSource, new NoQuirks(converters));
return createSql2oFromDataSource(dataSource);
}
@Bean("Runelite XP Tracker SQL2O")
Sql2o trackerSql2o() throws NamingException
public Sql2o trackerSql2o(@Qualifier("runelite-tracker") DataSource dataSource)
{
DataSource dataSource = (DataSource) getContext().lookup("jdbc/runelite-tracker");
Map<Class, Converter> converters = new HashMap<>();
return createSql2oFromDataSource(dataSource);
}
private static DataSource getDataSource(DataSourceProperties dataSourceProperties)
{
if (!Strings.isNullOrEmpty(dataSourceProperties.getJndiName()))
{
// Use JNDI provided datasource, which is already configured with pooling
JndiDataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
return dataSourceLookup.getDataSource(dataSourceProperties.getJndiName());
}
else
{
return dataSourceProperties.initializeDataSourceBuilder().build();
}
}
private static Sql2o createSql2oFromDataSource(final DataSource dataSource)
{
final Map<Class, Converter> converters = new HashMap<>();
converters.put(Instant.class, new InstantConverter());
return new Sql2o(dataSource, new NoQuirks(converters));
}

View File

@@ -24,22 +24,42 @@
*/
package net.runelite.http.service;
import java.util.List;
import net.runelite.http.api.RuneLiteAPI;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.GsonHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
/**
* Configure .js as application/json to trick Cloudflare into caching json responses
*/
@Configuration
@EnableWebMvc
public class SpringContentNegotiationConfigurer extends WebMvcConfigurerAdapter
public class SpringWebMvcConfigurer extends WebMvcConfigurerAdapter
{
/**
* Configure .js as application/json to trick Cloudflare into caching json responses
*/
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer)
{
configurer.mediaType("js", MediaType.APPLICATION_JSON);
}
/**
* Use GSON instead of Jackson for JSON serialization
* @param converters
*/
@Override
public void extendMessageConverters(List<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.UserEntry;
import net.runelite.http.service.util.redis.RedisPool;
import net.runelite.http.service.ws.SessionManager;
import net.runelite.http.service.ws.WSService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@@ -241,12 +239,6 @@ public class AccountService
LoginResponse response = new LoginResponse();
response.setUsername(username);
WSService service = SessionManager.findSession(uuid);
if (service != null)
{
service.send(response);
}
try (Jedis jedis = jedisPool.getResource())
{
jedis.publish("session." + uuid, websocketGson.toJson(response, WebsocketMessage.class));
@@ -276,10 +268,4 @@ public class AccountService
{
auth.handle(request, response);
}
@RequestMapping("/wscount")
public int wscount()
{
return SessionManager.getCount();
}
}

View File

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

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

View File

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

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
{
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()
.filter(item -> item.isTradeable)
.mapToInt(item -> item.id)
.toArray();
log.debug("Loaded {} tradeable items", tradeableItems.length);
}

View File

@@ -66,7 +66,7 @@ public class LootTrackerController
}
@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);
if (e == null)
@@ -75,7 +75,7 @@ public class LootTrackerController
return null;
}
return service.get(e.getUser(), count);
return service.get(e.getUser(), count, start);
}
@DeleteMapping

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_DROP_QUERY = "INSERT INTO drops (killId, itemId, itemQuantity) VALUES (LAST_INSERT_ID(), :itemId, :itemQuantity)";
private static final String SELECT_LOOT_QUERY = "SELECT killId,time,type,eventId,itemId,itemQuantity FROM kills JOIN drops ON drops.killId = kills.id WHERE accountId = :accountId ORDER BY TIME DESC LIMIT :limit";
private static final String SELECT_LOOT_QUERY = "SELECT killId,time,type,eventId,itemId,itemQuantity FROM kills JOIN drops ON drops.killId = kills.id WHERE accountId = :accountId ORDER BY TIME DESC LIMIT :limit OFFSET :offset";
private static final String DELETE_LOOT_ACCOUNT = "DELETE FROM kills WHERE accountId = :accountId";
private static final String DELETE_LOOT_ACCOUNT_EVENTID = "DELETE FROM kills WHERE accountId = :accountId AND eventId = :eventId";
@@ -119,7 +119,7 @@ public class LootTrackerService
}
}
public Collection<LootRecord> get(int accountId, int limit)
public Collection<LootRecord> get(int accountId, int limit, int offset)
{
List<LootResult> lootResults;
@@ -128,6 +128,7 @@ public class LootTrackerService
lootResults = con.createQuery(SELECT_LOOT_QUERY)
.addParameter("accountId", accountId)
.addParameter("limit", limit)
.addParameter("offset", offset)
.executeAndFetch(LootResult.class);
}
@@ -141,7 +142,7 @@ public class LootTrackerService
{
if (!gameItems.isEmpty())
{
LootRecord lootRecord = new LootRecord(current.getEventId(), current.getType(), gameItems);
LootRecord lootRecord = new LootRecord(current.getEventId(), current.getType(), gameItems, current.getTime());
lootRecords.add(lootRecord);
gameItems = new ArrayList<>();
@@ -156,7 +157,7 @@ public class LootTrackerService
if (!gameItems.isEmpty())
{
LootRecord lootRecord = new LootRecord(current.getEventId(), current.getType(), gameItems);
LootRecord lootRecord = new LootRecord(current.getEventId(), current.getType(), gameItems, current.getTime());
lootRecords.add(lootRecord);
}

View File

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

View File

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

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);
@Mapping(target = "time", ignore = true)
@Mapping(source = "attack.experience", target = "attack_xp")
@Mapping(source = "defence.experience", target = "defence_xp")
@Mapping(source = "strength.experience", target = "strength_xp")

View File

@@ -29,8 +29,8 @@ import com.google.common.hash.Funnels;
import java.nio.charset.Charset;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayDeque;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.ExecutionException;
import lombok.extern.slf4j.Slf4j;
import net.runelite.http.api.hiscore.HiscoreEndpoint;
@@ -50,8 +50,8 @@ import org.sql2o.Sql2o;
@Slf4j
public class XpTrackerService
{
private static final int QUEUE_LIMIT = 100_000;
private static final Duration UPDATE_TIME = Duration.ofMinutes(5);
private static final int QUEUE_LIMIT = 32768;
private static final int BLOOMFILTER_EXPECTED_INSERTIONS = 100_000;
@Autowired
@Qualifier("Runelite XP Tracker SQL2O")
@@ -60,7 +60,7 @@ public class XpTrackerService
@Autowired
private HiscoreService hiscoreService;
private final Queue<String> usernameUpdateQueue = new ConcurrentLinkedDeque<>();
private final Queue<String> usernameUpdateQueue = new ArrayDeque<>();
private BloomFilter<String> usernameFilter = createFilter();
public void update(String username) throws ExecutionException
@@ -76,13 +76,31 @@ public class XpTrackerService
return;
}
if (usernameUpdateQueue.size() >= QUEUE_LIMIT)
try (Connection con = sql2o.open())
{
log.warn("Username update queue is full ({})", QUEUE_LIMIT);
return;
PlayerEntity playerEntity = findOrCreatePlayer(con, username);
Duration frequency = updateFrequency(playerEntity);
Instant now = Instant.now();
Duration timeSinceLastUpdate = Duration.between(playerEntity.getLast_updated(), now);
if (timeSinceLastUpdate.toMillis() < frequency.toMillis())
{
log.debug("User {} updated too recently", username);
usernameFilter.put(username);
return;
}
synchronized (usernameUpdateQueue)
{
if (usernameUpdateQueue.size() >= QUEUE_LIMIT)
{
log.warn("Username update queue is full ({})", QUEUE_LIMIT);
return;
}
usernameUpdateQueue.add(username);
}
}
usernameUpdateQueue.add(username);
usernameFilter.put(username);
}
@@ -104,13 +122,6 @@ public class XpTrackerService
log.debug("Hiscore for {} already up to date", username);
return;
}
Duration difference = Duration.between(currentXp.getTime(), now);
if (difference.compareTo(UPDATE_TIME) <= 0)
{
log.debug("Updated {} too recently", username);
return;
}
}
con.createQuery("insert into xp (player,attack_xp,defence_xp,strength_xp,hitpoints_xp,ranged_xp,prayer_xp,magic_xp,cooking_xp,woodcutting_xp,"
@@ -172,6 +183,11 @@ public class XpTrackerService
.addParameter("construction_rank", hiscoreResult.getConstruction().getRank())
.addParameter("overall_rank", hiscoreResult.getOverall().getRank())
.executeUpdate();
con.createQuery("update player set rank = :rank, last_updated = CURRENT_TIMESTAMP where id = :id")
.addParameter("id", playerEntity.getId())
.addParameter("rank", hiscoreResult.getOverall().getRank())
.executeUpdate();
}
}
@@ -197,6 +213,7 @@ public class XpTrackerService
playerEntity.setId(id);
playerEntity.setName(username);
playerEntity.setTracked_since(now);
playerEntity.setLast_updated(now);
return playerEntity;
}
@@ -220,18 +237,21 @@ public class XpTrackerService
@Scheduled(fixedDelay = 1000)
public void update() throws ExecutionException
{
String next = usernameUpdateQueue.poll();
String next;
synchronized (usernameUpdateQueue)
{
next = usernameUpdateQueue.poll();
}
if (next == null)
{
return;
}
HiscoreResult hiscoreResult = hiscoreService.lookupUsername(next, HiscoreEndpoint.NORMAL);
update(next, hiscoreResult);
update(next);
}
@Scheduled(fixedDelay = 3 * 60 * 60 * 1000) // 3 hours
@Scheduled(fixedDelay = 6 * 60 * 60 * 1000) // 6 hours
public void clearFilter()
{
usernameFilter = createFilter();
@@ -241,14 +261,47 @@ public class XpTrackerService
{
final BloomFilter<String> filter = BloomFilter.create(
Funnels.stringFunnel(Charset.defaultCharset()),
100_000
BLOOMFILTER_EXPECTED_INSERTIONS
);
for (String toUpdate : usernameUpdateQueue)
synchronized (usernameUpdateQueue)
{
filter.put(toUpdate);
for (String toUpdate : usernameUpdateQueue)
{
filter.put(toUpdate);
}
}
return filter;
}
/**
* scale how often to check hiscore updates for players based on their rank
* @param playerEntity
* @return
*/
private static Duration updateFrequency(PlayerEntity playerEntity)
{
Integer rank = playerEntity.getRank();
if (rank == null || rank == -1)
{
return Duration.ofDays(7);
}
else if (rank < 10_000)
{
return Duration.ofHours(6);
}
else if (rank < 50_000)
{
return Duration.ofDays(2);
}
else if (rank < 100_000)
{
return Duration.ofDays(5);
}
else
{
return Duration.ofDays(7);
}
}
}

View File

@@ -33,4 +33,6 @@ public class PlayerEntity
private Integer id;
private String name;
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
-- ------------------------------------------------------
-- Server version 10.2.9-MariaDB
-- Server version 10.2.18-MariaDB
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
@@ -26,6 +26,8 @@ CREATE TABLE `player` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(32) NOT NULL,
`tracked_since` timestamp NOT NULL DEFAULT current_timestamp(),
`last_updated` timestamp NOT NULL DEFAULT current_timestamp(),
`rank` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
@@ -116,7 +118,7 @@ CREATE TABLE `xp` (
`overall_rank` int(11) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `player_time` (`player`,`time`),
INDEX `idx_time` (`time`),
KEY `idx_time` (`time`),
CONSTRAINT `fk_player` FOREIGN KEY (`player`) REFERENCES `player` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;
@@ -130,4 +132,4 @@ CREATE TABLE `xp` (
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
-- Dump completed on 2018-01-20 18:37:09
-- Dump completed on 2019-02-15 21:01:17

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* Copyright (c) 2019, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -24,57 +24,23 @@
*/
package net.runelite.http.service;
import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
import javax.naming.NamingException;
import net.runelite.http.service.util.InstantConverter;
import org.junit.Ignore;
import org.junit.Test;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.sql2o.Sql2o;
import org.sql2o.converters.Converter;
import org.sql2o.quirks.NoQuirks;
@SpringBootApplication
@EnableScheduling
public class SpringBootWebApplicationTest
{
@Bean("Runelite SQL2O")
Sql2o sql2o()
{
Map<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
@Ignore
public void test() throws InterruptedException
public void run() throws InterruptedException
{
SpringApplication.run(SpringBootWebApplicationTest.class, new String[0]);
String[] args = new String[]{
"--spring.config.location=classpath:/application.yaml,classpath:/dev.yaml"
};
SpringApplication.run(SpringBootWebApplication.class, args);
for (;;)
{
Thread.sleep(100L);
}
}
}
}

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>
<artifactId>runelite-parent</artifactId>
<version>1.5.12-SNAPSHOT</version>
<version>1.5.14-SNAPSHOT</version>
<packaging>pom</packaging>
<name>RuneLite</name>

View File

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

View File

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

View File

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

View File

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

View File

@@ -29,7 +29,7 @@
<parent>
<groupId>net.runelite</groupId>
<artifactId>runelite-parent</artifactId>
<version>1.5.12-SNAPSHOT</version>
<version>1.5.14-SNAPSHOT</version>
</parent>
<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 MAGIC_CHARGING_ORBS = 726;
public static final int MAGIC_MAKE_TABLET = 4068;
public static final int MAGIC_ENCHANTING_JEWELRY = 931;
public static final int BURYING_BONES = 827;
public static final int USING_GILDED_ALTAR = 3705;
public static final int LOOKING_INTO = 832;

View File

@@ -1569,4 +1569,16 @@ public interface Client extends GameEngine
int getRasterizer3D_clipMidY2();
void checkClickbox(Model model, int orientation, int pitchSin, int pitchCos, int yawSin, int yawCos, int x, int y, int z, long hash);
/**
* Sets if a widget is in target mode
*/
void setSpellSelected(boolean selected);
/**
* Returns client item composition cache
*/
NodeCache getItemCompositionCache();
EnumComposition getEnum(int id);
}

View File

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

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_HOVERED = 1198;
public static final int MINIMAP_ORB_XP_ACTIVATED_HOVERED = 1199;
public static final int UNKNOWN_BLACK_BLOBS = 1200;
public static final int MINIMAP_CLICK_MASK = 1200;
public static final int OPTIONS_ZOOM_SLIDER_THUMB = 1201;
public static final int EMOTE_SIT_UP = 1202;
public static final int EMOTE_STAR_JUMP = 1203;

View File

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

View File

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

View File

@@ -25,6 +25,10 @@
*/
package net.runelite.api.coords;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import lombok.Value;
import net.runelite.api.Client;
import static net.runelite.api.Constants.CHUNK_SIZE;
@@ -150,7 +154,8 @@ public class WorldPoint
}
/**
* Gets the coordinate of the tile that contains the passed local point.
* Gets the coordinate of the tile that contains the passed local point,
* accounting for instances.
*
* @param client the client
* @param localPoint the local coordinate
@@ -190,6 +195,46 @@ public class WorldPoint
}
}
/**
* Get occurrences of a tile on the scene, accounting for instances. There may be
* more than one if the same template chunk occurs more than once on the scene.
* @param client
* @param worldPoint
* @return
*/
public static Collection<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
*

View File

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

View File

@@ -79,6 +79,7 @@ public interface Widget
/**
* Gets the current click configuration of the widget.
* @see WidgetConfig
*
* @see WidgetConfig
*/
@@ -551,6 +552,13 @@ public interface Widget
*/
void setOnMouseOverListener(Object... args);
/**
* Sets a script to be ran every frame when the mouse is in the widget bounds
*
* @param args A ScriptID, then the args for the script
*/
void setOnMouseRepeatListener(Object... args);
/**
* Sets a script to be ran when the mouse leaves the widget bounds
*
@@ -565,6 +573,20 @@ public interface Widget
*/
void setOnTimerListener(Object... args);
/**
* Sets a script to be ran when the target mode has been activated for this widget
*
* @param args A ScriptID, then the args for the script
*/
void setOnTargetEnterListener(Object... args);
/**
* Sets a script to be ran when the target mode has been deactivated for this widget
*
* @param args A ScriptID, then the args for the script
*/
void setOnTargetLeaveListener(Object... args);
/**
* If this widget has any listeners on it
*/
@@ -769,4 +791,34 @@ public interface Widget
* Sets if the rectangle is filled or just stroked
*/
void setFilled(boolean filled);
/**
* Verb for spell targets
*/
String getTargetVerb();
/**
* Verb for spell targets
*/
void setTargetVerb(String targetVerb);
/**
* Can widgets under this widgets be clicked in this widgets bounding box
*/
boolean getNoClickThrough();
/**
* Can widgets under this widgets be clicked in this widgets bounding box
*/
void setNoClickThrough(boolean noClickThrough);
/**
* Can widgets under this widgets be scrolled in this widgets bounding box
*/
boolean getNoScrollThrough();
/**
* Can widgets under this widgets be scrolled in this widgets bounding box
*/
void setNoScrollThrough(boolean noScrollThrough);
}

View File

@@ -24,6 +24,8 @@
*/
package net.runelite.api.widgets;
import net.runelite.api.MenuAction;
/**
* Utility class used for defining options to be used on the click mask
* of a {@link Widget}.
@@ -36,12 +38,61 @@ public class WidgetConfig
* Enables displaying a ninth option on a menu.
*/
public static final int SHOW_MENU_OPTION_NINE = 1 << 9;
/**
* Can this widget be used on a item on the floor
*/
public static final int USE_GROUND_ITEM = 1 << 11;
/**
* Can this widget be used on a NPC
*/
public static final int USE_NPC = 2 << 11;
/**
* Can this widget be used on a game object
*/
public static final int USE_OBJECT = 4 << 11;
/**
* Can this widget be used on a player
*/
public static final int USE_PLAYER = 8 << 11;
/**
* Can this widget be used on a item in your inventory
*/
public static final int USE_ITEM = 16 << 11;
/**
* Can this widget be used on a widget with the WIDGET_USE_TARGET flag
*/
public static final int USE_WIDGET = 32 << 11;
/**
* Controls whether or not a widget can have another dragged onto it.
*/
public static final int DRAG_ON = 1 << 17;
/**
* Controls whether or not a widget can be dragged around.
*/
public static final int DRAG = 1 << 20;
/**
* Can widgets with USE_WIDGET be used on this widget
*/
public static final int WIDGET_USE_TARGET = 1 << 21;
/**
* Is the widget an (inventory?) item
*/
public static final int ITEM = 1 << 30;
/**
* Add a USE option
*
* @see MenuAction#ITEM_USE
*/
public static final int ITEM_USE_OP = 1 << 31;
}

View File

@@ -125,6 +125,8 @@ public class WidgetID
public static final int SKOTIZO_GROUP_ID = 308;
public static final int ENTERING_HOUSE_GROUP_ID = 71;
public static final int FULLSCREEN_MAP_GROUP_ID = 165;
public static final int QUESTLIST_GROUP_ID = 399;
public static final int SKILLS_GROUP_ID = 320;
static class WorldMap
{
@@ -292,6 +294,7 @@ public class WidgetID
static final int TOGGLE_RUN_ORB = 22; // Has the "Toggle run" name
static final int RUN_ORB_TEXT = 23;
static final int SPEC_ORB = 28;
static final int WORLDMAP_ORB = 40;
}
static class LoginClickToPlayScreen
@@ -747,4 +750,11 @@ public class WidgetID
{
static final int ROOT = 25;
}
static class QuestList
{
static final int FREE_CONTAINER = 9;
static final int MEMBERS_CONTAINER = 10;
static final int MINIQUEST_CONTAINER = 11;
}
}

View File

@@ -159,6 +159,7 @@ public enum WidgetInfo
MINIMAP_RUN_ORB_TEXT(WidgetID.MINIMAP_GROUP_ID, WidgetID.Minimap.RUN_ORB_TEXT),
MINIMAP_HEALTH_ORB(WidgetID.MINIMAP_GROUP_ID, WidgetID.Minimap.HEALTH_ORB),
MINIMAP_SPEC_ORB(WidgetID.MINIMAP_GROUP_ID, WidgetID.Minimap.SPEC_ORB),
MINIMAP_WORLDMAP_ORB(WidgetID.MINIMAP_GROUP_ID, WidgetID.Minimap.WORLDMAP_ORB),
LOGIN_CLICK_TO_PLAY_SCREEN(WidgetID.LOGIN_CLICK_TO_PLAY_GROUP_ID, 0),
LOGIN_CLICK_TO_PLAY_SCREEN_MESSAGE_OF_THE_DAY(WidgetID.LOGIN_CLICK_TO_PLAY_GROUP_ID, WidgetID.LoginClickToPlayScreen.MESSAGE_OF_THE_DAY),
@@ -463,7 +464,11 @@ public enum WidgetInfo
SKOTIZO_CONTAINER(WidgetID.SKOTIZO_GROUP_ID, WidgetID.Skotizo.CONTAINER),
FULLSCREEN_MAP_ROOT(WidgetID.FULLSCREEN_MAP_GROUP_ID, WidgetID.FullScreenMap.ROOT);
FULLSCREEN_MAP_ROOT(WidgetID.FULLSCREEN_MAP_GROUP_ID, WidgetID.FullScreenMap.ROOT),
QUESTLIST_FREE_CONTAINER(WidgetID.QUESTLIST_GROUP_ID, WidgetID.QuestList.FREE_CONTAINER),
QUESTLIST_MEMBERS_CONTAINER(WidgetID.QUESTLIST_GROUP_ID, WidgetID.QuestList.MEMBERS_CONTAINER),
QUESTLIST_MINIQUEST_CONTAINER(WidgetID.QUESTLIST_GROUP_ID, WidgetID.QuestList.MINIQUEST_CONTAINER);
private final int groupId;
private final int childId;

View File

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

View File

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

View File

@@ -183,9 +183,9 @@ public class RuneLite
System.exit(0);
}
final boolean developerMode = options.has("developer-mode");
final boolean developerMode = options.has("developer-mode") && RuneLiteProperties.getLauncherVersion() == null;
if (developerMode && RuneLiteProperties.getLauncherVersion() == null)
if (developerMode)
{
boolean assertions = false;
assert assertions = true;

View File

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

View File

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

View File

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

View File

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

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

View File

@@ -28,6 +28,7 @@ import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.inject.Inject;
import java.awt.image.BufferedImage;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import javax.annotation.Nullable;
@@ -36,11 +37,14 @@ import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.Client;
import net.runelite.api.GameState;
import net.runelite.api.SpritePixels;
import net.runelite.client.callback.ClientThread;
import net.runelite.client.util.ImageUtil;
@Slf4j
@Singleton
public class SpriteManager
{
@@ -127,4 +131,36 @@ public class SpriteManager
});
});
}
public void addSpriteOverrides(SpriteOverride[] add)
{
if (add.length <= 0)
{
return;
}
clientThread.invokeLater(() ->
{
Map<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;
import com.google.common.base.Strings;
import com.google.common.primitives.Ints;
import com.google.inject.Inject;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.datatransfer.DataFlavor;
@@ -33,21 +36,25 @@ import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.IntPredicate;
import java.util.function.Predicate;
import java.util.function.ToIntFunction;
import java.util.regex.Pattern;
import javax.swing.SwingUtilities;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.FontTypeFace;
import net.runelite.api.FontID;
import net.runelite.api.widgets.WidgetType;
import net.runelite.api.FontTypeFace;
import net.runelite.api.widgets.JavaScriptCallback;
import net.runelite.api.widgets.Widget;
import net.runelite.api.widgets.WidgetPositionMode;
import net.runelite.api.widgets.WidgetSizeMode;
import net.runelite.api.widgets.WidgetTextAlignment;
import net.runelite.api.widgets.WidgetType;
import net.runelite.client.callback.ClientThread;
import net.runelite.client.input.KeyListener;
import net.runelite.client.input.MouseListener;
@@ -57,6 +64,7 @@ import net.runelite.client.util.Text;
public class ChatboxTextInput extends ChatboxInput implements KeyListener, MouseListener
{
private static final int CURSOR_FLASH_RATE_MILLIS = 1000;
private static final Pattern BREAK_MATCHER = Pattern.compile("[^a-zA-Z0-9']");
private final ChatboxPanelManager chatboxPanelManager;
private final ClientThread clientThread;
@@ -66,13 +74,24 @@ public class ChatboxTextInput extends ChatboxInput implements KeyListener, Mouse
return i -> i >= 32 && i < 127;
}
@AllArgsConstructor
private static class Line
{
private final int start;
private final int end;
private final String text;
}
@Getter
private String prompt;
@Getter
private int lines;
private StringBuffer value = new StringBuffer();
@Getter
private int cursor = 0;
private int cursorStart = 0;
@Getter
private int cursorEnd = 0;
@@ -95,12 +114,14 @@ public class ChatboxTextInput extends ChatboxInput implements KeyListener, Mouse
@Getter
private int fontID = FontID.QUILL_8;
// This is a lambda so I can have atomic updates for it's captures
private ToIntFunction<MouseEvent> getCharOffset = null;
private Predicate<MouseEvent> isInBounds = null;
@Getter
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
protected ChatboxTextInput(ChatboxPanelManager chatboxPanelManager, ClientThread clientThread)
{
@@ -108,6 +129,16 @@ public class ChatboxTextInput extends ChatboxInput implements KeyListener, Mouse
this.clientThread = clientThread;
}
public ChatboxTextInput lines(int lines)
{
this.lines = lines;
if (built)
{
clientThread.invoke(this::update);
}
return this;
}
public ChatboxTextInput prompt(String prompt)
{
this.prompt = prompt;
@@ -157,7 +188,7 @@ public class ChatboxTextInput extends ChatboxInput implements KeyListener, Mouse
end = v;
}
this.cursor = start;
this.cursorStart = start;
this.cursorEnd = end;
if (built)
@@ -209,7 +240,6 @@ public class ChatboxTextInput extends ChatboxInput implements KeyListener, Mouse
protected void update()
{
this.built = true;
Widget container = chatboxPanelManager.getContainerWidget();
container.deleteAllChildren();
@@ -232,103 +262,209 @@ public class ChatboxTextInput extends ChatboxInput implements KeyListener, Mouse
protected void buildEdit(int x, int y, int w, int h)
{
final List<Line> editLines = new ArrayList<>();
Widget container = chatboxPanelManager.getContainerWidget();
String lt = Text.escapeJagex(value.substring(0, this.cursor));
String mt = Text.escapeJagex(value.substring(this.cursor, this.cursorEnd));
String rt = Text.escapeJagex(value.substring(this.cursorEnd));
Widget leftText = container.createChild(-1, WidgetType.TEXT);
Widget cursor = container.createChild(-1, WidgetType.RECTANGLE);
Widget middleText = container.createChild(-1, WidgetType.TEXT);
Widget rightText = container.createChild(-1, WidgetType.TEXT);
leftText.setFontId(fontID);
FontTypeFace font = leftText.getFont();
final Widget cursor = container.createChild(-1, WidgetType.RECTANGLE);
long start = System.currentTimeMillis();
cursor.setOnTimerListener((JavaScriptCallback) ev ->
{
boolean on = (System.currentTimeMillis() - start) % CURSOR_FLASH_RATE_MILLIS > (CURSOR_FLASH_RATE_MILLIS / 2);
cursor.setOpacity(on ? 255 : 0);
});
cursor.setTextColor(0xFFFFFF);
cursor.setHasListener(true);
cursor.setFilled(true);
cursor.setFontId(fontID);
FontTypeFace font = cursor.getFont();
if (h <= 0)
{
h = font.getBaseline();
}
int ltw = font.getTextWidth(lt);
int mtw = font.getTextWidth(mt);
int rtw = font.getTextWidth(rt);
final int oy = y;
final int ox = x;
final int oh = h;
int fullWidth = ltw + mtw + rtw;
int ox = x;
if (w > 0)
int breakIndex = -1;
final StringBuilder sb = new StringBuilder();
for (int i = 0; i < value.length(); i++)
{
x += (w - fullWidth) / 2;
}
int ltx = x;
int mtx = ltx + ltw;
int rtx = mtx + mtw;
leftText.setText(lt);
leftText.setOriginalX(ltx);
leftText.setOriginalY(y);
leftText.setOriginalWidth(ltw);
leftText.setOriginalHeight(h);
leftText.revalidate();
if (!mt.isEmpty())
{
cursor.setTextColor(0x113399);
}
else
{
cursor.setTextColor(0xFFFFFF);
long start = System.currentTimeMillis();
cursor.setOnTimerListener((JavaScriptCallback) ev ->
int count = i - sb.length();
final String c = value.charAt(i) + "";
sb.append(c);
if (BREAK_MATCHER.matcher(c).matches())
{
boolean on = (System.currentTimeMillis() - start) % CURSOR_FLASH_RATE_MILLIS > (CURSOR_FLASH_RATE_MILLIS / 2);
cursor.setOpacity(on ? 255 : 0);
});
cursor.setHasListener(true);
breakIndex = sb.length();
}
if (i == value.length() - 1)
{
Line line = new Line(count, count + sb.length() - 1, sb.toString());
editLines.add(line);
break;
}
if (font.getTextWidth(sb.toString() + value.charAt(i + 1)) < w)
{
continue;
}
if (editLines.size() < this.lines - 1 || this.lines == 0)
{
if (breakIndex > 1)
{
String str = sb.substring(0, breakIndex);
Line line = new Line(count, count + str.length() - 1, str);
editLines.add(line);
sb.replace(0, breakIndex, "");
breakIndex = -1;
continue;
}
Line line = new Line(count, count + sb.length() - 1, sb.toString());
editLines.add(line);
sb.replace(0, sb.length(), "");
}
}
cursor.setFilled(true);
cursor.setOriginalX(mtx - 1);
cursor.setOriginalY(y);
cursor.setOriginalWidth(2 + mtw);
cursor.setOriginalHeight(h);
cursor.revalidate();
middleText.setText(mt);
middleText.setFontId(fontID);
middleText.setOriginalX(mtx);
middleText.setOriginalY(y);
middleText.setOriginalWidth(mtw);
middleText.setOriginalHeight(h);
middleText.setTextColor(0xFFFFFF);
middleText.revalidate();
Rectangle bounds = new Rectangle(container.getCanvasLocation().getX() + container.getWidth(), y, 0, editLines.size() * oh);
for (int i = 0; i < editLines.size() || i == 0; i++)
{
final Line line = editLines.size() > 0 ? editLines.get(i) : new Line(0, 0, "");
final String text = line.text;
final int len = text.length();
rightText.setText(rt);
rightText.setFontId(fontID);
rightText.setOriginalX(rtx);
rightText.setOriginalY(y);
rightText.setOriginalWidth(rtw);
rightText.setOriginalHeight(h);
rightText.revalidate();
String lt = Text.escapeJagex(text);
String mt = "";
String rt = "";
final boolean isStartLine = cursorOnLine(cursorStart, line.start, line.end)
|| (cursorOnLine(cursorStart, line.start, line.end + 1) && i == editLines.size() - 1);
final boolean isEndLine = cursorOnLine(cursorEnd, line.start, line.end);
if (isStartLine || isEndLine || (cursorEnd > line.end && cursorStart < line.start))
{
final int cIdx = Ints.constrainToRange(cursorStart - line.start, 0, len);
final int ceIdx = Ints.constrainToRange(cursorEnd - line.start, 0, len);
lt = Text.escapeJagex(text.substring(0, cIdx));
mt = Text.escapeJagex(text.substring(cIdx, ceIdx));
rt = Text.escapeJagex(text.substring(ceIdx));
}
final int ltw = font.getTextWidth(lt);
final int mtw = font.getTextWidth(mt);
final int rtw = font.getTextWidth(rt);
final int fullWidth = ltw + mtw + rtw;
int ltx = ox;
if (w > 0)
{
ltx += (w - fullWidth) / 2;
}
final int mtx = ltx + ltw;
final int rtx = mtx + mtw;
if (ltx < bounds.x)
{
bounds.setLocation(ltx, bounds.y);
}
if (fullWidth > bounds.width)
{
bounds.setSize(fullWidth, bounds.height);
}
if (editLines.size() == 0 || isStartLine)
{
cursor.setOriginalX(mtx - 1);
cursor.setOriginalY(y);
cursor.setOriginalWidth(2);
cursor.setOriginalHeight(h);
cursor.revalidate();
}
if (!Strings.isNullOrEmpty(lt))
{
final Widget leftText = container.createChild(-1, WidgetType.TEXT);
leftText.setFontId(fontID);
leftText.setText(lt);
leftText.setOriginalX(ltx);
leftText.setOriginalY(y);
leftText.setOriginalWidth(ltw);
leftText.setOriginalHeight(h);
leftText.revalidate();
}
if (!Strings.isNullOrEmpty(mt))
{
final Widget background = container.createChild(-1, WidgetType.RECTANGLE);
background.setTextColor(0x113399);
background.setFilled(true);
background.setOriginalX(mtx - 1);
background.setOriginalY(y);
background.setOriginalWidth(2 + mtw);
background.setOriginalHeight(h);
background.revalidate();
final Widget middleText = container.createChild(-1, WidgetType.TEXT);
middleText.setText(mt);
middleText.setFontId(fontID);
middleText.setOriginalX(mtx);
middleText.setOriginalY(y);
middleText.setOriginalWidth(mtw);
middleText.setOriginalHeight(h);
middleText.setTextColor(0xFFFFFF);
middleText.revalidate();
}
if (!Strings.isNullOrEmpty(rt))
{
final Widget rightText = container.createChild(-1, WidgetType.TEXT);
rightText.setText(rt);
rightText.setFontId(fontID);
rightText.setOriginalX(rtx);
rightText.setOriginalY(y);
rightText.setOriginalWidth(rtw);
rightText.setOriginalHeight(h);
rightText.revalidate();
}
y += h;
}
net.runelite.api.Point ccl = container.getCanvasLocation();
int canvasX = ltx + ccl.getX();
Rectangle bounds = new Rectangle(ccl.getX() + ox, ccl.getY() + y, w > 0 ? w : fullWidth, h);
String tsValue = value.toString();
isInBounds = ev -> bounds.contains(ev.getPoint());
getCharOffset = ev ->
isInBounds = ev -> bounds.contains(new Point(ev.getX() - ccl.getX(), ev.getY() - ccl.getY()));
getPointCharOffset = p ->
{
if (fullWidth <= 0)
if (bounds.width <= 0)
{
return 0;
}
int cx = ev.getX() - canvasX;
int cx = p.x - ccl.getX() - ox;
int cy = p.y - ccl.getY() - oy;
int charIndex = (tsValue.length() * cx) / fullWidth;
int currentLine = Ints.constrainToRange(cy / oh, 0, editLines.size() - 1);
final Line line = editLines.get(currentLine);
final String tsValue = line.text;
int charIndex = tsValue.length();
int fullWidth = font.getTextWidth(tsValue);
int tx = ox;
if (w > 0)
{
tx += (w - fullWidth) / 2;
}
cx -= tx;
// `i` is used to track max execution time incase there is a font with ligature width data that causes this to fail
for (int i = tsValue.length(); i >= 0 && charIndex >= 0 && charIndex <= tsValue.length(); i--)
@@ -353,22 +489,72 @@ public class ChatboxTextInput extends ChatboxInput implements KeyListener, Mouse
break;
}
if (charIndex < 0)
charIndex = Ints.constrainToRange(charIndex, 0, tsValue.length());
return line.start + charIndex;
};
getLineOffset = code ->
{
if (editLines.size() < 2)
{
charIndex = 0;
}
if (charIndex > tsValue.length())
{
charIndex = tsValue.length();
return cursorStart;
}
return charIndex;
int currentLine = -1;
for (int i = 0; i < editLines.size(); i++)
{
Line l = editLines.get(i);
if (cursorOnLine(cursorStart, l.start, l.end)
|| (cursorOnLine(cursorStart, l.start, l.end + 1) && i == editLines.size() - 1))
{
currentLine = i;
break;
}
}
if (currentLine == -1
|| (code == KeyEvent.VK_UP && currentLine == 0)
|| (code == KeyEvent.VK_DOWN && currentLine == editLines.size() - 1))
{
return cursorStart;
}
final Line line = editLines.get(currentLine);
final int direction = code == KeyEvent.VK_UP ? -1 : 1;
final Point dest = new Point(cursor.getCanvasLocation().getX(), cursor.getCanvasLocation().getY() + (direction * oh));
final int charOffset = getPointCharOffset.applyAsInt(dest);
// Place cursor on right line if whitespace keep it on the same line or skip a line
final Line nextLine = editLines.get(currentLine + direction);
if ((direction == -1 && charOffset >= line.start)
|| (direction == 1 && (charOffset > nextLine.end && (currentLine + direction != editLines.size() - 1))))
{
return nextLine.end;
}
return charOffset;
};
}
private boolean cursorOnLine(final int cursor, final int start, final int end)
{
return (cursor >= start) && (cursor <= end);
}
private int getCharOffset(MouseEvent ev)
{
if (getPointCharOffset == null)
{
return 0;
}
return getPointCharOffset.applyAsInt(ev.getPoint());
}
@Override
protected void open()
{
this.built = true;
update();
}
@@ -398,12 +584,12 @@ public class ChatboxTextInput extends ChatboxInput implements KeyListener, Mouse
char c = e.getKeyChar();
if (charValidator.test(c))
{
if (cursor != cursorEnd)
if (cursorStart != cursorEnd)
{
value.delete(cursor, cursorEnd);
value.delete(cursorStart, cursorEnd);
}
value.insert(cursor, c);
cursorAt(cursor + 1);
value.insert(cursorStart, c);
cursorAt(cursorStart + 1);
if (onChanged != null)
{
onChanged.accept(getValue());
@@ -421,13 +607,13 @@ public class ChatboxTextInput extends ChatboxInput implements KeyListener, Mouse
{
case KeyEvent.VK_X:
case KeyEvent.VK_C:
if (cursor != cursorEnd)
if (cursorStart != cursorEnd)
{
String s = value.substring(cursor, cursorEnd);
String s = value.substring(cursorStart, cursorEnd);
if (code == KeyEvent.VK_X)
{
value.delete(cursor, cursorEnd);
cursorAt(cursor);
value.delete(cursorStart, cursorEnd);
cursorAt(cursorStart);
}
Toolkit.getDefaultToolkit()
.getSystemClipboard()
@@ -441,20 +627,20 @@ public class ChatboxTextInput extends ChatboxInput implements KeyListener, Mouse
.getSystemClipboard()
.getData(DataFlavor.stringFlavor)
.toString();
if (cursor != cursorEnd)
if (cursorStart != cursorEnd)
{
value.delete(cursor, cursorEnd);
value.delete(cursorStart, cursorEnd);
}
for (int i = 0; i < s.length(); i++)
{
char ch = s.charAt(i);
if (charValidator.test(ch))
{
value.insert(cursor, ch);
cursor++;
value.insert(cursorStart, ch);
cursorStart++;
}
}
cursorAt(cursor);
cursorAt(cursorStart);
if (onChanged != null)
{
onChanged.accept(getValue());
@@ -468,13 +654,13 @@ public class ChatboxTextInput extends ChatboxInput implements KeyListener, Mouse
}
return;
}
int newPos = cursor;
int newPos = cursorStart;
if (ev.isShiftDown())
{
if (selectionEnd == -1 || selectionStart == -1)
{
selectionStart = cursor;
selectionEnd = cursor;
selectionStart = cursorStart;
selectionEnd = cursorStart;
}
newPos = selectionEnd;
}
@@ -486,20 +672,20 @@ public class ChatboxTextInput extends ChatboxInput implements KeyListener, Mouse
switch (code)
{
case KeyEvent.VK_DELETE:
if (cursor != cursorEnd)
if (cursorStart != cursorEnd)
{
value.delete(cursor, cursorEnd);
cursorAt(cursor);
value.delete(cursorStart, cursorEnd);
cursorAt(cursorStart);
if (onChanged != null)
{
onChanged.accept(getValue());
}
return;
}
if (cursor < value.length())
if (cursorStart < value.length())
{
value.deleteCharAt(cursor);
cursorAt(cursor);
value.deleteCharAt(cursorStart);
cursorAt(cursorStart);
if (onChanged != null)
{
onChanged.accept(getValue());
@@ -507,20 +693,20 @@ public class ChatboxTextInput extends ChatboxInput implements KeyListener, Mouse
}
return;
case KeyEvent.VK_BACK_SPACE:
if (cursor != cursorEnd)
if (cursorStart != cursorEnd)
{
value.delete(cursor, cursorEnd);
cursorAt(cursor);
value.delete(cursorStart, cursorEnd);
cursorAt(cursorStart);
if (onChanged != null)
{
onChanged.accept(getValue());
}
return;
}
if (cursor > 0)
if (cursorStart > 0)
{
value.deleteCharAt(cursor - 1);
cursorAt(cursor - 1);
value.deleteCharAt(cursorStart - 1);
cursorAt(cursorStart - 1);
if (onChanged != null)
{
onChanged.accept(getValue());
@@ -535,6 +721,14 @@ public class ChatboxTextInput extends ChatboxInput implements KeyListener, Mouse
ev.consume();
newPos++;
break;
case KeyEvent.VK_UP:
ev.consume();
newPos = getLineOffset.applyAsInt(code);
break;
case KeyEvent.VK_DOWN:
ev.consume();
newPos = getLineOffset.applyAsInt(code);
break;
case KeyEvent.VK_HOME:
ev.consume();
newPos = 0;
@@ -553,9 +747,9 @@ public class ChatboxTextInput extends ChatboxInput implements KeyListener, Mouse
return;
case KeyEvent.VK_ESCAPE:
ev.consume();
if (cursor != cursorEnd)
if (cursorStart != cursorEnd)
{
cursorAt(cursor);
cursorAt(cursorStart);
return;
}
chatboxPanelManager.close();
@@ -602,16 +796,16 @@ public class ChatboxTextInput extends ChatboxInput implements KeyListener, Mouse
}
if (isInBounds == null || !isInBounds.test(mouseEvent))
{
if (cursor != cursorEnd)
if (cursorStart != cursorEnd)
{
selectionStart = -1;
selectionEnd = -1;
cursorAt(getCharOffset.applyAsInt(mouseEvent));
cursorAt(getCharOffset(mouseEvent));
}
return mouseEvent;
}
int nco = getCharOffset.applyAsInt(mouseEvent);
int nco = getCharOffset(mouseEvent);
if (mouseEvent.isShiftDown() && selectionEnd != -1)
{
@@ -653,7 +847,7 @@ public class ChatboxTextInput extends ChatboxInput implements KeyListener, Mouse
return mouseEvent;
}
int nco = getCharOffset.applyAsInt(mouseEvent);
int nco = getCharOffset(mouseEvent);
if (selectionStart != -1)
{
selectionEnd = nco;

View File

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

View File

@@ -38,10 +38,12 @@ import net.runelite.api.Item;
import net.runelite.api.ItemID;
import static net.runelite.api.ItemID.AGILITY_ARENA_TICKET;
import net.runelite.api.Player;
import net.runelite.api.Skill;
import static net.runelite.api.Skill.AGILITY;
import net.runelite.api.Tile;
import net.runelite.api.TileObject;
import net.runelite.api.coords.WorldPoint;
import net.runelite.api.events.BoostedLevelChanged;
import net.runelite.api.events.ConfigChanged;
import net.runelite.api.events.DecorativeObjectChanged;
import net.runelite.api.events.DecorativeObjectDespawned;
@@ -63,6 +65,7 @@ import net.runelite.api.events.WallObjectSpawned;
import net.runelite.client.Notifier;
import net.runelite.client.config.ConfigManager;
import net.runelite.client.eventbus.Subscribe;
import net.runelite.client.game.AgilityShortcut;
import net.runelite.client.game.ItemManager;
import net.runelite.client.plugins.Plugin;
import net.runelite.client.plugins.PluginDescriptor;
@@ -80,7 +83,7 @@ public class AgilityPlugin extends Plugin
private static final int AGILITY_ARENA_REGION_ID = 11157;
@Getter
private final Map<TileObject, Tile> obstacles = new HashMap<>();
private final Map<TileObject, Obstacle> obstacles = new HashMap<>();
@Getter
private final List<Tile> marksOfGrace = new ArrayList<>();
@@ -115,6 +118,9 @@ public class AgilityPlugin extends Plugin
private int lastAgilityXp;
private WorldPoint lastArenaTicketPosition;
@Getter
private int agilityLevel;
@Provides
AgilityConfig getConfig(ConfigManager configManager)
{
@@ -126,6 +132,7 @@ public class AgilityPlugin extends Plugin
{
overlayManager.add(agilityOverlay);
overlayManager.add(lapCounterOverlay);
agilityLevel = client.getBoostedSkillLevel(Skill.AGILITY);
}
@Override
@@ -136,6 +143,7 @@ public class AgilityPlugin extends Plugin
marksOfGrace.clear();
obstacles.clear();
session = null;
agilityLevel = 0;
}
@Subscribe
@@ -208,6 +216,17 @@ public class AgilityPlugin extends Plugin
}
}
@Subscribe
public void onBoostedLevelChanged(BoostedLevelChanged boostedLevelChanged)
{
Skill skill = boostedLevelChanged.getSkill();
if (skill == AGILITY)
{
agilityLevel = client.getBoostedSkillLevel(skill);
}
}
@Subscribe
public void onItemSpawned(ItemSpawned itemSpawned)
{
@@ -366,11 +385,40 @@ public class AgilityPlugin extends Plugin
}
if (Obstacles.COURSE_OBSTACLE_IDS.contains(newObject.getId()) ||
Obstacles.SHORTCUT_OBSTACLE_IDS.contains(newObject.getId()) ||
(Obstacles.TRAP_OBSTACLE_IDS.contains(newObject.getId())
&& Obstacles.TRAP_OBSTACLE_REGIONS.contains(newObject.getWorldLocation().getRegionID())))
{
obstacles.put(newObject, tile);
obstacles.put(newObject, new Obstacle(tile, null));
}
if (Obstacles.SHORTCUT_OBSTACLE_IDS.containsKey(newObject.getId()))
{
AgilityShortcut closestShortcut = null;
int distance = -1;
// Find the closest shortcut to this object
for (AgilityShortcut shortcut : Obstacles.SHORTCUT_OBSTACLE_IDS.get(newObject.getId()))
{
if (shortcut.getWorldLocation() == null)
{
closestShortcut = shortcut;
break;
}
else
{
int newDistance = shortcut.getWorldLocation().distanceTo2D(newObject.getWorldLocation());
if (closestShortcut == null || newDistance < distance)
{
closestShortcut = shortcut;
distance = newDistance;
}
}
}
if (closestShortcut != null)
{
obstacles.put(newObject, new Obstacle(tile, closestShortcut));
}
}
}
}

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;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multimap;
import java.util.List;
import java.util.Set;
import static net.runelite.api.NullObjectID.*;
import static net.runelite.api.NullObjectID.NULL_10872;
import static net.runelite.api.NullObjectID.NULL_10873;
import static net.runelite.api.NullObjectID.NULL_12945;
import static net.runelite.api.NullObjectID.NULL_18083;
import static net.runelite.api.NullObjectID.NULL_18116;
import static net.runelite.api.NullObjectID.NULL_18122;
import static net.runelite.api.NullObjectID.NULL_18124;
import static net.runelite.api.NullObjectID.NULL_18129;
import static net.runelite.api.NullObjectID.NULL_18130;
import static net.runelite.api.NullObjectID.NULL_18132;
import static net.runelite.api.NullObjectID.NULL_18133;
import static net.runelite.api.NullObjectID.NULL_18135;
import static net.runelite.api.NullObjectID.NULL_18136;
import static net.runelite.api.NullObjectID.NULL_3550;
import static net.runelite.api.ObjectID.*;
import net.runelite.client.game.AgilityShortcut;
class Obstacles
{
@@ -62,7 +78,7 @@ class Obstacles
STEPPING_STONE_15412, TROPICAL_TREE_15414, MONKEYBARS_15417, SKULL_SLOPE_15483, ROPE_15487, TROPICAL_TREE_16062,
// Falador
ROUGH_WALL_10833, TIGHTROPE_10834, HAND_HOLDS_10836, GAP_11161, GAP_11360, TIGHTROPE_11361,
TIGHTROPE_11364, GAP_11365, LEDGE_11366, LEDGE_11367, LEDGE_11368, LEDGE_11370, EDGE_11371,
TIGHTROPE_11364, GAP_11365, LEDGE_11366, LEDGE_11367, LEDGE_11369, LEDGE_11370, EDGE_11371,
// Wilderness
OBSTACLE_PIPE_23137, ROPESWING_23132, STEPPING_STONE_23556, LOG_BALANCE_23542, ROCKS_23640,
// Seers
@@ -91,144 +107,7 @@ class Obstacles
ZIP_LINE_11645, ZIP_LINE_11646
);
static final Set<Integer> SHORTCUT_OBSTACLE_IDS = ImmutableSet.of(
// Grand Exchange
UNDERWALL_TUNNEL_16529, UNDERWALL_TUNNEL_16530,
// South Varrock
STEPPING_STONE_16533, FENCE_16518, ROCKS_16549, ROCKS_16550,
// Falador
WALL_17049, WALL_17050, CRUMBLING_WALL_24222, UNDERWALL_TUNNEL, UNDERWALL_TUNNEL_16528, CREVICE_16543,
// Draynor
UNDERWALL_TUNNEL_19032, UNDERWALL_TUNNEL_19036,
// South Lumbridge
BROKEN_RAFT, STEPPING_STONE_16513,
// Trollheim
ROCKS_3790, ROCKS_3791, ROCKS_3803, ROCKS_3804, ROCKS_16523, ROCKS_16524, ROCKS_3748, ROCKS_16545, ROCKS_16521,
ROCKS_16522, ROCKS_16464,
// North Camelot
LOG_BALANCE_16540, LOG_BALANCE_16541, LOG_BALANCE_16542,
// Rellekka
BROKEN_FENCE,
// Ardougne
LOG_BALANCE_16546, LOG_BALANCE_16547, LOG_BALANCE_16548,
// Yanille
CASTLE_WALL, HOLE_16520, WALL_17047,
// Observatory
NULL_31849,
// Gnome Stronghold
ROCKS_16534, ROCKS_16535,
// Karamja Volcano
STRONG_TREE_17074,
// Shilo Village
STEPPING_STONE_16466,
// Vine east of Shilo Village
NULL_26884, NULL_26886,
// Stepping stones east of Shilo Village
STEPPING_STONES, STEPPING_STONES_23646, STEPPING_STONES_23647,
// Middle of Karamja
A_WOODEN_LOG,
// Slayer Tower
SPIKEY_CHAIN, SPIKEY_CHAIN_16538,
// Fremennik Slayer Cave
STRANGE_FLOOR_16544, CREVICE_16539, STEPS_29993,
// Wilderness
STEPPING_STONE_14918, STEPPING_STONE_14917, ROCKY_HANDHOLDS_26404, ROCKY_HANDHOLDS_26405, ROCKY_HANDHOLDS_26406,
// Godwars
ROCKY_HANDHOLDS_26400, ROCKY_HANDHOLDS_26401, ROCKY_HANDHOLDS_26402,
// Seers' Village Coal Mine
LOG_BALANCE_23274,
// Arceuus Essence Mine
ROCKS_27984, ROCKS_27985, BOULDER_27990, ROCKS_27987, ROCKS_27988,
// Wintertodt
GAP_29326,
// Gnome Stronghold Slayer Underground
TUNNEL_30174, TUNNEL_30175,
// Taverley Underground
OBSTACLE_PIPE_16509, STRANGE_FLOOR, ROCKS, ROCKS_14106, LOOSE_RAILING_28849,
// Heroes Guild
CREVICE_9739, CREVICE_9740,
// Fossil Island
HOLE_31481, HOLE_31482, LADDER_30938, LADDER_30939, LADDER_30940, LADDER_30941, ROPE_ANCHOR, ROPE_ANCHOR_30917,
RUBBER_CAP_MUSHROOM,
ROCKS_31757, ROCKS_31758, ROCKS_31759, PILLAR_31809,
// West Brimhaven
ROPESWING_23568, ROPESWING_23569,
// Brimhaven Dungeon
VINE_26880, VINE_26882, PIPE_21728, STEPPING_STONE_19040, PIPE_21727, LOG_BALANCE_20882, LOG_BALANCE_20884,
STEPPING_STONE_21738, STEPPING_STONE_21739, TIGHTGAP,
// Lumbridge
STILE_12982,
// Edgeville Dungeon
MONKEYBARS_23566, OBSTACLE_PIPE_16511,
// Miscellania
STEPPING_STONE_11768,
// Kalphite
CREVICE_16465,
// Eagles' Peak
ROCKS_19849,
// Catherby
CROSSBOW_TREE_17062, ROCKS_17042,
// McGrubor's Woods
LOOSE_RAILING,
// Cairn Isle
ROCKS_2231,
// South Kourend
STEPPING_STONE_29728, STEPPING_STONE_29729, STEPPING_STONE_29730,
// Cosmic Temple
JUTTING_WALL_17002,
// Arandar
ROCKS_16514, ROCKS_16515, LOG_BALANCE_3933,
// South River Salve
STEPPING_STONE_13504,
DARK_TUNNEL_10047,
// Ectofuntus
WEATHERED_WALL, WEATHERED_WALL_16526,
// Mos Le'Harmless
STEPPING_STONE_19042,
// North River Salve
ROCKS_16998, ROCKS_16999, ORNATE_RAILING, ORNATE_RAILING_17000,
// West Zul-Andra
STEPPING_STONE_10663,
// Yanille Agility Dungeon
BALANCING_LEDGE_23548, OBSTACLE_PIPE_23140, MONKEYBARS_23567, PILE_OF_RUBBLE_23563, PILE_OF_RUBBLE_23564,
// High Level Wilderness Dungeon
CREVICE_19043,
// Revenant Caves
PILLAR_31561,
// Elf Camp Isafdar Tirranwn
LOG_BALANCE_3931, LOG_BALANCE_3930, LOG_BALANCE_3929, LOG_BALANCE_3932, DENSE_FOREST_3938, DENSE_FOREST_3939,
DENSE_FOREST_3998, DENSE_FOREST_3999, DENSE_FOREST, LEAVES, LEAVES_3924, LEAVES_3925, STICKS, TRIPWIRE,
// Gu'Tanoth bridge
GAP, GAP_2831,
// Lumbridge Swamp Caves
STEPPING_STONE_5948, STEPPING_STONE_5949, ROCKS_6673,
// Morytania Pirate Ship
ROCK_16115,
// Agility Pyramid Entrance
CLIMBING_ROCKS_11948, CLIMBING_ROCKS_11949,
// Lumber Yard
BROKEN_FENCE_2618,
// Ungael and Vorkath crater
NULL_25337, NULL_29868, NULL_29869, NULL_29870, ICE_CHUNKS_31822, NULL_31823, ICE_CHUNKS_31990,
// Underwater Area Fossil Island
TUNNEL_30959, HOLE_30966, OBSTACLE, OBSTACLE_30767, OBSTACLE_30964, OBSTACLE_30962,
// Tree Gnome Village
LOOSE_RAILING_2186,
// Weiss
LITTLE_BOULDER, ROCKSLIDE_33184, ROCKSLIDE_33185, NULL_33327, NULL_33328, LEDGE_33190, ROCKSLIDE_33191, FALLEN_TREE_33192,
// Al-Kharid
BROKEN_WALL_33344, BIG_WINDOW,
// Burgh de Rott
LOW_FENCE,
// Taverley
STILE,
// Asgarnian Ice Dungeon
STEPS,
// Fossil Island Wyvern Cave
STAIRS_31485,
// Mount Karuulm
ROCKS_34397, ROCKS_34396
);
static final Multimap<Integer, AgilityShortcut> SHORTCUT_OBSTACLE_IDS;
static final Set<Integer> TRAP_OBSTACLE_IDS = ImmutableSet.of(
// Agility pyramid
@@ -236,4 +115,17 @@ class Obstacles
);
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.eventbus.Subscribe;
import net.runelite.client.game.ItemManager;
import net.runelite.client.game.SpriteManager;
import net.runelite.client.game.chatbox.ChatboxPanelManager;
import net.runelite.client.input.KeyListener;
import net.runelite.client.input.KeyManager;
@@ -125,6 +126,9 @@ public class BankTagsPlugin extends Plugin implements MouseWheelListener, KeyLis
@Inject
private KeyManager keyManager;
@Inject
private SpriteManager spriteManager;
private boolean shiftPressed = false;
@Provides
@@ -139,7 +143,7 @@ public class BankTagsPlugin extends Plugin implements MouseWheelListener, KeyLis
keyManager.registerKeyListener(this);
mouseManager.registerMouseWheelListener(this);
clientThread.invokeLater(tabInterface::init);
client.getSpriteOverrides().putAll(TabSprites.toMap(client));
spriteManager.addSpriteOverrides(TabSprites.values());
}
@Override
@@ -148,11 +152,7 @@ public class BankTagsPlugin extends Plugin implements MouseWheelListener, KeyLis
keyManager.unregisterKeyListener(this);
mouseManager.unregisterMouseWheelListener(this);
clientThread.invokeLater(tabInterface::destroy);
for (TabSprites value : TabSprites.values())
{
client.getSpriteOverrides().remove(value.getSpriteId());
}
spriteManager.removeSpriteOverrides(TabSprites.values());
shiftPressed = false;
}

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)
{
itemId = Math.abs(itemId);

View File

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

View File

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

View File

@@ -115,6 +115,7 @@ class TabManager
{
tagTab.setHidden(true);
tabs.remove(tagTab);
removeIcon(tag);
}
}
@@ -124,6 +125,16 @@ class TabManager
configManager.setConfiguration(CONFIG_GROUP, TAG_TABS_CONFIG, tags);
}
void removeIcon(final String tag)
{
configManager.unsetConfiguration(CONFIG_GROUP, ICON_SEARCH + Text.standardize(tag));
}
void setIcon(final String tag, final String icon)
{
configManager.setConfiguration(CONFIG_GROUP, ICON_SEARCH + Text.standardize(tag), icon);
}
int size()
{
return tabs.size();

View File

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

View File

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

View File

@@ -26,15 +26,15 @@ package net.runelite.client.plugins.cannon;
import java.awt.Color;
import java.awt.image.BufferedImage;
import net.runelite.client.ui.overlay.infobox.Counter;
import net.runelite.client.ui.overlay.infobox.InfoBox;
public class CannonCounter extends Counter
public class CannonCounter extends InfoBox
{
private final CannonPlugin plugin;
public CannonCounter(BufferedImage img, CannonPlugin plugin)
CannonCounter(BufferedImage img, CannonPlugin plugin)
{
super(img, plugin, String.valueOf(plugin.getCballsLeft()));
super(img, plugin);
this.plugin = plugin;
}

View File

@@ -75,7 +75,8 @@ public class CerberusPlugin extends Plugin
@Subscribe
public void onGameStateChanged(GameStateChanged event)
{
if (event.getGameState() == GameState.LOADING)
GameState gameState = event.getGameState();
if (gameState == GameState.LOGIN_SCREEN || gameState == GameState.HOPPING || gameState == GameState.CONNECTION_LOST)
{
ghosts.clear();
}

View File

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

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* Copyright (c) 2018, TheStonedTurtle <https://github.com/TheStonedTurtle>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -22,46 +22,34 @@
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.http.service.ws;
package net.runelite.client.plugins.chathistory;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import net.runelite.client.config.Config;
import net.runelite.client.config.ConfigGroup;
import net.runelite.client.config.ConfigItem;
public class SessionManager
@ConfigGroup("chathistory")
public interface ChatHistoryConfig extends Config
{
private static final ConcurrentMap<UUID, WSService> sessions = new ConcurrentHashMap<>();
public static void changeSessionUID(WSService service, UUID uuid)
@ConfigItem(
keyName = "retainChatHistory",
name = "Retain Chat History",
description = "Retains chat history when logging in/out or world hopping",
position = 0
)
default boolean retainChatHistory()
{
synchronized (service)
{
remove(service);
service.setUuid(uuid);
sessions.put(uuid, service);
}
return true;
}
static void remove(WSService service)
@ConfigItem(
keyName = "pmTargetCycling",
name = "PM Target Cycling",
description = "Pressing Tab while sending a PM will cycle the target username based on PM history",
position = 1
)
default boolean pmTargetCycling()
{
synchronized (service)
{
UUID current = service.getUuid();
if (current != null)
{
sessions.remove(current);
service.setUuid(null);
}
}
}
public static WSService findSession(UUID uuid)
{
return sessions.get(uuid);
}
public static int getCount()
{
return sessions.size();
return true;
}
}

View File

@@ -25,47 +25,74 @@
package net.runelite.client.plugins.chathistory;
import com.google.common.collect.EvictingQueue;
import com.google.common.collect.Sets;
import com.google.inject.Provides;
import java.awt.event.KeyEvent;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Iterator;
import java.util.Queue;
import java.util.Set;
import javax.inject.Inject;
import net.runelite.api.ChatMessageType;
import net.runelite.api.Client;
import net.runelite.api.ScriptID;
import net.runelite.api.VarClientInt;
import net.runelite.api.VarClientStr;
import net.runelite.api.events.ChatMessage;
import net.runelite.api.events.MenuOptionClicked;
import net.runelite.api.vars.InputType;
import net.runelite.client.callback.ClientThread;
import net.runelite.client.chat.ChatMessageManager;
import net.runelite.client.chat.QueuedMessage;
import net.runelite.client.config.ConfigManager;
import net.runelite.client.eventbus.Subscribe;
import net.runelite.client.input.KeyListener;
import net.runelite.client.input.KeyManager;
import net.runelite.client.plugins.Plugin;
import net.runelite.client.plugins.PluginDescriptor;
import net.runelite.client.util.Text;
@PluginDescriptor(
name = "Chat History",
description = "Retain your chat history when logging in/out or world hopping"
description = "Retain your chat history when logging in/out or world hopping",
tags = {"chat", "history", "retain", "cycle", "pm"}
)
public class ChatHistoryPlugin extends Plugin
public class ChatHistoryPlugin extends Plugin implements KeyListener
{
private static final String WELCOME_MESSAGE = "Welcome to Old School RuneScape.";
private static final String CLEAR_HISTORY = "Clear history";
private static final String CLEAR_PRIVATE = "<col=ffff00>Private:";
private static final Set<ChatMessageType> ALLOWED_HISTORY = Sets.newHashSet(
ChatMessageType.PUBLIC,
ChatMessageType.PUBLIC_MOD,
ChatMessageType.CLANCHAT,
ChatMessageType.PRIVATE_MESSAGE_RECEIVED,
ChatMessageType.PRIVATE_MESSAGE_SENT,
ChatMessageType.PRIVATE_MESSAGE_RECEIVED_MOD,
ChatMessageType.GAME
);
private static final int CYCLE_HOTKEY = KeyEvent.VK_TAB;
private Queue<QueuedMessage> messageQueue;
private Deque<String> friends;
@Inject
private Client client;
@Inject
private ClientThread clientThread;
@Inject
private ChatHistoryConfig config;
@Inject
private KeyManager keyManager;
@Inject
private ChatMessageManager chatMessageManager;
@Provides
ChatHistoryConfig getConfig(ConfigManager configManager)
{
return configManager.getConfig(ChatHistoryConfig.class);
}
@Override
protected void startUp()
{
messageQueue = EvictingQueue.create(100);
friends = new ArrayDeque<>(5);
keyManager.registerKeyListener(this);
}
@Override
@@ -73,6 +100,9 @@ public class ChatHistoryPlugin extends Plugin
{
messageQueue.clear();
messageQueue = null;
friends.clear();
friends = null;
keyManager.unregisterKeyListener(this);
}
@Subscribe
@@ -82,6 +112,11 @@ public class ChatHistoryPlugin extends Plugin
// of information that chat history was reset
if (chatMessage.getMessage().equals(WELCOME_MESSAGE))
{
if (!config.retainChatHistory())
{
return;
}
QueuedMessage queuedMessage;
while ((queuedMessage = messageQueue.poll()) != null)
@@ -92,21 +127,33 @@ public class ChatHistoryPlugin extends Plugin
return;
}
if (ALLOWED_HISTORY.contains(chatMessage.getType()))
switch (chatMessage.getType())
{
final QueuedMessage queuedMessage = QueuedMessage.builder()
.type(chatMessage.getType())
.name(chatMessage.getName())
.sender(chatMessage.getSender())
.value(nbsp(chatMessage.getMessage()))
.runeLiteFormattedMessage(nbsp(chatMessage.getMessageNode().getRuneLiteFormatMessage()))
.timestamp(chatMessage.getTimestamp())
.build();
case PRIVATE_MESSAGE_SENT:
case PRIVATE_MESSAGE_RECEIVED:
case PRIVATE_MESSAGE_RECEIVED_MOD:
final String name = Text.removeTags(chatMessage.getName());
// Remove to ensure uniqueness & its place in history
friends.remove(name);
friends.add(name);
// intentional fall-through
case PUBLIC:
case PUBLIC_MOD:
case CLANCHAT:
case GAME:
final QueuedMessage queuedMessage = QueuedMessage.builder()
.type(chatMessage.getType())
.name(chatMessage.getName())
.sender(chatMessage.getSender())
.value(nbsp(chatMessage.getMessage()))
.runeLiteFormattedMessage(nbsp(chatMessage.getMessageNode().getRuneLiteFormatMessage()))
.timestamp(chatMessage.getTimestamp())
.build();
if (!messageQueue.contains(queuedMessage))
{
messageQueue.offer(queuedMessage);
}
if (!messageQueue.contains(queuedMessage))
{
messageQueue.offer(queuedMessage);
}
}
}
@@ -143,4 +190,64 @@ public class ChatHistoryPlugin extends Plugin
return null;
}
@Override
public void keyPressed(KeyEvent e)
{
if (e.getKeyCode() != CYCLE_HOTKEY || !config.pmTargetCycling())
{
return;
}
if (client.getVar(VarClientInt.INPUT_TYPE) != InputType.PRIVATE_MESSAGE.getType())
{
return;
}
clientThread.invoke(() ->
{
final String target = findPreviousFriend();
if (target == null)
{
return;
}
final String currentMessage = client.getVar(VarClientStr.INPUT_TEXT);
client.runScript(ScriptID.OPEN_PRIVATE_MESSAGE_INTERFACE, target);
client.setVar(VarClientStr.INPUT_TEXT, currentMessage);
client.runScript(ScriptID.CHAT_TEXT_INPUT_REBUILD, "");
});
}
@Override
public void keyTyped(KeyEvent e)
{
}
@Override
public void keyReleased(KeyEvent e)
{
}
private String findPreviousFriend()
{
final String currentTarget = client.getVar(VarClientStr.PRIVATE_MESSAGE_TARGET);
if (currentTarget == null || friends.isEmpty())
{
return null;
}
for (Iterator<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 big egg where no one dare goes and the ground is burnt. Beware of double agents! Equip a dragon med helm, a TokTz-Ket-Xil, a brine sabre, rune platebody and an uncharged amulet of glory.", SOUTHEAST_CORNER_OF_LAVA_DRAGON_ISLE, new WorldPoint(3227, 3831, 0), PANIC, item(DRAGON_MED_HELM), item(TOKTZKETXIL), item(BRINE_SABRE), item(RUNE_PLATEBODY), item(AMULET_OF_GLORY)),
new EmoteClue("Panic at the area flowers meet snow. Equip Blue D'hide vambs, a dragon spear and a rune plateskirt.", HALFWAY_DOWN_TROLLWEISS_MOUNTAIN, new WorldPoint(2776, 3781, 0), PANIC, item(BLUE_DHIDE_VAMB), item(DRAGON_SPEAR), item(RUNE_PLATESKIRT), item(SLED_4084)),
new EmoteClue("Do a push up at the bank of the Warrior's guild. Beware of double agents! Equip a dragon battleaxe, a dragon defender and a slayer helm of any kind.", WARRIORS_GUILD_BANK_29047, new WorldPoint(2843, 3543, 0), PUSH_UP, item(DRAGON_BATTLEAXE), item(DRAGON_DEFENDER), any("Any slayer helmet", item(SLAYER_HELMET), item(BLACK_SLAYER_HELMET), item(GREEN_SLAYER_HELMET), item(PURPLE_SLAYER_HELMET), item(RED_SLAYER_HELMET), item(TURQUOISE_SLAYER_HELMET), item(SLAYER_HELMET_I), item(BLACK_SLAYER_HELMET_I), item(GREEN_SLAYER_HELMET_I), item(PURPLE_SLAYER_HELMET_I), item(RED_SLAYER_HELMET_I), item(TURQUOISE_SLAYER_HELMET_I))),
new EmoteClue("Blow a raspberry at the bank of the Warrior's guild. Beware of double agents! Equip a dragon battleaxe, a dragon defender and a slayer helm of any kind.", WARRIORS_GUILD_BANK_29047, new WorldPoint(2843, 3543, 0), RASPBERRY, item(DRAGON_BATTLEAXE), item(DRAGON_DEFENDER), any("Any slayer helmet", item(SLAYER_HELMET), item(BLACK_SLAYER_HELMET), item(GREEN_SLAYER_HELMET), item(PURPLE_SLAYER_HELMET), item(RED_SLAYER_HELMET), item(TURQUOISE_SLAYER_HELMET), item(SLAYER_HELMET_I), item(BLACK_SLAYER_HELMET_I), item(GREEN_SLAYER_HELMET_I), item(PURPLE_SLAYER_HELMET_I), item(RED_SLAYER_HELMET_I), item(TURQUOISE_SLAYER_HELMET_I))),
new EmoteClue("Do a push up at the bank of the Warrior's guild. Beware of double agents! Equip a dragon battleaxe, a dragon defender and a slayer helm of any kind.", WARRIORS_GUILD_BANK_29047, new WorldPoint(2843, 3543, 0), PUSH_UP, item(DRAGON_BATTLEAXE), item(DRAGON_DEFENDER), any("Any slayer helmet", item(SLAYER_HELMET), item(BLACK_SLAYER_HELMET), item(GREEN_SLAYER_HELMET), item(PURPLE_SLAYER_HELMET), item(RED_SLAYER_HELMET), item(TURQUOISE_SLAYER_HELMET), item(SLAYER_HELMET_I), item(BLACK_SLAYER_HELMET_I), item(GREEN_SLAYER_HELMET_I), item(PURPLE_SLAYER_HELMET_I), item(RED_SLAYER_HELMET_I), item(TURQUOISE_SLAYER_HELMET_I), item(HYDRA_SLAYER_HELMET), item(HYDRA_SLAYER_HELMET_I))),
new EmoteClue("Blow a raspberry at the bank of the Warrior's guild. Beware of double agents! Equip a dragon battleaxe, a dragon defender and a slayer helm of any kind.", WARRIORS_GUILD_BANK_29047, new WorldPoint(2843, 3543, 0), RASPBERRY, item(DRAGON_BATTLEAXE), item(DRAGON_DEFENDER), any("Any slayer helmet", item(SLAYER_HELMET), item(BLACK_SLAYER_HELMET), item(GREEN_SLAYER_HELMET), item(PURPLE_SLAYER_HELMET), item(RED_SLAYER_HELMET), item(TURQUOISE_SLAYER_HELMET), item(SLAYER_HELMET_I), item(BLACK_SLAYER_HELMET_I), item(GREEN_SLAYER_HELMET_I), item(PURPLE_SLAYER_HELMET_I), item(RED_SLAYER_HELMET_I), item(TURQUOISE_SLAYER_HELMET_I), item(HYDRA_SLAYER_HELMET), item(HYDRA_SLAYER_HELMET_I))),
new EmoteClue("Blow a raspberry at the monkey cage in Ardougne Zoo. Equip a studded leather body, bronze platelegs and a normal staff with no orb.", NEAR_THE_PARROTS_IN_ARDOUGNE_ZOO, new WorldPoint(2607, 3282, 0), RASPBERRY, item(STUDDED_BODY), item(BRONZE_PLATELEGS), item(STAFF)),
new EmoteClue("Blow raspberries outside the entrance to Keep Le Faye. Equip a coif, an iron platebody and leather gloves.", OUTSIDE_KEEP_LE_FAYE, new WorldPoint(2757, 3401, 0), RASPBERRY, item(COIF), item(IRON_PLATEBODY), item(LEATHER_GLOVES)),
new EmoteClue("Blow a raspberry in the Fishing Guild bank. Beware of double agents! Equip an elemental shield, blue dragonhide chaps and a rune warhammer.", FISHING_GUILD_BANK, new WorldPoint(2588, 3419, 0), RASPBERRY, item(ELEMENTAL_SHIELD), item(BLUE_DHIDE_CHAPS), item(RUNE_WARHAMMER)),

View File

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

View File

@@ -347,6 +347,12 @@ class DevToolsOverlay extends Overlay
{
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<>("DragDeadZone", Widget::getDragDeadZone, Widget::setDragDeadZone, Integer.class));
out.add(new WidgetField<>("DragDeadTime", Widget::getDragDeadTime, Widget::setDragDeadTime, Integer.class));
out.add(new WidgetField<>("NoClickThrough", Widget::getNoClickThrough, Widget::setNoClickThrough, Boolean.class));
out.add(new WidgetField<>("NoScrollThrough", Widget::getNoScrollThrough, Widget::setNoScrollThrough, Boolean.class));
out.add(new WidgetField<>("TargetVerb", Widget::getTargetVerb, Widget::setTargetVerb, String.class));
return out;
}

View File

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

View File

@@ -34,6 +34,7 @@ import java.util.Optional;
import java.util.UUID;
import javax.inject.Inject;
import lombok.Data;
import net.runelite.client.RuneLiteProperties;
import net.runelite.client.discord.DiscordPresence;
import net.runelite.client.discord.DiscordService;
import net.runelite.client.ws.PartyService;
@@ -57,14 +58,16 @@ class DiscordState
private final DiscordService discordService;
private final DiscordConfig config;
private PartyService party;
private final RuneLiteProperties properties;
private DiscordPresence lastPresence;
@Inject
private DiscordState(final DiscordService discordService, final DiscordConfig config, final PartyService party)
private DiscordState(final DiscordService discordService, final DiscordConfig config, final PartyService party, final RuneLiteProperties properties)
{
this.discordService = discordService;
this.config = config;
this.party = party;
this.properties = properties;
}
/**
@@ -90,6 +93,7 @@ class DiscordState
final DiscordPresence.DiscordPresenceBuilder presenceBuilder = DiscordPresence.builder()
.state(lastPresence.getState())
.details(lastPresence.getDetails())
.largeImageText(lastPresence.getLargeImageText())
.startTimestamp(lastPresence.getStartTimestamp())
.smallImageKey(lastPresence.getSmallImageKey())
.partyMax(lastPresence.getPartyMax())
@@ -168,11 +172,15 @@ class DiscordState
}
}
// Replace snapshot with + to make tooltip shorter (so it will span only 1 line)
final String versionShortHand = properties.getVersion().replace("-SNAPSHOT", "+");
final DiscordPresence.DiscordPresenceBuilder presenceBuilder = DiscordPresence.builder()
.state(MoreObjects.firstNonNull(state, ""))
.details(MoreObjects.firstNonNull(details, ""))
.largeImageText(properties.getTitle() + " v" + versionShortHand)
.startTimestamp(event.getStart())
.smallImageKey(MoreObjects.firstNonNull(imageKey, "default"))
.smallImageKey(imageKey)
.partyMax(PARTY_MAX)
.partySize(party.getMembers().size());

View File

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

View File

@@ -147,7 +147,8 @@ public class FishingPlugin extends Plugin
@Subscribe
public void onGameStateChanged(GameStateChanged gameStateChanged)
{
if (gameStateChanged.getGameState() == GameState.LOADING)
GameState gameState = gameStateChanged.getGameState();
if (gameState == GameState.CONNECTION_LOST || gameState == GameState.LOGIN_SCREEN || gameState == GameState.HOPPING)
{
fishingSpots.clear();
minnowSpots.clear();

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.glBindTexture(gl.GL_TEXTURE_2D, interfaceTexture);
if (client.isStretchedEnabled())
{
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) 2018, SomeoneWithAnInternetConnection
* All rights reserved.
@@ -46,6 +46,7 @@ import net.runelite.api.ChatMessageType;
import net.runelite.api.Client;
import net.runelite.api.GameState;
import net.runelite.api.GrandExchangeOffer;
import net.runelite.api.GrandExchangeOfferState;
import net.runelite.api.ItemComposition;
import net.runelite.api.MenuAction;
import net.runelite.api.MenuEntry;
@@ -56,11 +57,15 @@ import net.runelite.api.events.GameStateChanged;
import net.runelite.api.events.GameTick;
import net.runelite.api.events.GrandExchangeOfferChanged;
import net.runelite.api.events.MenuEntryAdded;
import net.runelite.api.events.SessionClose;
import net.runelite.api.events.SessionOpen;
import net.runelite.api.events.WidgetLoaded;
import net.runelite.api.widgets.Widget;
import net.runelite.api.widgets.WidgetID;
import net.runelite.api.widgets.WidgetInfo;
import net.runelite.client.Notifier;
import net.runelite.client.account.AccountSession;
import net.runelite.client.account.SessionManager;
import net.runelite.client.config.ConfigManager;
import net.runelite.client.eventbus.Subscribe;
import net.runelite.client.game.ItemManager;
@@ -73,8 +78,10 @@ import net.runelite.client.ui.NavigationButton;
import net.runelite.client.util.ImageUtil;
import net.runelite.client.util.StackFormatter;
import net.runelite.client.util.Text;
import net.runelite.http.api.osbuddy.GrandExchangeClient;
import net.runelite.http.api.osbuddy.GrandExchangeResult;
import net.runelite.http.api.ge.GrandExchangeClient;
import net.runelite.http.api.ge.GrandExchangeTrade;
import net.runelite.http.api.osbuddy.OSBGrandExchangeClient;
import net.runelite.http.api.osbuddy.OSBGrandExchangeResult;
@PluginDescriptor(
name = "Grand Exchange",
@@ -86,7 +93,7 @@ public class GrandExchangePlugin extends Plugin
{
private static final int OFFER_CONTAINER_ITEM = 21;
private static final int OFFER_DEFAULT_ITEM_ID = 6512;
private static final GrandExchangeClient CLIENT = new GrandExchangeClient();
private static final OSBGrandExchangeClient CLIENT = new OSBGrandExchangeClient();
private static final String OSB_GE_TEXT = "<br>OSBuddy Actively traded price: ";
private static final String BUY_LIMIT_GE_TEXT = "<br>Buy limit: ";
@@ -134,10 +141,38 @@ public class GrandExchangePlugin extends Plugin
@Inject
private ScheduledExecutorService executorService;
@Inject
private SessionManager sessionManager;
@Inject
private ConfigManager configManager;
private Widget grandExchangeText;
private Widget grandExchangeItem;
private Map<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
GrandExchangeConfig provideConfig(ConfigManager configManager)
{
@@ -167,6 +202,12 @@ public class GrandExchangePlugin extends Plugin
mouseManager.registerMouseListener(inputListener);
keyManager.registerKeyListener(inputListener);
}
AccountSession accountSession = sessionManager.getAccountSession();
if (accountSession != null)
{
grandExchangeClient = new GrandExchangeClient(accountSession.getUuid());
}
}
@Override
@@ -178,6 +219,27 @@ public class GrandExchangePlugin extends Plugin
grandExchangeText = null;
grandExchangeItem = null;
itemGELimits = null;
grandExchangeClient = null;
}
@Subscribe
public void onSessionOpen(SessionOpen sessionOpen)
{
AccountSession accountSession = sessionManager.getAccountSession();
if (accountSession.getUuid() != null)
{
grandExchangeClient = new GrandExchangeClient(accountSession.getUuid());
}
else
{
grandExchangeClient = null;
}
}
@Subscribe
public void onSessionClose(SessionClose sessionClose)
{
grandExchangeClient = null;
}
@Subscribe
@@ -204,11 +266,79 @@ public class GrandExchangePlugin extends Plugin
@Subscribe
public void onGrandExchangeOfferChanged(GrandExchangeOfferChanged offerEvent)
{
GrandExchangeOffer offer = offerEvent.getOffer();
final int slot = offerEvent.getSlot();
final GrandExchangeOffer offer = offerEvent.getOffer();
ItemComposition offerItem = itemManager.getItemComposition(offer.getItemId());
boolean shouldStack = offerItem.isStackable() || offer.getTotalQuantity() > 1;
BufferedImage itemImage = itemManager.getImage(offer.getItemId(), offer.getTotalQuantity(), shouldStack);
SwingUtilities.invokeLater(() -> panel.getOffersPanel().updateOffer(offerItem, itemImage, offerEvent.getOffer(), offerEvent.getSlot()));
SwingUtilities.invokeLater(() -> panel.getOffersPanel().updateOffer(offerItem, itemImage, offer, slot));
submitTrades(slot, offer);
updateConfig(slot, offer);
}
private void submitTrades(int slot, GrandExchangeOffer offer)
{
if (grandExchangeClient == null)
{
return;
}
// Only interested in offers which are fully bought/sold
if (offer.getState() != GrandExchangeOfferState.BOUGHT && offer.getState() != GrandExchangeOfferState.SOLD)
{
return;
}
SavedOffer savedOffer = getOffer(slot);
if (!shouldUpdate(savedOffer, offer))
{
return;
}
// getPrice() is the price of the offer, not necessarily what the item bought at
int priceEach = offer.getSpent() / offer.getTotalQuantity();
GrandExchangeTrade grandExchangeTrade = new GrandExchangeTrade();
grandExchangeTrade.setBuy(offer.getState() == GrandExchangeOfferState.BOUGHT);
grandExchangeTrade.setItemId(offer.getItemId());
grandExchangeTrade.setQuantity(offer.getTotalQuantity());
grandExchangeTrade.setPrice(priceEach);
log.debug("Submitting trade: {}", grandExchangeTrade);
grandExchangeClient.submit(grandExchangeTrade);
}
private void updateConfig(int slot, GrandExchangeOffer offer)
{
if (offer.getState() == GrandExchangeOfferState.EMPTY)
{
deleteOffer(slot);
}
else
{
SavedOffer savedOffer = new SavedOffer();
savedOffer.setItemId(offer.getItemId());
savedOffer.setQuantitySold(offer.getQuantitySold());
savedOffer.setTotalQuantity(offer.getTotalQuantity());
savedOffer.setPrice(offer.getPrice());
savedOffer.setSpent(offer.getSpent());
savedOffer.setState(offer.getState());
setOffer(slot, savedOffer);
}
}
private boolean shouldUpdate(SavedOffer savedOffer, GrandExchangeOffer grandExchangeOffer)
{
if (savedOffer == null)
{
return false;
}
// Only update offer if state has changed
return savedOffer.getState() != grandExchangeOffer.getState();
}
@Subscribe
@@ -346,7 +476,7 @@ public class GrandExchangePlugin extends Plugin
try
{
final GrandExchangeResult result = CLIENT.lookupItem(itemId);
final OSBGrandExchangeResult result = CLIENT.lookupItem(itemId);
final String text = geText.getText() + OSB_GE_TEXT + StackFormatter.formatNumber(result.getOverall_average());
geText.setText(text);
}

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