Merges Injector

Welcome to the new world boys.
This commit is contained in:
zeruth
2019-06-06 20:47:41 -04:00
parent 79ed69ccdf
commit 882be3cb71
3613 changed files with 193663 additions and 158070 deletions

111
RuneLitePlus/Test.java Normal file
View File

@@ -0,0 +1,111 @@
/*
* Copyright (c) 2018, https://runelitepl.us
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.runeswag.client.plugins;
import api.Client;
import api.events.ChatMessage;
import api.events.ClientTick;
import api.events.GameTick;
import api.events.MenuOpened;
import api.events.VarbitChanged;
import com.google.common.eventbus.Subscribe;
import com.google.inject.Binder;
import javax.inject.Inject;
import javax.inject.Singleton;
import com.google.inject.Provides;
import com.runeswag.client.config.ConfigManager;
import com.runeswag.client.misc.Plugin;
import com.runeswag.client.misc.PluginDescriptor;
import com.runeswag.client.ui.OverlayManager;
import lombok.extern.slf4j.Slf4j;
/**
* Authors gazivodag longstreet
*/
@PluginDescriptor(
name = "Test",
description = "Testing plugin for building functionality",
tags = {}
)
@Singleton
@Slf4j
public class Test extends Plugin
{
@Provides
TestConfig getConfig(ConfigManager configManager)
{
return configManager.getConfig(TestConfig.class);
}
@Inject
private TestOverlay testOverlay;
@Inject
private TestConfig config;
@Inject
private Client client;
@Inject
private OverlayManager overlayManager;
@Override
public void configure(Binder binder)
{
}
@Override
protected void startUp() throws Exception
{
overlayManager.add(testOverlay);
System.out.println("Test Plugin started");
}
@Override
protected void shutDown() throws Exception
{
}
@Subscribe
public void onMenuOpened(MenuOpened event)
{
System.out.println("[Test Plugin] Menu Opened");
}
@Subscribe
public void onChatMessage(ChatMessage chatMessage)
{
System.out.println("[Test Plugin] Chat Message");
}
@Subscribe
public void onVarbitChanged(VarbitChanged event)
{
System.out.println("[Test Plugin] Varbit Changed");
}
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright (c) 2018, Cas <https://github.com/casvandongen>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.runeswag.client.plugins;
import com.runeswag.client.config.Config;
import com.runeswag.client.config.ConfigGroup;
import com.runeswag.client.config.ConfigItem;
import java.awt.*;
@ConfigGroup("test")
public interface TestConfig extends Config
{
@ConfigItem(
keyName = "Testing 1",
name = "Test option",
description = "Enable/disable nothing",
position = 1
)
default boolean tested()
{
return true;
}
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright (c) 2018, Adam <Adam@sigterm.info>
* Copyright (c) 2018, Cas <https://github.com/casvandongen>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.runeswag.client.plugins;
import api.Client;
import com.runeswag.client.ui.Overlay;
import com.runeswag.client.ui.OverlayLayer;
import com.runeswag.client.ui.OverlayPosition;
import javax.inject.Inject;
import java.awt.*;
class TestOverlay extends Overlay
{
private static final Color SHORTCUT_HIGH_LEVEL_COLOR = Color.ORANGE;
private final Client client;
private final Test plugin;
private final TestConfig config;
@Inject
private TestOverlay(Client client, Test plugin, TestConfig config)
{
super(plugin);
setPosition(OverlayPosition.DYNAMIC);
setLayer(OverlayLayer.ABOVE_SCENE);
this.client = client;
this.plugin = plugin;
this.config = config;
}
@Override
public Dimension render(Graphics2D graphics)
{
System.out.println("Rendered Test");
graphics.setColor(Color.RED);
graphics.drawString("Hello World", 20, 20);
return null;
}
}

451
RuneLitePlus/pom.xml Normal file
View File

@@ -0,0 +1,451 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (c) 2016-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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.runeswag</groupId>
<artifactId>runeswag-parent</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>client</artifactId>
<name>RuneLite Client</name>
<properties>
<guice.version>4.1.0</guice.version>
<jogl.version>2.3.2</jogl.version>
<hamcrest.version>1.3</hamcrest.version>
<jopt.version>5.0.1</jopt.version>
<commons.text.version>1.2</commons.text.version>
<sigpipe.jbsdiff.version>1.0</sigpipe.jbsdiff.version>
<java.dev.jna.version>4.5.1</java.dev.jna.version>
<runelite.pushingpixels.substance.version>8.0.02</runelite.pushingpixels.substance.version>
<runelite.pushingpixels.trident.version>1.5.00</runelite.pushingpixels.trident.version>
<runelite.discord.version>1.1</runelite.discord.version>
<runelite.orange.extensions.version>1.0</runelite.orange.extensions.version>
<maven.resources.plugin.version>3.0.2</maven.resources.plugin.version>
<maven.jarsigner.plugin.version>1.4</maven.jarsigner.plugin.version>
<maven.shade.plugin.version>3.2.1</maven.shade.plugin.version>
<jarsigner.skip>true</jarsigner.skip>
</properties>
<repositories>
<repository>
<id>RuneLit</id>
<name>RuneLit</name>
<url>https://raw.githubusercontent.com/runelite-extended/maven-repo/master</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.8.0-beta4</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.3.0-alpha4</version>
</dependency>
<dependency>
<groupId>net.sf.jopt-simple</groupId>
<artifactId>jopt-simple</artifactId>
<version>5.0.1</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>27.1-jre</version>
</dependency>
<dependency>
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
<version>4.2.2</version>
<classifier>no_aop</classifier>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.5</version>
</dependency>
<dependency>
<groupId>net.runelite.pushingpixels</groupId>
<artifactId>substance</artifactId>
<version>8.0.02</version>
</dependency>
<dependency>
<groupId>net.runelite.pushingpixels</groupId>
<artifactId>trident</artifactId>
<version>1.5.00</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.6</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
<version>1.6</version>
</dependency>
<dependency>
<groupId>org.jogamp.jogl</groupId>
<artifactId>jogl-all</artifactId>
<version>2.3.2</version>
</dependency>
<dependency>
<groupId>org.jogamp.jogl</groupId>
<artifactId>jogl-all</artifactId>
<version>2.3.2</version>
<classifier>natives-windows-amd64</classifier>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.jogamp.jogl</groupId>
<artifactId>jogl-all</artifactId>
<version>2.3.2</version>
<classifier>natives-windows-i586</classifier>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.jogamp.jogl</groupId>
<artifactId>jogl-all</artifactId>
<version>2.3.2</version>
<classifier>natives-linux-amd64</classifier>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.jogamp.jogl</groupId>
<artifactId>jogl-all</artifactId>
<version>2.3.2</version>
<classifier>natives-linux-i586</classifier>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.jogamp.gluegen</groupId>
<artifactId>gluegen-rt</artifactId>
<version>2.3.2</version>
</dependency>
<dependency>
<groupId>org.jogamp.gluegen</groupId>
<artifactId>gluegen-rt</artifactId>
<version>2.3.2</version>
<classifier>natives-windows-amd64</classifier>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.jogamp.gluegen</groupId>
<artifactId>gluegen-rt</artifactId>
<version>2.3.2</version>
<classifier>natives-windows-i586</classifier>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.jogamp.gluegen</groupId>
<artifactId>gluegen-rt</artifactId>
<version>2.3.2</version>
<classifier>natives-linux-amd64</classifier>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.jogamp.gluegen</groupId>
<artifactId>gluegen-rt</artifactId>
<version>2.3.2</version>
<classifier>natives-linux-i586</classifier>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.sigpipe</groupId>
<artifactId>jbsdiff</artifactId>
<version>1.0</version>
<exclusions>
<exclusion>
<groupId>org.tukaani</groupId>
<artifactId>xz</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- net.runelite:discord also has this -->
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
<version>4.5.1</version>
</dependency>
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna-platform</artifactId>
<version>4.5.1</version>
</dependency>
<dependency>
<groupId>com.runeswag</groupId>
<artifactId>api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>net.runelit</groupId>
<artifactId>client-patch</artifactId>
<version>1.5.26.2</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>net.runelite</groupId>
<artifactId>http-api</artifactId>
<version>1.5.26-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>net.runelite</groupId>
<artifactId>discord</artifactId>
<version>1.1</version>
</dependency>
<dependency>
<groupId>net.runelite</groupId>
<artifactId>orange-extensions</artifactId>
<version>1.0</version>
<scope>provided</scope>
</dependency>
<!-- <dependency>
<groupId>net.runelite</groupId>
<artifactId>extended-mixins</artifactId>
<version>${project.version}</version>
</dependency>-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13-beta-3</version>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>2.0.2-beta</version>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-library</artifactId>
<version>2.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.inject.extensions</groupId>
<artifactId>guice-testlib</artifactId>
<version>4.2.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.inject.extensions</groupId>
<artifactId>guice-grapher</artifactId>
<version>4.2.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.24.1-GA</version>
</dependency>
<dependency>
<groupId>org.xeustechnologies</groupId>
<artifactId>jcl-core</artifactId>
<version>2.8</version>
</dependency>
<dependency>
<groupId>org.jetbrains</groupId>
<artifactId>annotations</artifactId>
<version>17.0.0</version>
</dependency>
<dependency>
<groupId>com.github.joonasvali.naturalmouse</groupId>
<artifactId>naturalmouse</artifactId>
<version>[1.0.0,)</version>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm-all</artifactId>
<version>6.0_BETA</version>
</dependency>
<dependency>
<groupId>io.sentry</groupId>
<artifactId>sentry-logback</artifactId>
<version>1.7.16</version>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<excludes>
<exclude>logback.xml</exclude>
</excludes>
<filtering>true</filtering>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>logback.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>${maven.resources.plugin.version}</version>
<configuration>
<nonFilteredFileExtensions>
<nonFilteredFileExtension>ttf</nonFilteredFileExtension>
<nonFilteredFileExtension>png</nonFilteredFileExtension>
<nonFilteredFileExtension>gif</nonFilteredFileExtension>
</nonFilteredFileExtensions>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>${maven.shade.plugin.version}</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<shadedArtifactAttached>true</shadedArtifactAttached>
<shadedClassifierName>shaded</shadedClassifierName>
<filters>
<!-- include runtime apis -->
<filter>
<!-- net.runelite:client-patch and net.runelite:api -->
<artifact>net.runelite:api</artifact>
<includes>
<include>**</include>
</includes>
</filter>
<filter>
<!-- net.runelit:client-patch -->
<artifact>net.runelit:client-patch</artifact>
<includes>
<include>**</include>
</includes>
</filter>
<filter>
<artifact>net.runelite.pushingpixels:*</artifact>
<includes>
<include>**</include>
</includes>
</filter>
<filter>
<artifact>com.google.guava:*</artifact>
<includes>
<include>**</include>
</includes>
</filter>
<filter>
<artifact>ch.qos.logback:*</artifact>
<includes>
<include>**</include>
</includes>
</filter>
<filter>
<artifact>org.jogamp.jogl:*</artifact>
<includes>
<include>**</include>
</includes>
</filter>
<filter>
<artifact>org.jogamp.gluegen:*</artifact>
<includes>
<include>**</include>
</includes>
</filter>
</filters>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>net.runelite.client.RuneLite</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
<!-- Strip jar before signing -->
<plugin>
<groupId>io.github.zlika</groupId>
<artifactId>reproducible-build-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jarsigner-plugin</artifactId>
<version>${maven.jarsigner.plugin.version}</version>
<executions>
<execution>
<id>sign</id>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
<plugin>
<groupId>net.runelite</groupId>
<artifactId>script-assembler-plugin</artifactId>
<version>1.5.23-SNAPSHOT</version>
<executions>
<execution>
<id>assemble</id>
<goals>
<goal>assemble</goal>
</goals>
<configuration>
<scriptDirectory>src/main/scripts</scriptDirectory>
<outputDirectory>${project.build.outputDirectory}/runelite</outputDirectory>
</configuration>
</execution>
<execution>
<id>build-index</id>
<goals>
<goal>build-index</goal>
</goals>
<configuration>
<archiveOverlayDirectory>${project.build.outputDirectory}/runelite</archiveOverlayDirectory>
<indexFile>${project.build.outputDirectory}/runelite/index</indexFile>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,113 @@
/*
* Copyright (c) 2018, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.client;
import java.io.IOException;
import java.util.UUID;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import javax.inject.Singleton;
import lombok.extern.slf4j.Slf4j;
@Singleton
@Slf4j
public class ClientSessionManager
{
private final SessionClient sessionClient = new SessionClient();
private final ScheduledExecutorService executorService;
private ScheduledFuture<?> scheduledFuture;
private UUID sessionId;
@Inject
ClientSessionManager(ScheduledExecutorService executorService)
{
this.executorService = executorService;
}
public void start()
{
try
{
sessionId = sessionClient.open();
log.debug("Opened session {}", sessionId);
}
catch (IOException ex)
{
log.warn("error opening session", ex);
}
scheduledFuture = executorService.scheduleWithFixedDelay(this::ping, 1, 10, TimeUnit.MINUTES);
}
public void shutdown()
{
if (sessionId != null)
{
try
{
sessionClient.delete(sessionId);
}
catch (IOException ex)
{
log.warn(null, ex);
}
sessionId = null;
}
scheduledFuture.cancel(true);
}
private void ping()
{
try
{
if (sessionId == null)
{
sessionId = sessionClient.open();
log.debug("Opened session {}", sessionId);
return;
}
}
catch (IOException ex)
{
log.warn(null, ex);
}
try
{
sessionClient.ping(sessionId);
}
catch (IOException ex)
{
log.warn("Resetting session", ex);
sessionId = null;
}
}
}

View File

@@ -0,0 +1,344 @@
/*
* Copyright (c) 2016-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.client;
import api.ChatMessageType;
import api.Client;
import api.GameState;
import com.google.common.base.Strings;
import com.google.common.escape.Escaper;
import com.google.common.escape.Escapers;
import com.google.inject.Inject;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.TrayIcon;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import javax.inject.Singleton;
import lombok.extern.slf4j.Slf4j;
import net.runelite.client.chat.ChatColorType;
import net.runelite.client.chat.ChatMessageBuilder;
import net.runelite.client.chat.ChatMessageManager;
import net.runelite.client.chat.QueuedMessage;
import net.runelite.client.config.RuneLiteConfig;
import net.runelite.client.ui.ClientUI;
import net.runelite.client.util.OSType;
@Singleton
@Slf4j
public class Notifier
{
// Default timeout of notification in milliseconds
private static final int DEFAULT_TIMEOUT = 10000;
private static final String DOUBLE_QUOTE = "\"";
private static final Escaper SHELL_ESCAPE = Escapers.builder()
.addEscape('"', "'")
.build();
// Notifier properties
private static final Color FLASH_COLOR = new Color(255, 0, 0, 70);
private static final int FLASH_DURATION = 2000;
private final Client client;
private final String appName;
private final RuneLiteConfig runeLiteConfig;
private final ClientUI clientUI;
private final ScheduledExecutorService executorService;
private final ChatMessageManager chatMessageManager;
private final Path notifyIconPath;
private final boolean terminalNotifierAvailable;
private Instant flashStart;
@Inject
private Notifier(
final ClientUI clientUI,
final Client client,
final RuneLiteConfig runeliteConfig,
final RuneLiteProperties runeLiteProperties,
final ScheduledExecutorService executorService,
final ChatMessageManager chatMessageManager)
{
this.client = client;
this.appName = runeLiteProperties.getTitle();
this.clientUI = clientUI;
this.runeLiteConfig = runeliteConfig;
this.executorService = executorService;
this.chatMessageManager = chatMessageManager;
this.notifyIconPath = RuneLite.RUNELITE_DIR.toPath().resolve("icon.png");
// First check if we are running in launcher
this.terminalNotifierAvailable =
!Strings.isNullOrEmpty(RuneLiteProperties.getLauncherVersion())
&& isTerminalNotifierAvailable();
storeIcon();
}
public void notify(String message)
{
notify(message, TrayIcon.MessageType.NONE);
}
public void notify(String message, TrayIcon.MessageType type)
{
if (!runeLiteConfig.sendNotificationsWhenFocused() && clientUI.isFocused())
{
return;
}
if (runeLiteConfig.requestFocusOnNotification())
{
clientUI.requestFocus();
}
if (runeLiteConfig.enableTrayNotifications())
{
sendNotification(appName, message, type);
}
if (runeLiteConfig.enableNotificationSound())
{
Toolkit.getDefaultToolkit().beep();
}
if (runeLiteConfig.enableGameMessageNotification() && client.getGameState() == GameState.LOGGED_IN)
{
final String formattedMessage = new ChatMessageBuilder()
.append(ChatColorType.HIGHLIGHT)
.append(message)
.build();
chatMessageManager.queue(QueuedMessage.builder()
.type(ChatMessageType.CONSOLE)
.name(appName)
.runeLiteFormattedMessage(formattedMessage)
.build());
}
if (runeLiteConfig.enableFlashNotification())
{
flashStart = Instant.now();
}
log.debug(message);
}
public void processFlash(final Graphics2D graphics)
{
if (flashStart == null || client.getGameCycle() % 40 >= 20)
{
return;
}
else if (client.getGameState() != GameState.LOGGED_IN)
{
flashStart = null;
return;
}
final Color color = graphics.getColor();
graphics.setColor(FLASH_COLOR);
graphics.fill(new Rectangle(client.getCanvas().getSize()));
graphics.setColor(color);
if (Instant.now().minusMillis(FLASH_DURATION).isAfter(flashStart))
{
flashStart = null;
}
}
private void sendNotification(
final String title,
final String message,
final TrayIcon.MessageType type)
{
final String escapedTitle = SHELL_ESCAPE.escape(title);
final String escapedMessage = SHELL_ESCAPE.escape(message);
switch (OSType.getOSType())
{
case Linux:
sendLinuxNotification(escapedTitle, escapedMessage, type);
break;
case MacOS:
sendMacNotification(escapedTitle, escapedMessage);
break;
default:
sendTrayNotification(title, message, type);
}
}
private void sendTrayNotification(
final String title,
final String message,
final TrayIcon.MessageType type)
{
if (clientUI.getTrayIcon() != null)
{
clientUI.getTrayIcon().displayMessage(title, message, type);
}
}
private void sendLinuxNotification(
final String title,
final String message,
final TrayIcon.MessageType type)
{
final List<String> commands = new ArrayList<>();
commands.add("notify-send");
commands.add(title);
commands.add(message);
commands.add("-i");
commands.add(SHELL_ESCAPE.escape(notifyIconPath.toAbsolutePath().toString()));
commands.add("-u");
commands.add(toUrgency(type));
commands.add("-t");
commands.add(String.valueOf(DEFAULT_TIMEOUT));
executorService.submit(() ->
{
try
{
Process notificationProcess = sendCommand(commands);
boolean exited = notificationProcess.waitFor(500, TimeUnit.MILLISECONDS);
if (exited && notificationProcess.exitValue() == 0)
{
return;
}
}
catch (IOException | InterruptedException ex)
{
log.debug("error sending notification", ex);
}
// fall back to tray notification
sendTrayNotification(title, message, type);
});
}
private void sendMacNotification(final String title, final String message)
{
final List<String> commands = new ArrayList<>();
if (terminalNotifierAvailable)
{
commands.add("terminal-notifier");
commands.add("-group");
commands.add("net.runelite.launcher");
commands.add("-sender");
commands.add("net.runelite.launcher");
commands.add("-message");
commands.add(DOUBLE_QUOTE + message + DOUBLE_QUOTE);
commands.add("-title");
commands.add(DOUBLE_QUOTE + title + DOUBLE_QUOTE);
}
else
{
commands.add("osascript");
commands.add("-e");
final String script = "display notification " + DOUBLE_QUOTE +
message +
DOUBLE_QUOTE +
" with title " +
DOUBLE_QUOTE +
title +
DOUBLE_QUOTE;
commands.add(script);
}
try
{
sendCommand(commands);
}
catch (IOException ex)
{
log.warn("error sending notification", ex);
}
}
private static Process sendCommand(final List<String> commands) throws IOException
{
return new ProcessBuilder(commands.toArray(new String[commands.size()]))
.redirectErrorStream(true)
.start();
}
private void storeIcon()
{
if (OSType.getOSType() == OSType.Linux && !Files.exists(notifyIconPath))
{
try (InputStream stream = Notifier.class.getResourceAsStream("/runelite.png"))
{
Files.copy(stream, notifyIconPath);
}
catch (IOException ex)
{
log.warn(null, ex);
}
}
}
private boolean isTerminalNotifierAvailable()
{
if (OSType.getOSType() == OSType.MacOS)
{
try
{
final Process exec = Runtime.getRuntime().exec(new String[]{"terminal-notifier", "-help"});
exec.waitFor();
return exec.exitValue() == 0;
}
catch (IOException | InterruptedException e)
{
return false;
}
}
return false;
}
private static String toUrgency(TrayIcon.MessageType type)
{
switch (type)
{
case WARNING:
case ERROR:
return "critical";
default:
return "normal";
}
}
}

View File

@@ -0,0 +1,412 @@
/*
* Copyright (c) 2016-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.client;
import api.Client;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import com.google.common.annotations.VisibleForTesting;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
import java.io.File;
import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;
import java.net.Authenticator;
import java.net.PasswordAuthentication;
import java.util.Locale;
import javax.annotation.Nullable;
import javax.inject.Provider;
import javax.inject.Singleton;
import joptsimple.ArgumentAcceptingOptionSpec;
import joptsimple.OptionParser;
import joptsimple.OptionSet;
import joptsimple.util.EnumConverter;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import net.runelite.client.account.SessionManager;
import net.runelite.client.chat.ChatMessageManager;
import net.runelite.client.chat.CommandManager;
import net.runelite.client.config.ConfigManager;
import net.runelite.client.discord.DiscordService;
import net.runelite.client.eventbus.EventBus;
import net.runelite.client.game.ClanManager;
import net.runelite.client.game.ItemManager;
import net.runelite.client.game.LootManager;
import net.runelite.client.game.chatbox.ChatboxPanelManager;
import net.runelite.client.graphics.ModelOutlineRenderer;
import net.runelite.client.menus.MenuManager;
import net.runelite.client.plugins.Plugin;
import net.runelite.client.plugins.PluginInstantiationException;
import net.runelite.client.plugins.PluginManager;
import net.runelite.client.rs.ClientUpdateCheckMode;
import net.runelite.client.task.Scheduler;
import net.runelite.client.ui.ClientUI;
import net.runelite.client.ui.DrawManager;
import net.runelite.client.ui.RuneLiteSplashScreen;
import net.runelite.client.ui.overlay.OverlayManager;
import net.runelite.client.ui.overlay.OverlayRenderer;
import net.runelite.client.ui.overlay.WidgetOverlay;
import net.runelite.client.ui.overlay.arrow.ArrowMinimapOverlay;
import net.runelite.client.ui.overlay.arrow.ArrowWorldOverlay;
import net.runelite.client.ui.overlay.infobox.InfoBoxManager;
import net.runelite.client.ui.overlay.infobox.InfoBoxOverlay;
import net.runelite.client.ui.overlay.tooltip.TooltipOverlay;
import net.runelite.client.ui.overlay.worldmap.WorldMapOverlay;
import net.runelite.client.ws.PartyService;
import org.slf4j.LoggerFactory;
@Singleton
@Slf4j
public class RuneLite
{
public static final String RUNELIT_VERSION = "0.1.2";
public static final File RUNELITE_DIR = new File(System.getProperty("user.home"), ".runelite");
public static final File PROFILES_DIR = new File(RUNELITE_DIR, "profiles");
public static final File PLUGIN_DIR = new File(RUNELITE_DIR, "plugins");
public static final File SCREENSHOT_DIR = new File(RUNELITE_DIR, "screenshots");
static final RuneLiteSplashScreen splashScreen = new RuneLiteSplashScreen();
@Getter
private static Injector injector;
@Inject
private PluginManager pluginManager;
@Inject
private EventBus eventBus;
@Inject
private ConfigManager configManager;
@Inject
private DrawManager drawManager;
@Inject
private SessionManager sessionManager;
@Inject
public DiscordService discordService;
@Inject
private ClientSessionManager clientSessionManager;
@Inject
private ClientUI clientUI;
@Inject
private InfoBoxManager infoBoxManager;
@Inject
private OverlayManager overlayManager;
@Inject
private PartyService partyService;
@Inject
private Provider<ItemManager> itemManager;
@Inject
private Provider<OverlayRenderer> overlayRenderer;
@Inject
private Provider<ClanManager> clanManager;
@Inject
private Provider<ChatMessageManager> chatMessageManager;
@Inject
private Provider<MenuManager> menuManager;
@Inject
private Provider<CommandManager> commandManager;
@Inject
private Provider<InfoBoxOverlay> infoBoxOverlay;
@Inject
private Provider<TooltipOverlay> tooltipOverlay;
@Inject
private Provider<WorldMapOverlay> worldMapOverlay;
@Inject
private Provider<ArrowWorldOverlay> arrowWorldOverlay;
@Inject
private Provider<ArrowMinimapOverlay> arrowMinimapOverlay;
@Inject
private Provider<LootManager> lootManager;
@Inject
private Provider<ChatboxPanelManager> chatboxPanelManager;
@Inject
@Nullable
private Client client;
@Inject
private Provider<ModelOutlineRenderer> modelOutlineRenderer;
@Inject
private Scheduler scheduler;
public static void main(String[] args) throws Exception
{
Locale.setDefault(Locale.ENGLISH);
final OptionParser parser = new OptionParser();
parser.accepts("developer-mode", "Enable developer tools");
parser.accepts("debug", "Show extra debugging output");
parser.accepts("no-splash", "Do not show the splash screen");
final ArgumentAcceptingOptionSpec<String> proxyInfo = parser
.accepts("proxy")
.withRequiredArg().ofType(String.class);
final ArgumentAcceptingOptionSpec<ClientUpdateCheckMode> updateMode = parser
.accepts("rs", "Select client type")
.withRequiredArg()
.ofType(ClientUpdateCheckMode.class)
.defaultsTo(ClientUpdateCheckMode.AUTO)
.withValuesConvertedBy(new EnumConverter<ClientUpdateCheckMode>(ClientUpdateCheckMode.class)
{
@Override
public ClientUpdateCheckMode convert(String v)
{
return super.convert(v.toUpperCase());
}
});
parser.accepts("help", "Show this text").forHelp();
OptionSet options = parser.parse(args);
if (options.has("proxy"))
{
String[] proxy = options.valueOf(proxyInfo).split(":");
if (proxy.length >= 2)
{
System.setProperty("socksProxyHost", proxy[0]);
System.setProperty("socksProxyPort", proxy[1]);
}
if (proxy.length >= 4)
{
System.setProperty("java.net.socks.username", proxy[2]);
System.setProperty("java.net.socks.password", proxy[3]);
final String user = proxy[2];
final char[] pass = proxy[3].toCharArray();
Authenticator.setDefault(new Authenticator()
{
private PasswordAuthentication auth = new PasswordAuthentication(user, pass);
protected PasswordAuthentication getPasswordAuthentication()
{
return auth;
}
});
}
}
if (options.has("help"))
{
parser.printHelpOn(System.out);
System.exit(0);
}
final boolean developerMode = true;
if (developerMode)
{
boolean assertions = false;
assert assertions = true;
if (!assertions)
{
java.util.logging.Logger.getAnonymousLogger().warning("Developers should enable assertions; Add `-ea` to your JVM arguments`");
}
}
PROFILES_DIR.mkdirs();
if (options.has("debug"))
{
final Logger logger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
logger.setLevel(Level.DEBUG);
}
Thread.setDefaultUncaughtExceptionHandler((thread, throwable) ->
{
log.error("Uncaught exception:", throwable);
if (throwable instanceof AbstractMethodError)
{
log.error("Classes are out of date; Build with maven again.");
}
});
if (!options.has("no-splash"))
{
splashScreen.open(4);
}
// The submessage is shown in case the connection is slow
splashScreen.setMessage("Starting RuneLite Injector");
splashScreen.setSubMessage(" ");
final long start = System.currentTimeMillis();
injector = Guice.createInjector(new RuneLiteModule(
options.valueOf(updateMode),
developerMode));
injector.getInstance(RuneLite.class).start();
splashScreen.setProgress(1, 5);
final long end = System.currentTimeMillis();
final RuntimeMXBean rb = ManagementFactory.getRuntimeMXBean();
final long uptime = rb.getUptime();
log.info("Client initialization took {}ms. Uptime: {}ms", end - start, uptime);
}
public void start() throws Exception
{
// Load RuneLite or Vanilla client
final boolean isOutdated = client == null;
if (!isOutdated)
{
// Inject members into client
injector.injectMembers(client);
}
// Load user configuration
splashScreen.setMessage("Loading configuration");
configManager.load();
// Load the session, including saved configuration
sessionManager.loadSession();
splashScreen.setProgress(2, 5);
splashScreen.setMessage("Loading plugins");
// Begin watching for new plugins
pluginManager.watch();
// Tell the plugin manager if client is outdated or not
pluginManager.setOutdated(isOutdated);
// Load the plugins, but does not start them yet.
// This will initialize configuration
pluginManager.loadCorePlugins();
// Plugins have provided their config, so set default config
// to main settings
pluginManager.loadDefaultPluginConfiguration();
splashScreen.setProgress(3, 5);
splashScreen.setMessage("Starting Session");
// Start client session
clientSessionManager.start();
splashScreen.setProgress(4, 5);
// Load the session, including saved configuration
splashScreen.setMessage("Loading interface");
splashScreen.setProgress(5, 5);
// Initialize UI
clientUI.open(this);
// Close the splash screen
splashScreen.close();
// Register event listeners
eventBus.register(clientUI);
eventBus.register(pluginManager);
eventBus.register(overlayManager);
eventBus.register(drawManager);
eventBus.register(infoBoxManager);
eventBus.register(partyService);
if (!isOutdated)
{
// Initialize chat colors
chatMessageManager.get().loadColors();
eventBus.register(overlayRenderer.get());
eventBus.register(clanManager.get());
eventBus.register(itemManager.get());
eventBus.register(menuManager.get());
eventBus.register(chatMessageManager.get());
eventBus.register(commandManager.get());
eventBus.register(lootManager.get());
eventBus.register(chatboxPanelManager.get());
// Add core overlays
WidgetOverlay.createOverlays(client).forEach(overlayManager::add);
overlayManager.add(infoBoxOverlay.get());
overlayManager.add(worldMapOverlay.get());
overlayManager.add(tooltipOverlay.get());
overlayManager.add(arrowWorldOverlay.get());
overlayManager.add(arrowMinimapOverlay.get());
}
// Start plugins
pluginManager.startCorePlugins();
// Register additional schedulers
if (this.client != null)
{
scheduler.registerObject(modelOutlineRenderer.get());
}
}
public void shutdown()
{
configManager.sendConfig();
clientSessionManager.shutdown();
discordService.close();
for (final Plugin plugin : pluginManager.getPlugins())
{
try
{
pluginManager.stopPlugin(plugin);
}
catch (PluginInstantiationException e)
{
log.warn("Failed to gracefully close plugin", e);
}
}
}
@VisibleForTesting
public static void setInjector(Injector injector)
{
RuneLite.injector = injector;
}
}

View File

@@ -0,0 +1,126 @@
/*
* Copyright (c) 2016-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.client;
import api.Client;
import api.hooks.Callbacks;
import com.google.inject.AbstractModule;
import com.google.inject.Provides;
import com.google.inject.name.Names;
import java.applet.Applet;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import javax.annotation.Nullable;
import javax.inject.Singleton;
import lombok.extern.slf4j.Slf4j;
import net.runelite.client.account.SessionManager;
import net.runelite.client.callback.Hooks;
import net.runelite.client.chat.ChatMessageManager;
import net.runelite.client.config.ChatColorConfig;
import net.runelite.client.config.ConfigManager;
import net.runelite.client.config.RuneLiteConfig;
import net.runelite.client.eventbus.EventBus;
import net.runelite.client.game.ItemManager;
import net.runelite.client.menus.MenuManager;
import net.runelite.client.plugins.PluginManager;
import net.runelite.client.rs.ClientLoader;
import net.runelite.client.rs.ClientUpdateCheckMode;
import net.runelite.client.task.Scheduler;
import net.runelite.client.util.DeferredEventBus;
import net.runelite.client.util.ExecutorServiceExceptionLogger;
import net.runelite.http.api.RuneLiteAPI;
import okhttp3.OkHttpClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Slf4j
public class RuneLiteModule extends AbstractModule
{
private final ClientUpdateCheckMode updateCheckMode;
private final boolean developerMode;
public RuneLiteModule(final ClientUpdateCheckMode updateCheckMode, final boolean developerMode)
{
this.updateCheckMode = updateCheckMode;
this.developerMode = developerMode;
}
@Override
protected void configure()
{
bindConstant().annotatedWith(Names.named("updateCheckMode")).to(updateCheckMode);
bindConstant().annotatedWith(Names.named("developerMode")).to(developerMode);
bind(ScheduledExecutorService.class).toInstance(new ExecutorServiceExceptionLogger(Executors.newSingleThreadScheduledExecutor()));
bind(OkHttpClient.class).toInstance(RuneLiteAPI.CLIENT);
bind(MenuManager.class);
bind(ChatMessageManager.class);
bind(ItemManager.class);
bind(Scheduler.class);
bind(PluginManager.class);
bind(RuneLiteProperties.class);
bind(SessionManager.class);
bind(Callbacks.class).to(Hooks.class);
bind(EventBus.class)
.toInstance(new EventBus());
bind(EventBus.class)
.annotatedWith(Names.named("Deferred EventBus"))
.to(DeferredEventBus.class);
bind(Logger.class)
.annotatedWith(Names.named("Core Logger"))
.toInstance(LoggerFactory.getLogger(RuneLite.class));
}
@Provides
@Singleton
Applet provideApplet(ClientLoader clientLoader)
{
return clientLoader.load();
}
@Provides
@Singleton
Client provideClient(@Nullable Applet applet)
{
return applet instanceof Client ? (Client) applet : null;
}
@Provides
@Singleton
RuneLiteConfig provideConfig(ConfigManager configManager)
{
return configManager.getConfig(RuneLiteConfig.class);
}
@Provides
@Singleton
ChatColorConfig provideChatColorConfig(ConfigManager configManager)
{
return configManager.getConfig(ChatColorConfig.class);
}
}

View File

@@ -0,0 +1,124 @@
/*
* 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.client;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.inject.Singleton;
import lombok.extern.slf4j.Slf4j;
import net.runelite.client.plugins.runeliteplus.RuneLitePlusPlugin;
@Singleton
@Slf4j
public class RuneLiteProperties
{
public static String discordAppID = "409416265891971072";
private static final String RUNELITE_TITLE = "runelite.title";
private static final String RUNELITE_VERSION = "runelite.version";
private static final String RUNESCAPE_VERSION = "runescape.version";
private static final String DISCORD_APP_ID = "runelite.discord.appid";
private static final String DISCORD_INVITE = "runelite.discord.invite";
private static final String GITHUB_LINK = "runelite.github.link";
private static final String WIKI_LINK = "runelite.wiki.link";
private static final String PATREON_LINK = "runelite.patreon.link";
private static final String LAUNCHER_VERSION_PROPERTY = "runelite.launcher.version";
private final Properties properties = new Properties();
@Inject
public RuneLiteProperties()
{
try (InputStream in = getClass().getResourceAsStream("runelite.properties"))
{
properties.load(in);
}
catch (IOException ex)
{
log.warn("unable to load propertries", ex);
}
}
public String getTitle()
{
final StringBuilder sb = new StringBuilder(properties.getProperty(RUNELITE_TITLE));
String proxy;
if ((proxy = System.getProperty("socksProxyHost")) != null)
{
sb.append(String.format(" (%s)", proxy));
}
return sb.toString();
}
public String getVersion()
{
return properties.getProperty(RUNELITE_VERSION);
}
public String getRunescapeVersion()
{
return properties.getProperty(RUNESCAPE_VERSION);
}
public String getDiscordAppId()
{
if (RuneLitePlusPlugin.customPresenceEnabled)
{
return properties.getProperty(RuneLitePlusPlugin.rlPlusDiscordApp);
}
else
{
return properties.getProperty(DISCORD_APP_ID);
}
}
public String getDiscordInvite()
{
return properties.getProperty(DISCORD_INVITE);
}
public String getGithubLink()
{
return properties.getProperty(GITHUB_LINK);
}
public String getWikiLink()
{
return properties.getProperty(WIKI_LINK);
}
public String getPatreonLink()
{
return properties.getProperty(PATREON_LINK);
}
@Nullable
public static String getLauncherVersion()
{
return System.getProperty(LAUNCHER_VERSION_PROPERTY);
}
}

View File

@@ -0,0 +1,95 @@
/*
* Copyright (c) 2018, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.client;
import com.google.gson.JsonParseException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.UUID;
import net.runelite.http.api.RuneLiteAPI;
import okhttp3.HttpUrl;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
class SessionClient
{
UUID open() throws IOException
{
HttpUrl url = RuneLiteAPI.getSessionBase().newBuilder()
.build();
Request request = new Request.Builder()
.url(url)
.build();
try (Response response = RuneLiteAPI.CLIENT.newCall(request).execute())
{
ResponseBody body = response.body();
InputStream in = body.byteStream();
return RuneLiteAPI.GSON.fromJson(new InputStreamReader(in), UUID.class);
}
catch (JsonParseException | IllegalArgumentException ex) // UUID.fromString can throw IllegalArgumentException
{
throw new IOException(ex);
}
}
void ping(UUID uuid) throws IOException
{
HttpUrl url = RuneLiteAPI.getSessionBase().newBuilder()
.addPathSegment("ping")
.addQueryParameter("session", uuid.toString())
.build();
Request request = new Request.Builder()
.url(url)
.build();
try (Response response = RuneLiteAPI.CLIENT.newCall(request).execute())
{
if (!response.isSuccessful())
{
throw new IOException("Unsuccessful ping");
}
}
}
void delete(UUID uuid) throws IOException
{
HttpUrl url = RuneLiteAPI.getSessionBase().newBuilder()
.addQueryParameter("session", uuid.toString())
.build();
Request request = new Request.Builder()
.delete()
.url(url)
.build();
RuneLiteAPI.CLIENT.newCall(request).execute().close();
}
}

View File

@@ -0,0 +1,39 @@
/*
* 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.client.account;
import java.time.Instant;
import java.util.UUID;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(of = "uuid")
public class AccountSession
{
private final UUID uuid;
private final Instant created;
private String username;
}

View File

@@ -0,0 +1,232 @@
/*
* 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.client.account;
import com.google.gson.Gson;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.time.Instant;
import java.util.UUID;
import javax.inject.Inject;
import javax.inject.Singleton;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import net.runelite.client.RuneLite;
import net.runelite.client.config.ConfigManager;
import net.runelite.client.eventbus.EventBus;
import net.runelite.client.eventbus.Subscribe;
import net.runelite.client.events.SessionClose;
import net.runelite.client.events.SessionOpen;
import net.runelite.client.util.LinkBrowser;
import net.runelite.client.ws.WSClient;
import net.runelite.http.api.account.AccountClient;
import net.runelite.http.api.account.OAuthResponse;
import net.runelite.http.api.ws.messages.LoginResponse;
@Singleton
@Slf4j
public class SessionManager
{
private static final File SESSION_FILE = new File(RuneLite.RUNELITE_DIR, "session");
@Getter
private AccountSession accountSession;
private final EventBus eventBus;
private final ConfigManager configManager;
private final WSClient wsClient;
@Inject
private SessionManager(ConfigManager configManager, EventBus eventBus, WSClient wsClient)
{
this.configManager = configManager;
this.eventBus = eventBus;
this.wsClient = wsClient;
eventBus.register(this);
}
public void loadSession()
{
if (!SESSION_FILE.exists())
{
log.info("No session file exists");
return;
}
AccountSession session;
try (FileInputStream in = new FileInputStream(SESSION_FILE))
{
session = new Gson().fromJson(new InputStreamReader(in), AccountSession.class);
log.debug("Loaded session for {}", session.getUsername());
}
catch (Exception ex)
{
log.warn("Unable to load session file", ex);
return;
}
// Check if session is still valid
AccountClient accountClient = new AccountClient(session.getUuid());
if (!accountClient.sesssionCheck())
{
log.debug("Loaded session {} is invalid", session.getUuid());
return;
}
openSession(session, false);
}
private void saveSession()
{
if (accountSession == null)
{
return;
}
try (FileWriter fw = new FileWriter(SESSION_FILE))
{
new Gson().toJson(accountSession, fw);
log.debug("Saved session to {}", SESSION_FILE);
}
catch (IOException ex)
{
log.warn("Unable to save session file", ex);
}
}
private void deleteSession()
{
SESSION_FILE.delete();
}
/**
* Set the given session as the active session and open a socket to the
* server with the given session
*
* @param session session
*/
private void openSession(AccountSession session, boolean openSocket)
{
// Change session on the websocket
if (openSocket)
{
wsClient.changeSession(session.getUuid());
}
accountSession = session;
if (session.getUsername() != null)
{
// Initialize config for new session
// If the session isn't logged in yet, don't switch to the new config
configManager.switchSession();
}
eventBus.post(new SessionOpen());
}
private void closeSession()
{
wsClient.changeSession(null);
if (accountSession == null)
{
return;
}
log.debug("Logging out of account {}", accountSession.getUsername());
AccountClient client = new AccountClient(accountSession.getUuid());
try
{
client.logout();
}
catch (IOException ex)
{
log.warn("Unable to logout of session", ex);
}
accountSession = null; // No more account
// Restore config
configManager.switchSession();
eventBus.post(new SessionClose());
}
public void login()
{
// If a session is already open, use that id. Otherwise generate a new id.
UUID uuid = wsClient.getSessionId() != null ? wsClient.getSessionId() : UUID.randomUUID();
AccountClient loginClient = new AccountClient(uuid);
final OAuthResponse login;
try
{
login = loginClient.login();
}
catch (IOException ex)
{
log.warn("Unable to get oauth url", ex);
return;
}
// Create new session
openSession(new AccountSession(login.getUid(), Instant.now()), true);
// Navigate to login link
LinkBrowser.browse(login.getOauthUrl());
}
@Subscribe
public void onLoginResponse(LoginResponse loginResponse)
{
log.debug("Now logged in as {}", loginResponse.getUsername());
AccountSession session = getAccountSession();
session.setUsername(loginResponse.getUsername());
// Open session, again, now that we have a username
// This triggers onSessionOpen
// The socket is already opened here anyway so we pass true for openSocket
openSession(session, true);
// Save session to disk
saveSession();
}
public void logout()
{
closeSession();
deleteSession();
}
}

View File

@@ -0,0 +1,115 @@
/*
* 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.callback;
import api.Client;
import com.google.inject.Inject;
import java.util.Iterator;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.function.BooleanSupplier;
import javax.inject.Singleton;
import lombok.extern.slf4j.Slf4j;
@Singleton
@Slf4j
public class ClientThread
{
private ConcurrentLinkedQueue<BooleanSupplier> invokes = new ConcurrentLinkedQueue<>();
@Inject
private Client client;
public void invoke(Runnable r)
{
invoke(() ->
{
r.run();
return true;
});
}
/**
* Will run r on the game thread, at a unspecified point in the future.
* If r returns false, r will be ran again, at a later point
*/
public void invoke(BooleanSupplier r)
{
if (client.isClientThread())
{
if (r.getAsBoolean())
{
invokes.add(r);
}
return;
}
invokeLater(r);
}
/**
* Will run r on the game thread after this method returns
* If r returns false, r will be ran again, at a later point
*/
public void invokeLater(Runnable r)
{
invokeLater(() ->
{
r.run();
return true;
});
}
public void invokeLater(BooleanSupplier r)
{
invokes.add(r);
}
void invoke()
{
assert client.isClientThread();
Iterator<BooleanSupplier> ir = invokes.iterator();
for (; ir.hasNext(); )
{
BooleanSupplier r = ir.next();
boolean remove = true;
try
{
remove = r.getAsBoolean();
}
catch (ThreadDeath d)
{
throw d;
}
catch (Throwable e)
{
log.warn("Exception in invoke", e);
}
if (remove)
{
ir.remove();
}
}
}
}

View File

@@ -0,0 +1,535 @@
/*
* 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.client.callback;
import api.BufferProvider;
import api.Client;
import api.MainBufferProvider;
import api.NullItemID;
import api.Point;
import api.RenderOverview;
import api.Renderable;
import api.WorldMapManager;
import api.config.Constants;
import api.events.BeforeMenuRender;
import api.events.BeforeRender;
import api.events.GameTick;
import api.hooks.Callbacks;
import api.hooks.DrawCallbacks;
import api.widgets.Widget;
import static api.widgets.WidgetInfo.WORLD_MAP_VIEW;
import api.widgets.WidgetItem;
import com.google.inject.Injector;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.image.BufferedImage;
import java.awt.image.VolatileImage;
import javax.inject.Inject;
import javax.inject.Singleton;
import lombok.extern.slf4j.Slf4j;
import net.runelite.client.Notifier;
import net.runelite.client.RuneLite;
import net.runelite.client.chat.ChatMessageManager;
import net.runelite.client.eventbus.EventBus;
import net.runelite.client.input.KeyManager;
import net.runelite.client.input.MouseManager;
import net.runelite.client.task.Scheduler;
import net.runelite.client.ui.ClientUI;
import net.runelite.client.ui.DrawManager;
import net.runelite.client.ui.overlay.OverlayLayer;
import net.runelite.client.ui.overlay.OverlayManager;
import net.runelite.client.ui.overlay.OverlayRenderer;
import net.runelite.client.ui.overlay.infobox.InfoBoxManager;
import net.runelite.client.util.DeferredEventBus;
/**
* This class contains field required for mixins and runelite hooks to work.
* All remaining method hooks in this class are performance-critical or contain client-specific logic and so they
* can't just be placed in mixins or sent through event bus.
*/
@Singleton
@Slf4j
public class Hooks implements Callbacks
{
private static final long CHECK = Constants.GAME_TICK_LENGTH; // ms - how often to run checks
private static final Injector injector = RuneLite.getInjector();
private static final Client client = injector.getInstance(Client.class);
private static final OverlayRenderer renderer = injector.getInstance(OverlayRenderer.class);
private static final OverlayManager overlayManager = injector.getInstance(OverlayManager.class);
private static final GameTick GAME_TICK = new GameTick();
private static final BeforeRender BEFORE_RENDER = new BeforeRender();
@Inject
private EventBus eventBus;
@Inject
private DeferredEventBus deferredEventBus;
@Inject
private Scheduler scheduler;
@Inject
private InfoBoxManager infoBoxManager;
@Inject
private ChatMessageManager chatMessageManager;
@Inject
private MouseManager mouseManager;
@Inject
private KeyManager keyManager;
@Inject
private ClientThread clientThread;
@Inject
private DrawManager drawManager;
@Inject
private Notifier notifier;
@Inject
private ClientUI clientUi;
private Dimension lastStretchedDimensions;
private VolatileImage stretchedImage;
private Graphics2D stretchedGraphics;
private long lastCheck;
private boolean shouldProcessGameTick;
@Override
public void post(Object event)
{
eventBus.post(event);
}
@Override
public void postDeferred(Object event)
{
deferredEventBus.post(event);
}
@Override
public void clientMainLoop()
{
if (shouldProcessGameTick)
{
shouldProcessGameTick = false;
deferredEventBus.replay();
eventBus.post(GAME_TICK);
int tick = client.getTickCount();
client.setTickCount(tick + 1);
}
eventBus.post(BEFORE_RENDER);
clientThread.invoke();
long now = System.currentTimeMillis();
if (now - lastCheck < CHECK)
{
return;
}
lastCheck = now;
try
{
// tick pending scheduled tasks
scheduler.tick();
// cull infoboxes
infoBoxManager.cull();
chatMessageManager.process();
checkWorldMap();
}
catch (Exception ex)
{
log.warn("error during main loop tasks", ex);
}
}
/**
* When the world map opens it loads about ~100mb of data into memory, which
* represents about half of the total memory allocated by the client.
* This gets cached and never released, which causes GC pressure which can affect
* performance. This method reinitializes the world map cache, which allows the
* data to be garbage collected, and causes the map data from disk each time
* is it opened.
*/
private void checkWorldMap()
{
Widget widget = client.getWidget(WORLD_MAP_VIEW);
if (widget != null)
{
return;
}
RenderOverview renderOverview = client.getRenderOverview();
if (renderOverview == null)
{
return;
}
WorldMapManager manager = renderOverview.getWorldMapManager();
if (manager != null && manager.isLoaded())
{
log.debug("World map was closed, reinitializing");
renderOverview.initializeWorldMap(renderOverview.getWorldMapData());
}
}
@Override
public MouseEvent mousePressed(MouseEvent mouseEvent)
{
return mouseManager.processMousePressed(mouseEvent);
}
@Override
public MouseEvent mouseReleased(MouseEvent mouseEvent)
{
return mouseManager.processMouseReleased(mouseEvent);
}
@Override
public MouseEvent mouseClicked(MouseEvent mouseEvent)
{
return mouseManager.processMouseClicked(mouseEvent);
}
@Override
public MouseEvent mouseEntered(MouseEvent mouseEvent)
{
return mouseManager.processMouseEntered(mouseEvent);
}
@Override
public MouseEvent mouseExited(MouseEvent mouseEvent)
{
return mouseManager.processMouseExited(mouseEvent);
}
@Override
public MouseEvent mouseDragged(MouseEvent mouseEvent)
{
return mouseManager.processMouseDragged(mouseEvent);
}
@Override
public MouseEvent mouseMoved(MouseEvent mouseEvent)
{
return mouseManager.processMouseMoved(mouseEvent);
}
@Override
public MouseWheelEvent mouseWheelMoved(MouseWheelEvent event)
{
return mouseManager.processMouseWheelMoved(event);
}
@Override
public void keyPressed(KeyEvent keyEvent)
{
keyManager.processKeyPressed(keyEvent);
}
@Override
public void keyReleased(KeyEvent keyEvent)
{
keyManager.processKeyReleased(keyEvent);
}
@Override
public void keyTyped(KeyEvent keyEvent)
{
keyManager.processKeyTyped(keyEvent);
}
@Override
public void draw(MainBufferProvider mainBufferProvider, Graphics graphics, int x, int y)
{
if (graphics == null)
{
return;
}
Image image = mainBufferProvider.getImage();
final Image finalImage;
final Graphics2D graphics2d = (Graphics2D) image.getGraphics();
try
{
renderer.render(graphics2d, OverlayLayer.ALWAYS_ON_TOP);
}
catch (Exception ex)
{
log.warn("Error during overlay rendering", ex);
}
notifier.processFlash(graphics2d);
// Draw clientUI overlays
clientUi.paintOverlays(graphics2d);
graphics2d.dispose();
if (client.isGpu())
{
// processDrawComplete gets called on GPU by the gpu plugin at the end of its
// drawing cycle, which is later on.
return;
}
// Stretch the game image if the user has that enabled
if (client.isStretchedEnabled())
{
GraphicsConfiguration gc = clientUi.getGraphicsConfiguration();
Dimension stretchedDimensions = client.getStretchedDimensions();
if (lastStretchedDimensions == null || !lastStretchedDimensions.equals(stretchedDimensions)
|| (stretchedImage != null && stretchedImage.validate(gc) == VolatileImage.IMAGE_INCOMPATIBLE))
{
/*
Reuse the resulting image instance to avoid creating an extreme amount of objects
*/
stretchedImage = gc.createCompatibleVolatileImage(stretchedDimensions.width, stretchedDimensions.height);
if (stretchedGraphics != null)
{
stretchedGraphics.dispose();
}
stretchedGraphics = (Graphics2D) stretchedImage.getGraphics();
lastStretchedDimensions = stretchedDimensions;
/*
Fill Canvas before drawing stretched image to prevent artifacts.
*/
graphics.setColor(Color.BLACK);
graphics.fillRect(0, 0, client.getCanvas().getWidth(), client.getCanvas().getHeight());
}
stretchedGraphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
client.isStretchedFast()
? RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR
: RenderingHints.VALUE_INTERPOLATION_BILINEAR);
stretchedGraphics.drawImage(image, 0, 0, stretchedDimensions.width, stretchedDimensions.height, null);
finalImage = stretchedImage;
}
else
{
finalImage = image;
}
// Draw the image onto the game canvas
graphics.drawImage(finalImage, 0, 0, client.getCanvas());
// finalImage is backed by the client buffer which will change soon. make a copy
// so that callbacks can safely use it later from threads.
drawManager.processDrawComplete(() -> copy(finalImage));
}
/**
* Copy an image
*
* @param src
* @return
*/
private static Image copy(Image src)
{
final int width = src.getWidth(null);
final int height = src.getHeight(null);
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics graphics = image.getGraphics();
graphics.drawImage(src, 0, 0, width, height, null);
graphics.dispose();
return image;
}
@Override
public void drawScene()
{
MainBufferProvider bufferProvider = (MainBufferProvider) client.getBufferProvider();
BufferedImage image = (BufferedImage) bufferProvider.getImage();
Graphics2D graphics2d = image.createGraphics();
// Update selected scene tile
if (!client.isMenuOpen())
{
Point p = client.getMouseCanvasPosition();
p = new Point(
p.getX() - client.getViewportXOffset(),
p.getY() - client.getViewportYOffset());
client.setCheckClick(true);
client.setMouseCanvasHoverPosition(p);
}
try
{
renderer.render(graphics2d, OverlayLayer.ABOVE_SCENE);
}
catch (Exception ex)
{
log.warn("Error during overlay rendering", ex);
}
finally
{
graphics2d.dispose();
}
}
@Override
public void drawAboveOverheads()
{
MainBufferProvider bufferProvider = (MainBufferProvider) client.getBufferProvider();
BufferedImage image = (BufferedImage) bufferProvider.getImage();
Graphics2D graphics2d = image.createGraphics();
try
{
renderer.render(graphics2d, OverlayLayer.UNDER_WIDGETS);
}
catch (Exception ex)
{
log.warn("Error during overlay rendering", ex);
}
finally
{
graphics2d.dispose();
}
}
public static void drawAfterWidgets()
{
MainBufferProvider bufferProvider = (MainBufferProvider) client.getBufferProvider();
BufferedImage image = (BufferedImage) bufferProvider.getImage();
Graphics2D graphics2d = image.createGraphics();
try
{
renderer.render(graphics2d, OverlayLayer.ABOVE_WIDGETS);
renderer.render(graphics2d, OverlayLayer.ABOVE_MAP);
}
catch (Exception ex)
{
log.warn("Error during overlay rendering", ex);
}
finally
{
graphics2d.dispose();
}
// WidgetItemOverlays render at ABOVE_WIDGETS, reset widget item
// list for next frame.
overlayManager.getItemWidgets().clear();
}
@Override
public void updateNpcs()
{
// The NPC update event seem to run every server tick,
// but having the game tick event after all packets
// have been processed is typically more useful.
shouldProcessGameTick = true;
// Replay deferred events, otherwise if two npc
// update packets get processed in one frame, a
// despawn event could be published prior to the
// spawn event, which is deferred
deferredEventBus.replay();
}
public static void renderDraw(Renderable renderable, int orientation, int pitchSin, int pitchCos, int yawSin, int yawCos, int x, int y, int z, long hash)
{
DrawCallbacks drawCallbacks = client.getDrawCallbacks();
if (drawCallbacks != null)
{
drawCallbacks.draw(renderable, orientation, pitchSin, pitchCos, yawSin, yawCos, x, y, z, hash);
}
else
{
renderable.draw(orientation, pitchSin, pitchCos, yawSin, yawCos, x, y, z, hash);
}
}
public static void clearColorBuffer(int x, int y, int width, int height, int color)
{
BufferProvider bp = client.getBufferProvider();
int canvasWidth = bp.getWidth();
int[] pixels = bp.getPixels();
int pixelPos = y * canvasWidth + x;
int pixelJump = canvasWidth - width;
for (int cy = y; cy < y + height; cy++)
{
for (int cx = x; cx < x + width; cx++)
{
pixels[pixelPos++] = 0;
}
pixelPos += pixelJump;
}
}
@Override
public void drawItem(int itemId, WidgetItem widgetItem)
{
// Empty bank item
if (widgetItem.getId() != NullItemID.NULL_6512)
{
overlayManager.getItemWidgets().add(widgetItem);
}
}
public static boolean drawMenu()
{
BeforeMenuRender event = new BeforeMenuRender();
client.getCallbacks().post(event);
return event.isConsumed();
}
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright (c) 2017, Tomas Slusny <slusnucky@gmail.com>
* 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.chat;
import java.awt.Color;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(exclude = {"color", "isDefault"})
public class ChatColor
{
private ChatColorType type;
private Color color;
private boolean transparent;
private boolean isDefault;
public ChatColor(ChatColorType type, Color color, boolean transparent)
{
this(type, color, transparent, false);
}
public ChatColor(ChatColorType type, Color color, boolean transparent, boolean isDefault)
{
this.type = type;
this.color = color;
this.transparent = transparent;
this.isDefault = isDefault;
}
}

View File

@@ -0,0 +1,31 @@
/*
* Copyright (c) 2017, Tomas Slusny <slusnucky@gmail.com>
* 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.chat;
public enum ChatColorType
{
NORMAL,
HIGHLIGHT
}

View File

@@ -0,0 +1,42 @@
/*
* 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.client.chat;
import api.events.ChatMessage;
import java.util.function.BiConsumer;
import java.util.function.BiPredicate;
import lombok.AllArgsConstructor;
import lombok.Getter;
import net.runelite.client.events.ChatInput;
@AllArgsConstructor
@Getter
class ChatCommand
{
private final String name;
private boolean async;
private final BiConsumer<ChatMessage, String> execute;
private final BiPredicate<ChatInput, String> input;
}

View File

@@ -0,0 +1,198 @@
/*
* 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.client.chat;
import api.Client;
import api.GameState;
import api.events.ChatMessage;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ScheduledExecutorService;
import java.util.function.BiConsumer;
import java.util.function.BiPredicate;
import javax.inject.Inject;
import javax.inject.Singleton;
import lombok.extern.slf4j.Slf4j;
import net.runelite.client.eventbus.EventBus;
import net.runelite.client.eventbus.Subscribe;
import net.runelite.client.events.ChatInput;
import net.runelite.client.events.ChatboxInput;
import net.runelite.client.events.PrivateMessageInput;
@Singleton
@Slf4j
public class ChatCommandManager implements ChatboxInputListener
{
private final Map<String, ChatCommand> commands = new HashMap<>();
private final Client client;
private final ScheduledExecutorService scheduledExecutorService;
@Inject
private ChatCommandManager(EventBus eventBus, CommandManager commandManager, Client client, ScheduledExecutorService scheduledExecutorService)
{
this.client = client;
this.scheduledExecutorService = scheduledExecutorService;
eventBus.register(this);
commandManager.register(this);
}
public void registerCommand(String command, BiConsumer<ChatMessage, String> execute)
{
registerCommand(command, execute, null);
}
public void registerCommand(String command, BiConsumer<ChatMessage, String> execute, BiPredicate<ChatInput, String> input)
{
commands.put(command.toLowerCase(), new ChatCommand(command, false, execute, input));
}
public void registerCommandAsync(String command, BiConsumer<ChatMessage, String> execute)
{
registerCommandAsync(command, execute, null);
}
public void registerCommandAsync(String command, BiConsumer<ChatMessage, String> execute, BiPredicate<ChatInput, String> input)
{
commands.put(command.toLowerCase(), new ChatCommand(command, true, execute, input));
}
public void unregisterCommand(String command)
{
commands.remove(command.toLowerCase());
}
@Subscribe
public void onChatMessage(ChatMessage chatMessage)
{
if (client.getGameState() != GameState.LOGGED_IN)
{
return;
}
switch (chatMessage.getType())
{
case PUBLICCHAT:
case MODCHAT:
case FRIENDSCHAT:
case PRIVATECHAT:
case MODPRIVATECHAT:
case PRIVATECHATOUT:
break;
default:
return;
}
String message = chatMessage.getMessage();
String command = extractCommand(message);
if (command == null)
{
return;
}
ChatCommand chatCommand = commands.get(command.toLowerCase());
if (chatCommand == null)
{
return;
}
if (chatCommand.isAsync())
{
scheduledExecutorService.execute(() -> chatCommand.getExecute().accept(chatMessage, message));
}
else
{
chatCommand.getExecute().accept(chatMessage, message);
}
}
@Override
public boolean onChatboxInput(ChatboxInput chatboxInput)
{
String message = chatboxInput.getValue();
if (message.startsWith("/"))
{
message = message.substring(1); // clan chat input
}
String command = extractCommand(message);
if (command == null)
{
return false;
}
ChatCommand chatCommand = commands.get(command.toLowerCase());
if (chatCommand == null)
{
return false;
}
BiPredicate<ChatInput, String> input = chatCommand.getInput();
if (input == null)
{
return false;
}
return input.test(chatboxInput, message);
}
@Override
public boolean onPrivateMessageInput(PrivateMessageInput privateMessageInput)
{
final String message = privateMessageInput.getMessage();
String command = extractCommand(message);
if (command == null)
{
return false;
}
ChatCommand chatCommand = commands.get(command.toLowerCase());
if (chatCommand == null)
{
return false;
}
BiPredicate<ChatInput, String> input = chatCommand.getInput();
if (input == null)
{
return false;
}
return input.test(privateMessageInput, message);
}
private static String extractCommand(String message)
{
int idx = message.indexOf(' ');
if (idx == -1)
{
return message;
}
return message.substring(0, idx);
}
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright (c) 2017, Tomas Slusny <slusnucky@gmail.com>
* 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.chat;
import java.awt.Color;
import net.runelite.client.util.ColorUtil;
import net.runelite.client.util.Text;
public class ChatMessageBuilder
{
private final StringBuilder builder = new StringBuilder();
public ChatMessageBuilder append(final ChatColorType type)
{
builder.append("<col").append(type.name()).append(">");
return this;
}
public ChatMessageBuilder append(final Color color, final String message)
{
builder.append(ColorUtil.wrapWithColorTag(message, color));
return this;
}
public ChatMessageBuilder append(final String message)
{
builder.append(Text.escapeJagex(message));
return this;
}
public ChatMessageBuilder img(int imageId)
{
builder.append("<img=").append(imageId).append('>');
return this;
}
public String build()
{
return builder.toString();
}
}

View File

@@ -0,0 +1,636 @@
/*
* Copyright (c) 2017, Tomas Slusny <slusnucky@gmail.com>
* 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.chat;
import api.ChatLineBuffer;
import api.ChatMessageType;
import api.Client;
import api.MessageNode;
import api.Player;
import api.Varbits;
import api.events.ChatMessage;
import api.events.ConfigChanged;
import api.events.ResizeableChanged;
import api.events.ScriptCallbackEvent;
import api.events.VarbitChanged;
import com.google.common.base.MoreObjects;
import com.google.common.base.Strings;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multimap;
import java.awt.Color;
import java.util.Arrays;
import java.util.Collection;
import java.util.Objects;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicReference;
import javax.inject.Inject;
import javax.inject.Singleton;
import net.runelite.client.callback.ClientThread;
import net.runelite.client.config.ChatColorConfig;
import net.runelite.client.eventbus.Subscribe;
import net.runelite.client.ui.JagexColors;
import net.runelite.client.util.ColorUtil;
@Singleton
public class ChatMessageManager
{
private static final Set<Integer> TUTORIAL_ISLAND_REGIONS = ImmutableSet.of(12336, 12335, 12592, 12080, 12079, 12436);
private final Multimap<ChatMessageType, ChatColor> colorCache = HashMultimap.create();
private final Client client;
private final ChatColorConfig chatColorConfig;
private final ClientThread clientThread;
private int transparencyVarbit = -1;
private final Queue<QueuedMessage> queuedMessages = new ConcurrentLinkedQueue<>();
@Inject
private ChatMessageManager(
Client client,
ChatColorConfig chatColorConfig,
ClientThread clientThread)
{
this.client = client;
this.chatColorConfig = chatColorConfig;
this.clientThread = clientThread;
}
@Subscribe
public void onVarbitChanged(VarbitChanged event)
{
int setting = client.getVar(Varbits.TRANSPARENT_CHATBOX);
if (transparencyVarbit != setting)
{
transparencyVarbit = setting;
refreshAll();
}
}
@Subscribe
public void onResizeableChanged(ResizeableChanged event)
{
refreshAll();
}
@Subscribe
public void onConfigChanged(ConfigChanged event)
{
if (event.getGroup().equals("textrecolor"))
{
loadColors();
clientThread.invokeLater(this::refreshAll);
}
}
@Subscribe
public void onChatMessage(ChatMessage chatMessage)
{
MessageNode messageNode = chatMessage.getMessageNode();
ChatMessageType chatMessageType = chatMessage.getType();
boolean isChatboxTransparent = client.isResized() && client.getVar(Varbits.TRANSPARENT_CHATBOX) == 1;
Color usernameColor = null;
Color senderColor = null;
switch (chatMessageType)
{
case MODPRIVATECHAT:
case PRIVATECHAT:
case PRIVATECHATOUT:
usernameColor = isChatboxTransparent ? chatColorConfig.transparentPrivateUsernames() : chatColorConfig.opaquePrivateUsernames();
break;
case TRADEREQ:
case AUTOTYPER:
case PUBLICCHAT:
case MODCHAT:
{
boolean isFriend = client.isFriended(chatMessage.getName(), true) && !client.getLocalPlayer().getName().equals(chatMessage.getName());
if (isFriend)
{
usernameColor = isChatboxTransparent ? chatColorConfig.transparentPublicFriendUsernames() : chatColorConfig.opaquePublicFriendUsernames();
}
if (usernameColor == null)
{
usernameColor = isChatboxTransparent ? chatColorConfig.transparentUsername() : chatColorConfig.opaqueUsername();
}
break;
}
case FRIENDSCHAT:
usernameColor = isChatboxTransparent ? chatColorConfig.transparentClanUsernames() : chatColorConfig.opaqueClanUsernames();
break;
}
senderColor = isChatboxTransparent ? chatColorConfig.transparentClanChannelName() : chatColorConfig.opaqueClanChannelName();
if (usernameColor != null)
{
messageNode.setName(ColorUtil.wrapWithColorTag(messageNode.getName(), usernameColor));
}
String sender = chatMessage.getSender();
if (senderColor != null && !Strings.isNullOrEmpty(sender))
{
messageNode.setSender(ColorUtil.wrapWithColorTag(sender, senderColor));
}
final Collection<ChatColor> chatColors = colorCache.get(chatMessageType);
for (ChatColor chatColor : chatColors)
{
if (chatColor.isTransparent() != isChatboxTransparent || chatColor.getType() != ChatColorType.NORMAL || chatColor.isDefault())
{
continue;
}
messageNode.setValue(ColorUtil.wrapWithColorTag(messageNode.getValue(), chatColor.getColor()));
break;
}
}
@Subscribe
public void onScriptCallbackEvent(ScriptCallbackEvent scriptCallbackEvent)
{
final String eventName = scriptCallbackEvent.getEventName();
switch (eventName)
{
case "privateChatFrom":
case "privateChatTo":
case "privateChatSplitFrom":
case "privateChatSplitTo":
break;
default:
return;
}
boolean isChatboxTransparent = client.isResized() && client.getVar(Varbits.TRANSPARENT_CHATBOX) == 1;
Color usernameColor = isChatboxTransparent ? chatColorConfig.transparentPrivateUsernames() : chatColorConfig.opaquePrivateUsernames();
if (usernameColor == null)
{
return;
}
final String[] stringStack = client.getStringStack();
final int stringStackSize = client.getStringStackSize();
// Stack is: To/From playername :
String toFrom = stringStack[stringStackSize - 3];
stringStack[stringStackSize - 3] = ColorUtil.prependColorTag(toFrom, usernameColor);
}
private static Color getDefaultColor(ChatMessageType type, boolean transparent)
{
if (!transparent)
{
switch (type)
{
case PUBLICCHAT:
case MODCHAT:
return JagexColors.CHAT_PUBLIC_TEXT_OPAQUE_BACKGROUND;
case PRIVATECHATOUT:
case MODPRIVATECHAT:
case PRIVATECHAT:
return JagexColors.CHAT_PRIVATE_MESSAGE_TEXT_OPAQUE_BACKGROUND;
case FRIENDSCHAT:
return JagexColors.CHAT_CLAN_TEXT_OPAQUE_BACKGROUND;
case ITEM_EXAMINE:
case OBJECT_EXAMINE:
case NPC_EXAMINE:
case CONSOLE:
return JagexColors.CHAT_GAME_EXAMINE_TEXT_OPAQUE_BACKGROUND;
}
}
else
{
switch (type)
{
case PUBLICCHAT:
case MODCHAT:
return JagexColors.CHAT_PUBLIC_TEXT_TRANSPARENT_BACKGROUND;
case PRIVATECHATOUT:
case MODPRIVATECHAT:
case PRIVATECHAT:
return JagexColors.CHAT_PRIVATE_MESSAGE_TEXT_TRANSPARENT_BACKGROUND;
case FRIENDSCHAT:
return JagexColors.CHAT_CLAN_TEXT_TRANSPARENT_BACKGROUND;
case ITEM_EXAMINE:
case OBJECT_EXAMINE:
case NPC_EXAMINE:
case CONSOLE:
return JagexColors.CHAT_GAME_EXAMINE_TEXT_TRANSPARENT_BACKGROUND;
}
}
return null;
}
/**
* Load all configured colors
*/
public void loadColors()
{
colorCache.clear();
// Apply defaults
for (ChatMessageType chatMessageType : ChatMessageType.values())
{
Color defaultTransparent = getDefaultColor(chatMessageType, true);
if (defaultTransparent != null)
{
cacheColor(new ChatColor(ChatColorType.NORMAL, defaultTransparent, true, true), chatMessageType);
}
Color defaultOpaque = getDefaultColor(chatMessageType, false);
if (defaultOpaque != null)
{
cacheColor(new ChatColor(ChatColorType.NORMAL, defaultOpaque, false, true), chatMessageType);
}
}
if (chatColorConfig.opaquePublicChat() != null)
{
cacheColor(new ChatColor(ChatColorType.NORMAL, chatColorConfig.opaquePublicChat(), false),
ChatMessageType.PUBLICCHAT);
cacheColor(new ChatColor(ChatColorType.NORMAL, chatColorConfig.opaquePublicChat(), false),
ChatMessageType.MODCHAT);
}
if (chatColorConfig.opaquePublicChatHighlight() != null)
{
cacheColor(new ChatColor(ChatColorType.HIGHLIGHT, chatColorConfig.opaquePublicChatHighlight(), false),
ChatMessageType.PUBLICCHAT);
cacheColor(new ChatColor(ChatColorType.HIGHLIGHT, chatColorConfig.opaquePublicChatHighlight(), false),
ChatMessageType.MODCHAT);
}
if (chatColorConfig.opaquePrivateMessageSent() != null)
{
cacheColor(new ChatColor(ChatColorType.NORMAL, chatColorConfig.opaquePrivateMessageSent(), false),
ChatMessageType.PRIVATECHATOUT);
}
if (chatColorConfig.opaquePrivateMessageSentHighlight() != null)
{
cacheColor(new ChatColor(ChatColorType.HIGHLIGHT, chatColorConfig.opaquePrivateMessageSentHighlight(), false),
ChatMessageType.PRIVATECHATOUT);
}
if (chatColorConfig.opaquePrivateMessageReceived() != null)
{
cacheColor(new ChatColor(ChatColorType.NORMAL, chatColorConfig.opaquePrivateMessageReceived(), false),
ChatMessageType.PRIVATECHAT);
cacheColor(new ChatColor(ChatColorType.NORMAL, chatColorConfig.opaquePrivateMessageReceived(), false),
ChatMessageType.MODPRIVATECHAT);
}
if (chatColorConfig.opaquePrivateMessageReceivedHighlight() != null)
{
cacheColor(new ChatColor(ChatColorType.HIGHLIGHT, chatColorConfig.opaquePrivateMessageReceivedHighlight(), false),
ChatMessageType.PRIVATECHAT);
cacheColor(new ChatColor(ChatColorType.HIGHLIGHT, chatColorConfig.opaquePrivateMessageReceivedHighlight(), false),
ChatMessageType.MODPRIVATECHAT);
}
if (chatColorConfig.opaqueClanChatInfo() != null)
{
cacheColor(new ChatColor(ChatColorType.NORMAL, chatColorConfig.opaqueClanChatInfo(), false),
ChatMessageType.FRIENDSCHATNOTIFICATION);
}
if (chatColorConfig.opaqueClanChatInfoHighlight() != null)
{
cacheColor(new ChatColor(ChatColorType.HIGHLIGHT, chatColorConfig.opaqueClanChatInfoHighlight(), false),
ChatMessageType.FRIENDSCHATNOTIFICATION);
}
if (chatColorConfig.opaqueClanChatMessage() != null)
{
cacheColor(new ChatColor(ChatColorType.NORMAL, chatColorConfig.opaqueClanChatMessage(), false),
ChatMessageType.FRIENDSCHAT);
}
if (chatColorConfig.opaqueClanChatMessageHighlight() != null)
{
cacheColor(new ChatColor(ChatColorType.HIGHLIGHT, chatColorConfig.opaqueClanChatMessageHighlight(), false),
ChatMessageType.FRIENDSCHAT);
}
if (chatColorConfig.opaqueAutochatMessage() != null)
{
cacheColor(new ChatColor(ChatColorType.NORMAL, chatColorConfig.opaqueAutochatMessage(), false),
ChatMessageType.AUTOTYPER);
}
if (chatColorConfig.opaqueAutochatMessageHighlight() != null)
{
cacheColor(new ChatColor(ChatColorType.HIGHLIGHT, chatColorConfig.opaqueAutochatMessageHighlight(), false),
ChatMessageType.AUTOTYPER);
}
if (chatColorConfig.opaqueTradeChatMessage() != null)
{
cacheColor(new ChatColor(ChatColorType.NORMAL, chatColorConfig.opaqueTradeChatMessage(), false),
ChatMessageType.TRADEREQ);
}
if (chatColorConfig.opaqueTradeChatMessageHighlight() != null)
{
cacheColor(new ChatColor(ChatColorType.HIGHLIGHT, chatColorConfig.opaqueTradeChatMessageHighlight(), false),
ChatMessageType.TRADEREQ);
}
if (chatColorConfig.opaqueServerMessage() != null)
{
cacheColor(new ChatColor(ChatColorType.NORMAL, chatColorConfig.opaqueServerMessage(), false),
ChatMessageType.GAMEMESSAGE);
}
if (chatColorConfig.opaqueServerMessageHighlight() != null)
{
cacheColor(new ChatColor(ChatColorType.HIGHLIGHT, chatColorConfig.opaqueServerMessageHighlight(), false),
ChatMessageType.GAMEMESSAGE);
}
if (chatColorConfig.opaqueGameMessage() != null)
{
cacheColor(new ChatColor(ChatColorType.NORMAL, chatColorConfig.opaqueGameMessage(), false),
ChatMessageType.CONSOLE);
}
if (chatColorConfig.opaqueGameMessageHighlight() != null)
{
cacheColor(new ChatColor(ChatColorType.HIGHLIGHT, chatColorConfig.opaqueGameMessageHighlight(), false),
ChatMessageType.CONSOLE);
}
if (chatColorConfig.opaqueExamine() != null)
{
cacheColor(new ChatColor(ChatColorType.NORMAL, chatColorConfig.opaqueExamine(), false),
ChatMessageType.OBJECT_EXAMINE);
cacheColor(new ChatColor(ChatColorType.NORMAL, chatColorConfig.opaqueExamine(), false),
ChatMessageType.NPC_EXAMINE);
cacheColor(new ChatColor(ChatColorType.NORMAL, chatColorConfig.opaqueExamine(), false),
ChatMessageType.ITEM_EXAMINE);
}
if (chatColorConfig.opaqueExamineHighlight() != null)
{
cacheColor(new ChatColor(ChatColorType.HIGHLIGHT, chatColorConfig.opaqueExamineHighlight(), false),
ChatMessageType.OBJECT_EXAMINE);
cacheColor(new ChatColor(ChatColorType.HIGHLIGHT, chatColorConfig.opaqueExamineHighlight(), false),
ChatMessageType.NPC_EXAMINE);
cacheColor(new ChatColor(ChatColorType.HIGHLIGHT, chatColorConfig.opaqueExamineHighlight(), false),
ChatMessageType.ITEM_EXAMINE);
}
if (chatColorConfig.opaqueFiltered() != null)
{
cacheColor(new ChatColor(ChatColorType.NORMAL, chatColorConfig.opaqueFiltered(), false),
ChatMessageType.SPAM);
}
if (chatColorConfig.opaqueFilteredHighlight() != null)
{
cacheColor(new ChatColor(ChatColorType.HIGHLIGHT, chatColorConfig.opaqueFilteredHighlight(), false),
ChatMessageType.SPAM);
}
//Transparent Chat Colours
if (chatColorConfig.transparentPublicChat() != null)
{
cacheColor(new ChatColor(ChatColorType.NORMAL, chatColorConfig.transparentPublicChat(), true),
ChatMessageType.PUBLICCHAT);
cacheColor(new ChatColor(ChatColorType.NORMAL, chatColorConfig.transparentPublicChat(), true),
ChatMessageType.MODCHAT);
}
if (chatColorConfig.transparentPublicChatHighlight() != null)
{
cacheColor(new ChatColor(ChatColorType.HIGHLIGHT, chatColorConfig.transparentPublicChatHighlight(), true),
ChatMessageType.PUBLICCHAT);
cacheColor(new ChatColor(ChatColorType.HIGHLIGHT, chatColorConfig.transparentPublicChatHighlight(), true),
ChatMessageType.MODCHAT);
}
if (chatColorConfig.transparentPrivateMessageSent() != null)
{
cacheColor(new ChatColor(ChatColorType.NORMAL, chatColorConfig.transparentPrivateMessageSent(), true),
ChatMessageType.PRIVATECHATOUT);
}
if (chatColorConfig.transparentPrivateMessageSentHighlight() != null)
{
cacheColor(new ChatColor(ChatColorType.HIGHLIGHT, chatColorConfig.transparentPrivateMessageSentHighlight(), true),
ChatMessageType.PRIVATECHATOUT);
}
if (chatColorConfig.transparentPrivateMessageReceived() != null)
{
cacheColor(new ChatColor(ChatColorType.NORMAL, chatColorConfig.transparentPrivateMessageReceived(), true),
ChatMessageType.PRIVATECHAT);
cacheColor(new ChatColor(ChatColorType.NORMAL, chatColorConfig.transparentPrivateMessageReceived(), true),
ChatMessageType.MODPRIVATECHAT);
}
if (chatColorConfig.transparentPrivateMessageReceivedHighlight() != null)
{
cacheColor(new ChatColor(ChatColorType.HIGHLIGHT, chatColorConfig.transparentPrivateMessageReceivedHighlight(), true),
ChatMessageType.PRIVATECHAT);
cacheColor(new ChatColor(ChatColorType.HIGHLIGHT, chatColorConfig.transparentPrivateMessageReceivedHighlight(), true),
ChatMessageType.MODPRIVATECHAT);
}
if (chatColorConfig.transparentClanChatInfo() != null)
{
cacheColor(new ChatColor(ChatColorType.NORMAL, chatColorConfig.transparentClanChatInfo(), true),
ChatMessageType.FRIENDSCHATNOTIFICATION);
}
if (chatColorConfig.transparentClanChatInfoHighlight() != null)
{
cacheColor(new ChatColor(ChatColorType.HIGHLIGHT, chatColorConfig.transparentClanChatInfoHighlight(), true),
ChatMessageType.FRIENDSCHATNOTIFICATION);
}
if (chatColorConfig.transparentClanChatMessage() != null)
{
cacheColor(new ChatColor(ChatColorType.NORMAL, chatColorConfig.transparentClanChatMessage(), true),
ChatMessageType.FRIENDSCHAT);
}
if (chatColorConfig.transparentClanChatMessageHighlight() != null)
{
cacheColor(new ChatColor(ChatColorType.HIGHLIGHT, chatColorConfig.transparentClanChatMessageHighlight(), true),
ChatMessageType.FRIENDSCHAT);
}
if (chatColorConfig.transparentAutochatMessage() != null)
{
cacheColor(new ChatColor(ChatColorType.NORMAL, chatColorConfig.transparentAutochatMessage(), true),
ChatMessageType.AUTOTYPER);
}
if (chatColorConfig.transparentAutochatMessageHighlight() != null)
{
cacheColor(new ChatColor(ChatColorType.HIGHLIGHT, chatColorConfig.transparentAutochatMessageHighlight(), true),
ChatMessageType.AUTOTYPER);
}
if (chatColorConfig.transparentTradeChatMessage() != null)
{
cacheColor(new ChatColor(ChatColorType.NORMAL, chatColorConfig.transparentTradeChatMessage(), true),
ChatMessageType.TRADEREQ);
}
if (chatColorConfig.transparentTradeChatMessageHighlight() != null)
{
cacheColor(new ChatColor(ChatColorType.HIGHLIGHT, chatColorConfig.transparentTradeChatMessageHighlight(), true),
ChatMessageType.TRADEREQ);
}
if (chatColorConfig.transparentServerMessage() != null)
{
cacheColor(new ChatColor(ChatColorType.NORMAL, chatColorConfig.transparentServerMessage(), true),
ChatMessageType.GAMEMESSAGE);
}
if (chatColorConfig.transparentServerMessageHighlight() != null)
{
cacheColor(new ChatColor(ChatColorType.HIGHLIGHT, chatColorConfig.transparentServerMessageHighlight(), true),
ChatMessageType.GAMEMESSAGE);
}
if (chatColorConfig.transparentGameMessage() != null)
{
cacheColor(new ChatColor(ChatColorType.NORMAL, chatColorConfig.transparentGameMessage(), true),
ChatMessageType.CONSOLE);
}
if (chatColorConfig.transparentGameMessageHighlight() != null)
{
cacheColor(new ChatColor(ChatColorType.HIGHLIGHT, chatColorConfig.transparentGameMessageHighlight(), true),
ChatMessageType.CONSOLE);
}
if (chatColorConfig.transparentExamine() != null)
{
cacheColor(new ChatColor(ChatColorType.NORMAL, chatColorConfig.transparentExamine(), true),
ChatMessageType.OBJECT_EXAMINE);
cacheColor(new ChatColor(ChatColorType.NORMAL, chatColorConfig.transparentExamine(), true),
ChatMessageType.NPC_EXAMINE);
cacheColor(new ChatColor(ChatColorType.NORMAL, chatColorConfig.transparentExamine(), true),
ChatMessageType.ITEM_EXAMINE);
}
if (chatColorConfig.transparentExamineHighlight() != null)
{
cacheColor(new ChatColor(ChatColorType.HIGHLIGHT, chatColorConfig.transparentExamineHighlight(), true),
ChatMessageType.OBJECT_EXAMINE);
cacheColor(new ChatColor(ChatColorType.HIGHLIGHT, chatColorConfig.transparentExamineHighlight(), true),
ChatMessageType.NPC_EXAMINE);
cacheColor(new ChatColor(ChatColorType.HIGHLIGHT, chatColorConfig.transparentExamineHighlight(), true),
ChatMessageType.ITEM_EXAMINE);
}
if (chatColorConfig.transparentFiltered() != null)
{
cacheColor(new ChatColor(ChatColorType.NORMAL, chatColorConfig.transparentFiltered(), true),
ChatMessageType.SPAM);
}
if (chatColorConfig.transparentFilteredHighlight() != null)
{
cacheColor(new ChatColor(ChatColorType.HIGHLIGHT, chatColorConfig.transparentFilteredHighlight(), true),
ChatMessageType.SPAM);
}
}
private void cacheColor(final ChatColor chatColor, final ChatMessageType... types)
{
for (ChatMessageType chatMessageType : types)
{
// color is excluded from equals/hashCode on ChatColor
colorCache.remove(chatMessageType, chatColor);
colorCache.put(chatMessageType, chatColor);
}
}
public void queue(QueuedMessage message)
{
queuedMessages.add(message);
}
public void process()
{
if (!queuedMessages.isEmpty())
{
try
{
queuedMessages.forEach(this::add);
}
finally
{
queuedMessages.clear();
}
}
}
private void add(QueuedMessage message)
{
// Do not send message if the player is on tutorial island
final Player player = client.getLocalPlayer();
if (player != null && TUTORIAL_ISLAND_REGIONS.contains(player.getWorldLocation().getRegionID()))
{
return;
}
// this updates chat cycle
client.addChatMessage(
message.getType(),
MoreObjects.firstNonNull(message.getName(), ""),
MoreObjects.firstNonNull(message.getValue(), message.getRuneLiteFormattedMessage()),
message.getSender());
// Get last message from line buffer (the one we just added)
final ChatLineBuffer chatLineBuffer = client.getChatLineMap().get(message.getType().getType());
final MessageNode[] lines = chatLineBuffer.getLines();
final MessageNode line = lines[0];
// Update the message with RuneLite additions
line.setRuneLiteFormatMessage(message.getRuneLiteFormattedMessage());
line.setTimestamp(message.getTimestamp());
update(line);
}
public void update(final MessageNode target)
{
if (Strings.isNullOrEmpty(target.getRuneLiteFormatMessage()))
{
return;
}
final boolean transparent = client.isResized() && transparencyVarbit != 0;
final Collection<ChatColor> chatColors = colorCache.get(target.getType());
// If we do not have any colors cached, simply set clean message
if (chatColors == null || chatColors.isEmpty())
{
target.setValue(target.getRuneLiteFormatMessage());
return;
}
target.setValue(recolorMessage(transparent, target.getRuneLiteFormatMessage(), target.getType()));
}
private String recolorMessage(boolean transparent, String message, ChatMessageType messageType)
{
final Collection<ChatColor> chatColors = colorCache.get(messageType);
final AtomicReference<String> resultMessage = new AtomicReference<>(message);
// Replace custom formatting with actual colors
chatColors.stream()
.filter(chatColor -> chatColor.isTransparent() == transparent)
.forEach(chatColor ->
resultMessage.getAndUpdate(oldMessage -> oldMessage.replaceAll(
"<col" + chatColor.getType().name() + ">",
ColorUtil.colorTag(chatColor.getColor()))));
return resultMessage.get();
}
private void refreshAll()
{
client.getChatLineMap().values().stream()
.filter(Objects::nonNull)
.flatMap(clb -> Arrays.stream(clb.getLines()))
.filter(Objects::nonNull)
.forEach(this::update);
client.refreshChat();
}
}

View File

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

View File

@@ -0,0 +1,217 @@
/*
* Copyright (c) 2018, Kamiel
* Copyright (c) 2018, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.client.chat;
import api.Client;
import api.ScriptID;
import api.VarClientStr;
import api.events.CommandExecuted;
import api.events.ScriptCallbackEvent;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Singleton;
import lombok.extern.slf4j.Slf4j;
import net.runelite.client.callback.ClientThread;
import net.runelite.client.eventbus.EventBus;
import net.runelite.client.eventbus.Subscribe;
import net.runelite.client.events.ChatboxInput;
import net.runelite.client.events.PrivateMessageInput;
@Slf4j
@Singleton
public class CommandManager
{
private static final String RUNELITE_COMMAND = "runeliteCommand";
private static final String CHATBOX_INPUT = "chatboxInput";
private static final String PRIVMATE_MESSAGE = "privateMessage";
private final Client client;
private final EventBus eventBus;
private final ClientThread clientThread;
private boolean sending;
private final List<ChatboxInputListener> chatboxInputListenerList = new ArrayList<>();
@Inject
private CommandManager(Client client, EventBus eventBus, ClientThread clientThread)
{
this.client = client;
this.eventBus = eventBus;
this.clientThread = clientThread;
}
public void register(ChatboxInputListener chatboxInputListener)
{
chatboxInputListenerList.add(chatboxInputListener);
}
public void unregister(ChatboxInputListener chatboxInputListener)
{
chatboxInputListenerList.remove(chatboxInputListener);
}
@Subscribe
private void onScriptCallbackEvent(ScriptCallbackEvent event)
{
if (sending)
{
return;
}
switch (event.getEventName())
{
case RUNELITE_COMMAND:
runCommand();
break;
case CHATBOX_INPUT:
handleInput(event);
break;
case PRIVMATE_MESSAGE:
handlePrivateMessage(event);
break;
}
}
private void runCommand()
{
String typedText = client.getVar(VarClientStr.CHATBOX_TYPED_TEXT).substring(2); // strip ::
log.debug("Command: {}", typedText);
String[] split = typedText.split(" ");
// Fixes ArrayIndexOutOfBounds when typing ":: "
if (split.length == 0)
{
return;
}
String command = split[0];
String[] args = Arrays.copyOfRange(split, 1, split.length);
CommandExecuted commandExecuted = new CommandExecuted(command, args);
eventBus.post(commandExecuted);
}
private void handleInput(ScriptCallbackEvent event)
{
final String[] stringStack = client.getStringStack();
final int[] intStack = client.getIntStack();
int stringStackCount = client.getStringStackSize();
int intStackCount = client.getIntStackSize();
final String typedText = stringStack[stringStackCount - 1];
final int chatType = intStack[intStackCount - 1];
ChatboxInput chatboxInput = new ChatboxInput(typedText, chatType)
{
private boolean resumed;
@Override
public void resume()
{
if (resumed)
{
return;
}
resumed = true;
clientThread.invoke(() -> sendChatboxInput(chatType, typedText));
}
};
boolean stop = false;
for (ChatboxInputListener chatboxInputListener : chatboxInputListenerList)
{
stop |= chatboxInputListener.onChatboxInput(chatboxInput);
}
if (stop)
{
// input was blocked.
stringStack[stringStackCount - 1] = ""; // prevent script from sending
}
}
private void handlePrivateMessage(ScriptCallbackEvent event)
{
final String[] stringStack = client.getStringStack();
final int[] intStack = client.getIntStack();
int stringStackCount = client.getStringStackSize();
int intStackCount = client.getIntStackSize();
final String target = stringStack[stringStackCount - 2];
final String message = stringStack[stringStackCount - 1];
PrivateMessageInput privateMessageInput = new PrivateMessageInput(target, message)
{
private boolean resumed;
@Override
public void resume()
{
if (resumed)
{
return;
}
resumed = true;
clientThread.invoke(() -> sendPrivmsg(target, message));
}
};
boolean stop = false;
for (ChatboxInputListener chatboxInputListener : chatboxInputListenerList)
{
stop |= chatboxInputListener.onPrivateMessageInput(privateMessageInput);
}
if (stop)
{
intStack[intStackCount - 1] = 1;
client.setStringStackSize(stringStackCount - 2); // remove both target and message
}
}
private void sendChatboxInput(int chatType, String input)
{
sending = true;
try
{
client.runScript(ScriptID.CHATBOX_INPUT, chatType, input);
}
finally
{
sending = false;
}
}
private void sendPrivmsg(String target, String message)
{
client.runScript(ScriptID.PRIVMSG, target, message);
}
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright (c) 2016-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.client.chat;
import api.ChatMessageType;
import lombok.Builder;
import lombok.Data;
import lombok.NonNull;
@Data
@Builder
public class QueuedMessage
{
@NonNull
private final ChatMessageType type;
private final String value;
private String name;
private String sender;
private String runeLiteFormattedMessage;
private int timestamp;
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright (c) 2018, Ron Young <https://github.com/raiyni>
* 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.config;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Used with ConfigItem, determines if to use alpha slider on colors
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface Alpha
{
}

View File

@@ -0,0 +1,512 @@
/*
* Copyright (c) 2018, Hydrox6 <ikada@protonmail.ch>
* 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.config;
import java.awt.Color;
import net.runelite.client.ui.JagexColors;
@ConfigGroup("textrecolor")
public interface ChatColorConfig extends Config
{
@ConfigItem(
position = 31,
keyName = "opaquePublicChat",
name = "Public chat",
description = "Color of Public chat"
)
Color opaquePublicChat();
@ConfigItem(
position = 32,
keyName = "opaquePublicChatHighlight",
name = "Public chat highlight",
description = "Color of highlights in Public chat"
)
default Color opaquePublicChatHighlight()
{
return Color.decode("#000000");
}
@ConfigItem(
position = 33,
keyName = "opaquePrivateMessageSent",
name = "Sent private messages",
description = "Color of Private messages you've sent"
)
Color opaquePrivateMessageSent();
@ConfigItem(
position = 34,
keyName = "opaquePrivateMessageSentHighlight",
name = "Sent private messages highlight",
description = "Color of highlights in Private messages you've sent"
)
default Color opaquePrivateMessageSentHighlight()
{
return Color.decode("#002783");
}
@ConfigItem(
position = 35,
keyName = "opaquePrivateMessageReceived",
name = "Received private messages",
description = "Color of Private messages you've received"
)
Color opaquePrivateMessageReceived();
@ConfigItem(
position = 36,
keyName = "opaquePrivateMessageReceivedHighlight",
name = "Received private messages highlight",
description = "Color of highlights in Private messages you've received"
)
default Color opaquePrivateMessageReceivedHighlight()
{
return Color.decode("#002783");
}
@ConfigItem(
position = 37,
keyName = "opaqueClanChatInfo",
name = "Clan chat info",
description = "Clan Chat Information (eg. when joining a channel)"
)
default Color opaqueClanChatInfo()
{
return JagexColors.CHAT_GAME_EXAMINE_TEXT_OPAQUE_BACKGROUND;
}
@ConfigItem(
position = 38,
keyName = "opaqueClanChatInfoHighlight",
name = "Clan chat info highlight",
description = "Clan Chat Information highlight (used for the Raids plugin)"
)
default Color opaqueClanChatInfoHighlight()
{
return Color.RED;
}
@ConfigItem(
position = 39,
keyName = "opaqueClanChatMessage",
name = "Clan chat message",
description = "Color of Clan Chat Messages"
)
Color opaqueClanChatMessage();
@ConfigItem(
position = 40,
keyName = "opaqueClanChatMessageHighlight",
name = "Clan chat message highlight",
description = "Color of highlights in Clan Chat Messages"
)
default Color opaqueClanChatMessageHighlight()
{
return Color.decode("#000000");
}
@ConfigItem(
position = 41,
keyName = "opaqueAutochatMessage",
name = "Autochat",
description = "Color of Autochat messages"
)
Color opaqueAutochatMessage();
@ConfigItem(
position = 42,
keyName = "opaqueAutochatMessageHighlight",
name = "Autochat highlight",
description = "Color of highlights in Autochat messages"
)
Color opaqueAutochatMessageHighlight();
@ConfigItem(
position = 43,
keyName = "opaqueTradeChatMessage",
name = "Trade chat",
description = "Color of Trade Chat Messages"
)
Color opaqueTradeChatMessage();
@ConfigItem(
position = 44,
keyName = "opaqueTradeChatMessageHighlight",
name = "Trade chat highlight",
description = "Color of highlights in Trade Chat Messages"
)
Color opaqueTradeChatMessageHighlight();
@ConfigItem(
position = 45,
keyName = "opaqueServerMessage",
name = "Server message",
description = "Color of Server Messages (eg. 'Welcome to Runescape')"
)
Color opaqueServerMessage();
@ConfigItem(
position = 46,
keyName = "opaqueServerMessageHighlight",
name = "Server message highlight",
description = "Color of highlights in Server Messages"
)
Color opaqueServerMessageHighlight();
@ConfigItem(
position = 47,
keyName = "opaqueGameMessage",
name = "Game message",
description = "Color of Game Messages"
)
Color opaqueGameMessage();
@ConfigItem(
position = 48,
keyName = "opaqueGameMessageHighlight",
name = "Game message highlight",
description = "Color of highlights in Game Messages"
)
default Color opaqueGameMessageHighlight()
{
return Color.decode("#EF1020");
}
@ConfigItem(
position = 49,
keyName = "opaqueExamine",
name = "Examine",
description = "Color of Examine Text"
)
Color opaqueExamine();
@ConfigItem(
position = 50,
keyName = "opaqueExamineHighlight",
name = "Examine highlight",
description = "Color of highlights in Examine Text"
)
default Color opaqueExamineHighlight()
{
return Color.decode("#0000FF");
}
@ConfigItem(
position = 51,
keyName = "opaqueFiltered",
name = "Filtered",
description = "Color of Filtered Text (messages that aren't shown when Game messages are filtered)"
)
Color opaqueFiltered();
@ConfigItem(
position = 52,
keyName = "opaqueFilteredHighlight",
name = "Filtered highlight",
description = "Color of highlights in Filtered Text"
)
Color opaqueFilteredHighlight();
@ConfigItem(
position = 53,
keyName = "opaqueUsername",
name = "Usernames",
description = "Color of Usernames"
)
Color opaqueUsername();
@ConfigItem(
position = 54,
keyName = "opaquePrivateUsernames",
name = "Private chat usernames",
description = "Color of Usernames in Private Chat"
)
Color opaquePrivateUsernames();
@ConfigItem(
position = 55,
keyName = "opaqueClanChannelName",
name = "Clan channel name",
description = "Color of Clan Channel Name"
)
Color opaqueClanChannelName();
@ConfigItem(
position = 56,
keyName = "opaqueClanUsernames",
name = "Clan usernames",
description = "Color of Usernames in Clan Chat"
)
Color opaqueClanUsernames();
@ConfigItem(
position = 57,
keyName = "opaquePublicFriendUsernames",
name = "Public friend usernames",
description = "Color of Friend Usernames in Public Chat"
)
Color opaquePublicFriendUsernames();
@ConfigItem(
position = 61,
keyName = "transparentPublicChat",
name = "Public chat (transparent)",
description = "Color of Public chat (transparent)"
)
Color transparentPublicChat();
@ConfigItem(
position = 62,
keyName = "transparentPublicChatHighlight",
name = "Public chat highlight (transparent)",
description = "Color of highlights in Public chat (transparent)"
)
default Color transparentPublicChatHighlight()
{
return Color.decode("#FFFFFF");
}
@ConfigItem(
position = 63,
keyName = "transparentPrivateMessageSent",
name = "Sent private messages (transparent)",
description = "Color of Private messages you've sent (transparent)"
)
Color transparentPrivateMessageSent();
@ConfigItem(
position = 64,
keyName = "transparentPrivateMessageSentHighlight",
name = "Sent private messages highlight (transparent)",
description = "Color of highlights in Private messages you've sent (transparent)"
)
default Color transparentPrivateMessageSentHighlight()
{
return Color.decode("#FFFFFF");
}
@ConfigItem(
position = 65,
keyName = "transparentPrivateMessageReceived",
name = "Received private messages (transparent)",
description = "Color of Private messages you've received (transparent)"
)
Color transparentPrivateMessageReceived();
@ConfigItem(
position = 66,
keyName = "transparentPrivateMessageReceivedHighlight",
name = "Received private messages highlight (transparent)",
description = "Color of highlights in Private messages you've received (transparent)"
)
default Color transparentPrivateMessageReceivedHighlight()
{
return Color.decode("#FFFFFF");
}
@ConfigItem(
position = 67,
keyName = "transparentClanChatInfo",
name = "Clan chat info (transparent)",
description = "Clan Chat Information (eg. when joining a channel) (transparent)"
)
default Color transparentClanChatInfo()
{
return JagexColors.CHAT_GAME_EXAMINE_TEXT_TRANSPARENT_BACKGROUND;
}
@ConfigItem(
position = 68,
keyName = "transparentClanChatInfoHighlight",
name = "Clan chat info highlight (transparent)",
description = "Clan Chat Information highlight (used for the Raids plugin) (transparent)"
)
default Color transparentClanChatInfoHighlight()
{
return Color.RED;
}
@ConfigItem(
position = 69,
keyName = "transparentClanChatMessage",
name = "Clan chat message (transparent)",
description = "Color of Clan Chat Messages (transparent)"
)
Color transparentClanChatMessage();
@ConfigItem(
position = 70,
keyName = "transparentClanChatMessageHighlight",
name = "Clan chat message highlight (transparent)",
description = "Color of highlights in Clan Chat Messages (transparent)"
)
default Color transparentClanChatMessageHighlight()
{
return Color.decode("#FFFFFF");
}
@ConfigItem(
position = 71,
keyName = "transparentAutochatMessage",
name = "Autochat (transparent)",
description = "Color of Autochat messages (transparent)"
)
Color transparentAutochatMessage();
@ConfigItem(
position = 72,
keyName = "transparentAutochatMessageHighlight",
name = "Autochat highlight (transparent)",
description = "Color of highlights in Autochat messages (transparent)"
)
Color transparentAutochatMessageHighlight();
@ConfigItem(
position = 73,
keyName = "transparentTradeChatMessage",
name = "Trade chat (transparent)",
description = "Color of Trade Chat Messages (transparent)"
)
Color transparentTradeChatMessage();
@ConfigItem(
position = 74,
keyName = "transparentTradeChatMessageHighlight",
name = "Trade chat highlight (transparent)",
description = "Color of highlights in Trade Chat Messages (transparent)"
)
Color transparentTradeChatMessageHighlight();
@ConfigItem(
position = 75,
keyName = "transparentServerMessage",
name = "Server message (transparent)",
description = "Color of Server Messages (eg. 'Welcome to Runescape') (transparent)"
)
Color transparentServerMessage();
@ConfigItem(
position = 76,
keyName = "transparentServerMessageHighlight",
name = "Server message highlight (transparent)",
description = "Color of highlights in Server Messages (transparent)"
)
Color transparentServerMessageHighlight();
@ConfigItem(
position = 77,
keyName = "transparentGameMessage",
name = "Game message (transparent)",
description = "Color of Game Messages (transparent)"
)
Color transparentGameMessage();
@ConfigItem(
position = 78,
keyName = "transparentGameMessageHighlight",
name = "Game message highlight (transparent)",
description = "Color of highlights in Game Messages (transparent)"
)
default Color transparentGameMessageHighlight()
{
return Color.decode("#EF1020");
}
@ConfigItem(
position = 79,
keyName = "transparentExamine",
name = "Examine (transparent)",
description = "Color of Examine Text (transparent)"
)
Color transparentExamine();
@ConfigItem(
position = 80,
keyName = "transparentExamineHighlight",
name = "Examine highlight (transparent)",
description = "Color of highlights in Examine Text (transparent)"
)
default Color transparentExamineHighlight()
{
return Color.decode("#0000FF");
}
@ConfigItem(
position = 81,
keyName = "transparentFiltered",
name = "Filtered (transparent)",
description = "Color of Filtered Text (messages that aren't shown when Game messages are filtered) (transparent)"
)
Color transparentFiltered();
@ConfigItem(
position = 82,
keyName = "transparentFilteredHighlight",
name = "Filtered highlight (transparent)",
description = "Color of highlights in Filtered Text (transparent)"
)
Color transparentFilteredHighlight();
@ConfigItem(
position = 83,
keyName = "transparentUsername",
name = "Usernames (transparent)",
description = "Color of Usernames (transparent)"
)
Color transparentUsername();
@ConfigItem(
position = 84,
keyName = "transparentPrivateUsernames",
name = "Private chat usernames (transparent)",
description = "Color of Usernames in Private Chat (transparent)"
)
Color transparentPrivateUsernames();
@ConfigItem(
position = 85,
keyName = "transparentClanChannelName",
name = "Clan channel name (transparent)",
description = "Color of Clan Channel Name (transparent)"
)
Color transparentClanChannelName();
@ConfigItem(
position = 86,
keyName = "transparentClanUsernames",
name = "Clan usernames (transparent)",
description = "Color of Usernames in Clan Chat (transparent)"
)
Color transparentClanUsernames();
@ConfigItem(
position = 87,
keyName = "transparentPublicFriendUsernames",
name = "Public friend usernames (transparent)",
description = "Color of Friend Usernames in Public Chat (transparent)"
)
Color transparentPublicFriendUsernames();
}

View File

@@ -0,0 +1,29 @@
/*
* 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.client.config;
public interface Config
{
}

View File

@@ -0,0 +1,64 @@
/*
* 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.client.config;
import java.util.ArrayList;
import java.util.Collection;
public class ConfigDescriptor
{
private final ConfigGroup group;
private final Collection<ConfigItemsGroup> itemGroups;
public ConfigDescriptor(ConfigGroup group, Collection<ConfigItemsGroup> itemGroups)
{
this.group = group;
this.itemGroups = itemGroups;
}
public ConfigGroup getGroup()
{
return group;
}
public Collection<ConfigItemsGroup> getItemGroups()
{
return itemGroups;
}
public Collection<ConfigItemDescriptor> getItems()
{
Collection<ConfigItemDescriptor> allItems = new ArrayList<>();
for (ConfigItemsGroup g : itemGroups)
{
for (ConfigItemDescriptor item : g.getItems())
{
allItems.add(item);
}
}
return allItems;
}
}

View File

@@ -0,0 +1,43 @@
/*
* 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.client.config;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ConfigGroup
{
/**
* The key name of the config group used for storing configuration within the config group.
* This should typically be a lowercased version of your plugin name, with all spaces removed.
* <p>
* For example, the {@code Grand Exchange} plugin uses the key name {@code grandexchange}.
*/
String value();
}

View File

@@ -0,0 +1,157 @@
/*
* 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.client.config;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Objects;
import lombok.extern.slf4j.Slf4j;
@Slf4j
class ConfigInvocationHandler implements InvocationHandler
{
private final ConfigManager manager;
public ConfigInvocationHandler(ConfigManager manager)
{
this.manager = manager;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
{
Class<?> iface = proxy.getClass().getInterfaces()[0];
ConfigGroup group = iface.getAnnotation(ConfigGroup.class);
ConfigItem item = method.getAnnotation(ConfigItem.class);
if (group == null)
{
log.warn("Configuration proxy class {} has no @ConfigGroup!", proxy.getClass());
return null;
}
if (item == null)
{
log.warn("Configuration method {} has no @ConfigItem!", method);
return null;
}
if (args == null)
{
// Getting configuration item
String value = manager.getConfiguration(group.value(), item.keyName());
if (value == null)
{
if (method.isDefault())
{
return callDefaultMethod(proxy, method, null);
}
return null;
}
// Convert value to return type
Class<?> returnType = method.getReturnType();
try
{
return ConfigManager.stringToObject(value, returnType);
}
catch (Exception e)
{
log.warn("Unable to unmarshal {}.{} ", group.value(), item.keyName(), e);
if (method.isDefault())
{
Object defaultValue = callDefaultMethod(proxy, method, null);
manager.setConfiguration(group.value(), item.keyName(), defaultValue);
return defaultValue;
}
return null;
}
}
else
{
// Setting a configuration value
if (args.length != 1)
{
throw new RuntimeException("Invalid number of arguents to configuration method");
}
Object newValue = args[0];
Class<?> type = method.getParameterTypes()[0];
Object oldValue = manager.getConfiguration(group.value(), item.keyName(), type);
if (Objects.equals(oldValue, newValue))
{
// nothing to do
return null;
}
if (method.isDefault())
{
Object defaultValue = callDefaultMethod(proxy, method, args);
if (Objects.equals(newValue, defaultValue))
{
// Just unset if it goes back to the default
manager.unsetConfiguration(group.value(), item.keyName());
return null;
}
}
if (newValue == null)
{
manager.unsetConfiguration(group.value(), item.keyName());
}
else
{
String newValueStr = ConfigManager.objectToString(newValue);
manager.setConfiguration(group.value(), item.keyName(), newValueStr);
}
return null;
}
}
static Object callDefaultMethod(Object proxy, Method method, Object[] args) throws Throwable
{
// Call the default method implementation - https://rmannibucau.wordpress.com/2014/03/27/java-8-default-interface-methods-and-jdk-dynamic-proxies/
Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class, int.class);
constructor.setAccessible(true);
Class<?> declaringClass = method.getDeclaringClass();
return constructor.newInstance(declaringClass, MethodHandles.Lookup.PUBLIC | MethodHandles.Lookup.PRIVATE)
.unreflectSpecial(method, declaringClass)
.bindTo(proxy)
.invokeWithArguments(args);
}
}

View File

@@ -0,0 +1,66 @@
/*
* 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.client.config;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ConfigItem
{
int position() default -1;
String keyName();
String name();
String description();
boolean hidden() default false;
String warning() default "";
boolean secret() default false;
String group() default "";
String unhide() default "";
String hide() default "";
String parent() default "";
String enabledBy() default "";
String disabledBy() default "";
boolean parse() default false;
Class<?> clazz() default void.class;
String method() default "";
}

View File

@@ -0,0 +1,36 @@
/*
* 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.client.config;
import lombok.Value;
@Value
public class ConfigItemDescriptor
{
private final ConfigItem item;
private final Class<?> type;
private final Range range;
private final Alpha alpha;
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright (c) 2018, Craftiii4 <craftiii4@gmail.com>
* 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.config;
import java.util.ArrayList;
import java.util.Collection;
import lombok.AccessLevel;
import lombok.Getter;
public class ConfigItemsGroup
{
@Getter(AccessLevel.PUBLIC)
private final String group;
@Getter(AccessLevel.PUBLIC)
private Collection<ConfigItemDescriptor> items;
public ConfigItemsGroup(String group)
{
this.group = group;
this.items = new ArrayList<>();
}
public void addItem(ConfigItemDescriptor item)
{
items.add(item);
}
}

View File

@@ -0,0 +1,628 @@
/*
* 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.client.config;
import api.coords.WorldPoint;
import api.events.ConfigChanged;
import com.google.common.base.Strings;
import com.google.common.collect.ComparisonChain;
import com.google.common.collect.ImmutableMap;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.Rectangle;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.nio.channels.FileLock;
import java.nio.charset.Charset;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import javax.inject.Inject;
import javax.inject.Singleton;
import lombok.extern.slf4j.Slf4j;
import net.runelite.client.RuneLite;
import static net.runelite.client.RuneLite.PROFILES_DIR;
import net.runelite.client.eventbus.EventBus;
import net.runelite.client.util.ColorUtil;
@Singleton
@Slf4j
public class ConfigManager
{
private static final String SETTINGS_FILE_NAME = "runeliteplus.properties";
private static final String STANDARD_SETTINGS_FILE_NAME = "settings.properties";
private static final File SETTINGS_FILE = new File(RuneLite.RUNELITE_DIR, SETTINGS_FILE_NAME);
private static final File STANDARD_SETTINGS_FILE = new File(RuneLite.RUNELITE_DIR, STANDARD_SETTINGS_FILE_NAME);
@Inject
EventBus eventBus;
private final ScheduledExecutorService executor;
private final ConfigInvocationHandler handler = new ConfigInvocationHandler(this);
private final Properties properties = new Properties();
private final Map<String, String> pendingChanges = new HashMap<>();
@Inject
public ConfigManager(ScheduledExecutorService scheduledExecutorService)
{
this.executor = scheduledExecutorService;
executor.scheduleWithFixedDelay(this::sendConfig, 30, 30, TimeUnit.SECONDS);
}
public final void switchSession()
{
// Ensure existing config is saved
load();
}
public void load()
{
loadFromFile();
}
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()
{
log.info("Nothing changed, don't worry!");
}
private synchronized void loadFromFile()
{
properties.clear();
try (FileInputStream in = new FileInputStream(SETTINGS_FILE))
{
properties.load(new InputStreamReader(in, Charset.forName("UTF-8")));
}
catch (FileNotFoundException ex)
{
log.debug("Unable to load settings - no such file, syncing from standard settings");
syncLastModified();
}
catch (IllegalArgumentException | IOException ex)
{
log.warn("Unable to load settings", ex);
}
try
{
Map<String, String> copy = (Map) ImmutableMap.copyOf(properties);
copy.forEach((groupAndKey, value) ->
{
final String[] split = groupAndKey.split("\\.", 2);
if (split.length != 2)
{
log.debug("Properties key malformed!: {}", groupAndKey);
properties.remove(groupAndKey);
return;
}
final String groupName = split[0];
final String key = split[1];
ConfigChanged configChanged = new ConfigChanged();
configChanged.setGroup(groupName);
configChanged.setKey(key);
configChanged.setOldValue(null);
configChanged.setNewValue(value);
eventBus.post(configChanged);
});
}
catch (Exception ex)
{
log.warn("Error posting config events", ex);
}
}
private void saveToFile() throws IOException
{
ConfigManager.SETTINGS_FILE.getParentFile().mkdirs();
try (FileOutputStream out = new FileOutputStream(ConfigManager.SETTINGS_FILE))
{
final FileLock lock = out.getChannel().lock();
try
{
properties.store(new OutputStreamWriter(out, Charset.forName("UTF-8")), "RuneLite configuration");
}
finally
{
lock.release();
}
}
}
public <T> T getConfig(Class<T> clazz)
{
if (!Modifier.isPublic(clazz.getModifiers()))
{
throw new RuntimeException("Non-public configuration classes can't have default methods invoked");
}
T t = (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class<?>[]
{
clazz
}, handler);
return t;
}
public List<String> getConfigurationKeys(String prefix)
{
return properties.keySet().stream().filter(v -> ((String) v).startsWith(prefix)).map(String.class::cast).collect(Collectors.toList());
}
public String getConfiguration(String groupName, String key)
{
return properties.getProperty(groupName + "." + key);
}
public <T> T getConfiguration(String groupName, String key, Class<T> clazz)
{
String value = getConfiguration(groupName, key);
if (!Strings.isNullOrEmpty(value))
{
try
{
return (T) stringToObject(value, clazz);
}
catch (Exception e)
{
log.warn("Unable to unmarshal {}.{} ", groupName, key, e);
}
}
return null;
}
public void setConfiguration(String groupName, String key, String value)
{
String oldValue = (String) properties.setProperty(groupName + "." + key, value);
if (Objects.equals(oldValue, value))
{
return;
}
log.debug("Setting configuration value for {}.{} to {}", groupName, key, value);
synchronized (pendingChanges)
{
pendingChanges.put(groupName + "." + key, value);
}
ConfigChanged configChanged = new ConfigChanged();
configChanged.setGroup(groupName);
configChanged.setKey(key);
configChanged.setOldValue(oldValue);
configChanged.setNewValue(value);
eventBus.post(configChanged);
}
public void setConfiguration(String groupName, String key, Object value)
{
setConfiguration(groupName, key, objectToString(value));
}
public void unsetConfiguration(String groupName, String key)
{
String oldValue = (String) properties.remove(groupName + "." + key);
if (oldValue == null)
{
return;
}
log.debug("Unsetting configuration value for {}.{}", groupName, key);
synchronized (pendingChanges)
{
pendingChanges.put(groupName + "." + key, null);
}
ConfigChanged configChanged = new ConfigChanged();
configChanged.setGroup(groupName);
configChanged.setKey(key);
configChanged.setOldValue(oldValue);
eventBus.post(configChanged);
}
public ConfigDescriptor getConfigDescriptor(Object configurationProxy)
{
Class<?> inter = configurationProxy.getClass().getInterfaces()[0];
ConfigGroup group = inter.getAnnotation(ConfigGroup.class);
if (group == null)
{
throw new IllegalArgumentException("Not a config group");
}
final List<ConfigItemDescriptor> items = Arrays.stream(inter.getMethods())
.filter(m -> m.getParameterCount() == 0)
.map(m -> new ConfigItemDescriptor(
m.getDeclaredAnnotation(ConfigItem.class),
m.getReturnType(),
m.getDeclaredAnnotation(Range.class),
m.getDeclaredAnnotation(Alpha.class)
))
.sorted((a, b) -> ComparisonChain.start()
.compare(a.getItem().position(), b.getItem().position())
.compare(a.getItem().name(), b.getItem().name())
.result())
.collect(Collectors.toList());
Collection<ConfigItemsGroup> itemGroups = new ArrayList<>();
for (ConfigItemDescriptor item : items)
{
String groupName = item.getItem().group();
boolean found = false;
for (ConfigItemsGroup g : itemGroups)
{
if (g.getGroup().equals(groupName))
{
g.addItem(item);
found = true;
break;
}
}
if (!found)
{
ConfigItemsGroup newGroup = new ConfigItemsGroup(groupName);
newGroup.addItem(item);
itemGroups.add(newGroup);
}
}
itemGroups = itemGroups.stream().sorted((a, b) -> ComparisonChain.start()
.compare(a.getGroup(), b.getGroup())
.result())
.collect(Collectors.toList());
return new ConfigDescriptor(group, itemGroups);
}
/**
* Initialize the configuration from the default settings
*
* @param proxy
*/
public void setDefaultConfiguration(Object proxy, boolean override)
{
Class<?> clazz = proxy.getClass().getInterfaces()[0];
ConfigGroup group = clazz.getAnnotation(ConfigGroup.class);
if (group == null)
{
return;
}
for (Method method : clazz.getDeclaredMethods())
{
ConfigItem item = method.getAnnotation(ConfigItem.class);
// only apply default configuration for methods which read configuration (0 args)
if (item == null || method.getParameterCount() != 0)
{
continue;
}
if (!method.isDefault())
{
if (override)
{
String current = getConfiguration(group.value(), item.keyName());
// only unset if already set
if (current != null)
{
unsetConfiguration(group.value(), item.keyName());
}
}
continue;
}
if (!override)
{
String current = getConfiguration(group.value(), item.keyName());
if (current != null)
{
continue; // something else is already set
}
}
Object defaultValue;
try
{
defaultValue = ConfigInvocationHandler.callDefaultMethod(proxy, method, null);
}
catch (Throwable ex)
{
log.warn(null, ex);
continue;
}
String current = getConfiguration(group.value(), item.keyName());
String valueString = objectToString(defaultValue);
if (Objects.equals(current, valueString))
{
continue; // already set to the default value
}
log.debug("Setting default configuration value for {}.{} to {}", group.value(), item.keyName(), defaultValue);
setConfiguration(group.value(), item.keyName(), valueString);
}
}
static Object stringToObject(String str, Class<?> type)
{
if (type == boolean.class || type == Boolean.class)
{
return Boolean.parseBoolean(str);
}
if (type == int.class)
{
return Integer.parseInt(str);
}
if (type == Color.class)
{
return ColorUtil.fromString(str);
}
if (type == Dimension.class)
{
String[] splitStr = str.split("x");
int width = Integer.parseInt(splitStr[0]);
int height = Integer.parseInt(splitStr[1]);
return new Dimension(width, height);
}
if (type == Point.class)
{
String[] splitStr = str.split(":");
int width = Integer.parseInt(splitStr[0]);
int height = Integer.parseInt(splitStr[1]);
return new Point(width, height);
}
if (type == Rectangle.class)
{
String[] splitStr = str.split(":");
int x = Integer.parseInt(splitStr[0]);
int y = Integer.parseInt(splitStr[1]);
int width = Integer.parseInt(splitStr[2]);
int height = Integer.parseInt(splitStr[3]);
return new Rectangle(x, y, width, height);
}
if (type.isEnum())
{
return Enum.valueOf((Class<? extends Enum>) type, str);
}
if (type == Instant.class)
{
return Instant.parse(str);
}
if (type == Keybind.class || type == ModifierlessKeybind.class)
{
String[] splitStr = str.split(":");
int code = Integer.parseInt(splitStr[0]);
int mods = Integer.parseInt(splitStr[1]);
if (type == ModifierlessKeybind.class)
{
return new ModifierlessKeybind(code, mods);
}
return new Keybind(code, mods);
}
if (type == WorldPoint.class)
{
String[] splitStr = str.split(":");
int x = Integer.parseInt(splitStr[0]);
int y = Integer.parseInt(splitStr[1]);
int plane = Integer.parseInt(splitStr[2]);
return new WorldPoint(x, y, plane);
}
if (type == Duration.class)
{
return Duration.ofMillis(Long.parseLong(str));
}
if (type == Map.class)
{
Map<String, String> output = new HashMap<>();
str = str.substring(1, str.length() - 1);
String[] splitStr = str.split(", ");
for (String s : splitStr)
{
String[] keyVal = s.split("=");
if (keyVal.length > 1)
{
output.put(keyVal[0], keyVal[1]);
}
}
return output;
}
return str;
}
static String objectToString(Object object)
{
if (object instanceof Color)
{
return String.valueOf(((Color) object).getRGB());
}
if (object instanceof Enum)
{
return ((Enum) object).name();
}
if (object instanceof Dimension)
{
Dimension d = (Dimension) object;
return d.width + "x" + d.height;
}
if (object instanceof Point)
{
Point p = (Point) object;
return p.x + ":" + p.y;
}
if (object instanceof Rectangle)
{
Rectangle r = (Rectangle) object;
return r.x + ":" + r.y + ":" + r.width + ":" + r.height;
}
if (object instanceof Instant)
{
return ((Instant) object).toString();
}
if (object instanceof Keybind)
{
Keybind k = (Keybind) object;
return k.getKeyCode() + ":" + k.getModifiers();
}
if (object instanceof WorldPoint)
{
WorldPoint wp = (WorldPoint) object;
return wp.getX() + ":" + wp.getY() + ":" + wp.getPlane();
}
if (object instanceof Duration)
{
return Long.toString(((Duration) object).toMillis());
}
return object.toString();
}
public void sendConfig()
{
boolean changed;
synchronized (pendingChanges)
{
changed = !pendingChanges.isEmpty();
pendingChanges.clear();
}
if (changed)
{
try
{
saveToFile();
}
catch (IOException ex)
{
log.warn("unable to save configuration file", ex);
}
}
}
private void syncLastModified()
{
File newestFile;
newestFile = STANDARD_SETTINGS_FILE;
for (File profileDir : PROFILES_DIR.listFiles())
{
if (!profileDir.isDirectory())
{
continue;
}
for (File settings : profileDir.listFiles())
{
if (!settings.getName().equals(STANDARD_SETTINGS_FILE_NAME) ||
settings.lastModified() < newestFile.lastModified())
{
continue;
}
newestFile = settings;
}
}
syncPropertiesFromFile(newestFile);
}
}

View File

@@ -0,0 +1,91 @@
/*
* Copyright (c) 2018, Craftiii4 <craftiii4@gmail.com>
* 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.config;
import java.util.ArrayList;
import java.util.List;
import lombok.AccessLevel;
import lombok.Getter;
public class ConfigPanelItem
{
@Getter(AccessLevel.PUBLIC)
private ConfigPanelItem parent;
@Getter(AccessLevel.PUBLIC)
private List<ConfigPanelItem> children;
@Getter(AccessLevel.PUBLIC)
private ConfigItemDescriptor item;
public ConfigPanelItem(ConfigPanelItem parent, ConfigItemDescriptor item)
{
this.parent = parent;
this.children = new ArrayList<>();
this.item = item;
}
public List<ConfigPanelItem> getItemsAsList()
{
List<ConfigPanelItem> items = new ArrayList<>();
items.add(this);
for (ConfigPanelItem child : children)
{
items.addAll(child.getItemsAsList());
}
return items;
}
public int getDepth()
{
return (parent == null ? 0 : parent.getDepth() + 1);
}
public boolean addChildIfMatchParent(ConfigItemDescriptor cid)
{
if (item != null && item.getItem().keyName().equals(cid.getItem().parent()))
{
children.add(new ConfigPanelItem(this, cid));
return true;
}
else
{
for (ConfigPanelItem child : children)
{
if (child.addChildIfMatchParent(cid))
{
return true;
}
}
return false;
}
}
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright (c) 2018, Woox <https://github.com/wooxsolo>
* 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.config;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Getter
@RequiredArgsConstructor
public enum ExpandResizeType
{
KEEP_WINDOW_SIZE("Keep window size"),
KEEP_GAME_SIZE("Keep game size");
private final String type;
@Override
public String toString()
{
return type;
}
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright (c) 2018, Tanner <https://github.com/Reasel>
* 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.config;
import java.awt.Font;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import net.runelite.client.ui.FontManager;
@Getter
@RequiredArgsConstructor
public enum FontType
{
REGULAR("Regular", FontManager.getRunescapeFont()),
BOLD("Bold", FontManager.getRunescapeBoldFont()),
SMALL("Small", FontManager.getRunescapeSmallFont());
private final String name;
private final Font font;
@Override
public String toString()
{
return name;
}
}

View File

@@ -0,0 +1,212 @@
/*
* 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.config;
import com.google.common.collect.BiMap;
import com.google.common.collect.ImmutableBiMap;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import javax.annotation.Nullable;
import lombok.EqualsAndHashCode;
import lombok.Getter;
/**
* A combination of zero or more modifier keys (Ctrl, alt, shift, meta)
* and an optional non-modifier key
*/
@Getter
@EqualsAndHashCode
public class Keybind
{
private static final BiMap<Integer, Integer> MODIFIER_TO_KEY_CODE = new ImmutableBiMap.Builder<Integer, Integer>()
.put(InputEvent.CTRL_DOWN_MASK, KeyEvent.VK_CONTROL)
.put(InputEvent.ALT_DOWN_MASK, KeyEvent.VK_ALT)
.put(InputEvent.SHIFT_DOWN_MASK, KeyEvent.VK_SHIFT)
.put(InputEvent.META_DOWN_MASK, KeyEvent.VK_META)
.build();
// Bitmask of all supported modifers
private static final int KEYBOARD_MODIFIER_MASK = MODIFIER_TO_KEY_CODE.keySet().stream()
.reduce((a, b) -> a | b).get();
public static final Keybind NOT_SET = new Keybind(KeyEvent.VK_UNDEFINED, 0);
public static final Keybind CTRL = new Keybind(KeyEvent.VK_UNDEFINED, InputEvent.CTRL_DOWN_MASK);
public static final Keybind ALT = new Keybind(KeyEvent.VK_UNDEFINED, InputEvent.ALT_DOWN_MASK);
public static final Keybind SHIFT = new Keybind(KeyEvent.VK_UNDEFINED, InputEvent.SHIFT_DOWN_MASK);
private final int keyCode;
private final int modifiers;
protected Keybind(int keyCode, int modifiers, boolean ignoreModifiers)
{
modifiers &= KEYBOARD_MODIFIER_MASK;
// If the keybind is just modifiers we don't want the keyCode to contain the modifier too,
// becasue this breaks if you do the keycode backwards
Integer mf = getModifierForKeyCode(keyCode);
if (mf != null)
{
assert (modifiers & mf) != 0;
keyCode = KeyEvent.VK_UNDEFINED;
}
if (ignoreModifiers && keyCode != KeyEvent.VK_UNDEFINED)
{
modifiers = 0;
}
this.keyCode = keyCode;
this.modifiers = modifiers;
}
public Keybind(int keyCode, int modifiers)
{
this(keyCode, modifiers, false);
}
/**
* Constructs a keybind with that matches the passed KeyEvent
*/
public Keybind(KeyEvent e)
{
this(e.getExtendedKeyCode(), e.getModifiersEx());
assert matches(e);
}
/**
* If the KeyEvent is from a KeyPressed event this returns if the
* Event is this hotkey being pressed. If the KeyEvent is a
* KeyReleased event this returns if the event is this hotkey being
* released
*/
public boolean matches(KeyEvent e)
{
return matches(e, false);
}
protected boolean matches(KeyEvent e, boolean ignoreModifiers)
{
if (NOT_SET.equals(this))
{
return false;
}
int keyCode = e.getExtendedKeyCode();
int modifiers = e.getModifiersEx() & KEYBOARD_MODIFIER_MASK;
Integer mf = getModifierForKeyCode(keyCode);
if (mf != null)
{
modifiers |= mf;
keyCode = KeyEvent.VK_UNDEFINED;
}
if (e.getID() == KeyEvent.KEY_RELEASED && keyCode != KeyEvent.VK_UNDEFINED)
{
return this.keyCode == keyCode;
}
if (ignoreModifiers && keyCode != KeyEvent.VK_UNDEFINED)
{
return this.keyCode == keyCode;
}
return this.keyCode == keyCode && this.modifiers == modifiers;
}
@Override
public String toString()
{
if (keyCode == KeyEvent.VK_UNDEFINED && modifiers == 0)
{
return "Not set";
}
String key;
if (keyCode == KeyEvent.VK_UNDEFINED)
{
key = "";
}
else
{
key = KeyEvent.getKeyText(keyCode);
}
String mod = "";
if (modifiers != 0)
{
mod = getModifiersExText(modifiers);
}
if (mod.isEmpty() && key.isEmpty())
{
return "Not set";
}
if (!mod.isEmpty() && !key.isEmpty())
{
return mod + "+" + key;
}
if (mod.isEmpty())
{
return key;
}
return mod;
}
public static String getModifiersExText(int modifiers)
{
StringBuilder buf = new StringBuilder();
if ((modifiers & InputEvent.META_DOWN_MASK) != 0)
{
buf.append("Meta+");
}
if ((modifiers & InputEvent.CTRL_DOWN_MASK) != 0)
{
buf.append("Ctrl+");
}
if ((modifiers & InputEvent.ALT_DOWN_MASK) != 0)
{
buf.append("Alt+");
}
if ((modifiers & InputEvent.SHIFT_DOWN_MASK) != 0)
{
buf.append("Shift+");
}
if (buf.length() > 0)
{
buf.setLength(buf.length() - 1); // remove trailing '+'
}
return buf.toString();
}
@Nullable
public static Integer getModifierForKeyCode(int keyCode)
{
return MODIFIER_TO_KEY_CODE.inverse().get(keyCode);
}
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright (c) 2019 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.config;
import java.awt.event.KeyEvent;
public class ModifierlessKeybind extends Keybind
{
public ModifierlessKeybind(int keyCode, int modifiers)
{
super(keyCode, modifiers, true);
}
/**
* Constructs a keybind with that matches the passed KeyEvent
*/
public ModifierlessKeybind(KeyEvent e)
{
this(e.getExtendedKeyCode(), e.getModifiersEx());
assert matches(e);
}
/**
* If the KeyEvent is from a KeyPressed event this returns if the
* Event is this hotkey being pressed. If the KeyEvent is a
* KeyReleased event this returns if the event is this hotkey being
* released
*/
public boolean matches(KeyEvent e)
{
return matches(e, true);
}
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright (c) 2018, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.client.config;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Used with ConfigItem, describes valid int range for a config item.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface Range
{
int min() default 0;
int max() default Integer.MAX_VALUE;
}

View File

@@ -0,0 +1,286 @@
/*
* Copyright (c) 2017, Tyler <https://github.com/tylerthardy>
* 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.config;
import api.config.Constants;
import java.awt.Dimension;
@ConfigGroup("runelite")
public interface RuneLiteConfig extends Config
{
@ConfigItem(
keyName = "gameSize",
name = "Game size",
description = "The game will resize to this resolution upon starting the client",
position = 10
)
default Dimension gameSize()
{
return Constants.GAME_FIXED_SIZE;
}
@ConfigItem(
keyName = "automaticResizeType",
name = "Resize type",
description = "Choose how the window should resize when opening and closing panels",
position = 11
)
default ExpandResizeType automaticResizeType()
{
return ExpandResizeType.KEEP_GAME_SIZE;
}
@ConfigItem(
keyName = "lockWindowSize",
name = "Lock window size",
description = "Determines if the window resizing is allowed or not",
position = 12
)
default boolean lockWindowSize()
{
return false;
}
@ConfigItem(
keyName = "enablePlugins",
name = "Enable loading of external plugins",
description = "Enable loading of external plugins",
position = 10
)
default boolean enablePlugins()
{
return true;
}
@ConfigItem(
keyName = "containInScreen",
name = "Contain in screen",
description = "Makes the client stay contained in the screen when attempted to move out of it.<br>Note: Only works if custom chrome is enabled.",
position = 13
)
default boolean containInScreen()
{
return false;
}
@ConfigItem(
keyName = "rememberScreenBounds",
name = "Remember client position",
description = "Save the position and size of the client after exiting",
position = 14
)
default boolean rememberScreenBounds()
{
return true;
}
@ConfigItem(
keyName = "uiEnableCustomChrome",
name = "Enable custom window chrome",
description = "Use Runelite's custom window title and borders.",
warning = "Please restart your client after changing this setting",
position = 15
)
default boolean enableCustomChrome()
{
return true;
}
@ConfigItem(
keyName = "gameAlwaysOnTop",
name = "Enable client always on top",
description = "The game will always be on the top of the screen",
position = 16
)
default boolean gameAlwaysOnTop()
{
return false;
}
@ConfigItem(
keyName = "warningOnExit",
name = "Display warning on exit",
description = "Toggles a warning popup when trying to exit the client",
position = 17
)
default WarningOnExit warningOnExit()
{
return WarningOnExit.LOGGED_IN;
}
@ConfigItem(
keyName = "usernameInTitle",
name = "Show display name in title",
description = "Toggles displaying of local player's display name in client title",
position = 18
)
default boolean usernameInTitle()
{
return true;
}
@ConfigItem(
keyName = "notificationTray",
name = "Enable tray notifications",
description = "Enables tray notifications",
position = 20
)
default boolean enableTrayNotifications()
{
return true;
}
@ConfigItem(
keyName = "notificationRequestFocus",
name = "Request focus on notification",
description = "Toggles window focus request",
position = 21
)
default boolean requestFocusOnNotification()
{
return true;
}
@ConfigItem(
keyName = "notificationSound",
name = "Enable sound on notifications",
description = "Enables the playing of a beep sound when notifications are displayed",
position = 22
)
default boolean enableNotificationSound()
{
return true;
}
@ConfigItem(
keyName = "notificationGameMessage",
name = "Enable game message notifications",
description = "Puts a notification message in the chatbox",
position = 23
)
default boolean enableGameMessageNotification()
{
return false;
}
@ConfigItem(
keyName = "notificationFlash",
name = "Enable flash notification",
description = "Flashes the game frame as a notification",
position = 24
)
default boolean enableFlashNotification()
{
return false;
}
@ConfigItem(
keyName = "notificationFocused",
name = "Send notifications when focused",
description = "Toggles all notifications for when the client is focused",
position = 25
)
default boolean sendNotificationsWhenFocused()
{
return false;
}
@ConfigItem(
keyName = "fontType",
name = "Dynamic Overlay Font",
description = "Configures what font type is used for in-game overlays such as player name, ground items, etc.",
position = 30
)
default FontType fontType()
{
return FontType.SMALL;
}
@ConfigItem(
keyName = "tooltipFontType",
name = "Tooltip Font",
description = "Configures what font type is used for in-game tooltips such as food stats, NPC names, etc.",
position = 31
)
default FontType tooltipFontType()
{
return FontType.SMALL;
}
@ConfigItem(
keyName = "interfaceFontType",
name = "Interface Overlay Font",
description = "Configures what font type is used for in-game interface overlays such as panels, opponent info, clue scrolls etc.",
position = 32
)
default FontType interfaceFontType()
{
return FontType.REGULAR;
}
@ConfigItem(
keyName = "menuEntryShift",
name = "Require Shift for overlay menu",
description = "Overlay right-click menu will require shift to be added",
position = 33
)
default boolean menuEntryShift()
{
return true;
}
@ConfigItem(
keyName = "infoBoxVertical",
name = "Display infoboxes vertically",
description = "Toggles the infoboxes to display vertically",
position = 40
)
default boolean infoBoxVertical()
{
return false;
}
@ConfigItem(
keyName = "infoBoxWrap",
name = "Infobox wrap count",
description = "Configures the amount of infoboxes shown before wrapping",
position = 41
)
default int infoBoxWrap()
{
return 4;
}
@ConfigItem(
keyName = "infoBoxSize",
name = "Infobox size (px)",
description = "Configures the size of each infobox in pixels",
position = 42
)
default int infoBoxSize()
{
return 35;
}
}

View File

@@ -0,0 +1,5 @@
package net.runelite.client.config;
public class Stub
{
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright (c) 2018, Matt <https://github.com/ms813>
* 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.config;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Getter
@RequiredArgsConstructor
public enum WarningOnExit
{
ALWAYS("Always"),
LOGGED_IN("Logged in"),
NEVER("Never");
private final String type;
@Override
public String toString()
{
return type;
}
}

View File

@@ -0,0 +1,147 @@
/*
* Copyright (c) 2018, Tomas Slusny <slusnucky@gmail.com>
* 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.discord;
import java.time.Instant;
import lombok.Builder;
import lombok.Value;
/**
* Represents Discord Rich Presence RPC data
*/
@Builder
@Value
public class DiscordPresence
{
/**
* The user's current party status.
* Example: "Looking to Play", "Playing Solo", "In a Group"
*
* <b>Maximum: 128 characters</b>
*/
private String state;
/**
* What the player is currently doing.
* Example: "Competitive - Captain's Mode", "In Queue", "Unranked PvP"
*
* <b>Maximum: 128 characters</b>
*/
private String details;
/**
* Unix timestamp (seconds) for the start of the game.
*/
private Instant startTimestamp;
/**
* Unix timestamp (seconds) for the end of the game.
*/
private Instant endTimestamp;
/**
* Name of the uploaded image for the large profile artwork.
* Example: "default"
*
* <b>Maximum: 32 characters</b>
*/
private String largeImageKey;
/**
* Tooltip for the largeImageKey.
* Example: "Blade's Edge Arena", "Numbani", "Danger Zone"
*
* <b>Maximum: 128 characters</b>
*/
private String largeImageText;
/**
* Name of the uploaded image for the small profile artwork.
* Example: "rogue"
*
* <b>Maximum: 32 characters</b>
*/
private String smallImageKey;
/**
* Tooltip for the smallImageKey.
* Example: "Rogue - Level 100"
*
* <b>Maximum: 128 characters</b>
*/
private String smallImageText;
/**
* ID of the player's party, lobby, or group.
* Example: "ae488379-351d-4a4f-ad32-2b9b01c91657"
*
* <b>Maximum: 128 characters</b>
*/
private String partyId;
/**
* Current size of the player's party, lobby, or group.
* Example: 1
*/
private int partySize;
/**
* Maximum size of the player's party, lobby, or group.
* Example: 5
*/
private int partyMax;
/**
* Unique hashed string for Spectate and Join.
* Required to enable match interactive buttons in the user's presence.
* Example: "MmhuZToxMjMxMjM6cWl3amR3MWlqZA=="
*
* <b>Maximum: 128 characters</b>
*/
private String matchSecret;
/**
* Unique hashed string for Spectate button.
* This will enable the "Spectate" button on the user's presence if whitelisted.
* Example: "MTIzNDV8MTIzNDV8MTMyNDU0"
*
* <b>Maximum: 128 characters</b>
*/
private String joinSecret;
/**
* Unique hashed string for chat invitations and Ask to Join.
* This will enable the "Ask to Join" button on the user's presence if whitelisted.
* Example: "MTI4NzM0OjFpMmhuZToxMjMxMjM="
*
* <b>Maximum: 128 characters</b>
*/
private String spectateSecret;
/**
* Marks the matchSecret as a game session with a specific beginning and end.
*/
private boolean instance;
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright (c) 2018, Tomas Slusny <slusnucky@gmail.com>
* 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.discord;
/**
* Discord reply type for request
*/
public enum DiscordReplyType
{
/**
* Used to decline a request
*/
NO,
/**
* Used to accept a request
*/
YES,
/**
* Currently unused response, treated like NO.
*/
IGNORE
}

View File

@@ -0,0 +1,237 @@
/*
* Copyright (c) 2018, Tomas Slusny <slusnucky@gmail.com>
* 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.discord;
import com.google.common.base.Strings;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import javax.inject.Singleton;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import net.runelite.client.RuneLiteProperties;
import net.runelite.client.discord.events.DiscordDisconnected;
import net.runelite.client.discord.events.DiscordErrored;
import net.runelite.client.discord.events.DiscordJoinGame;
import net.runelite.client.discord.events.DiscordJoinRequest;
import net.runelite.client.discord.events.DiscordReady;
import net.runelite.client.discord.events.DiscordSpectateGame;
import net.runelite.client.eventbus.EventBus;
import net.runelite.discord.DiscordEventHandlers;
import net.runelite.discord.DiscordRPC;
import net.runelite.discord.DiscordRichPresence;
import net.runelite.discord.DiscordUser;
@Singleton
@Slf4j
public class DiscordService implements AutoCloseable
{
private final EventBus eventBus;
public final RuneLiteProperties runeLiteProperties;
private final ScheduledExecutorService executorService;
private final DiscordRPC discordRPC;
// Hold a reference to the event handlers to prevent the garbage collector from deleting them
private final DiscordEventHandlers discordEventHandlers;
@Getter
private DiscordUser currentUser;
@Inject
private DiscordService(
final EventBus eventBus,
final RuneLiteProperties runeLiteProperties,
final ScheduledExecutorService executorService)
{
this.eventBus = eventBus;
this.runeLiteProperties = runeLiteProperties;
this.executorService = executorService;
DiscordRPC discordRPC = null;
DiscordEventHandlers discordEventHandlers = null;
try
{
discordRPC = DiscordRPC.INSTANCE;
discordEventHandlers = new DiscordEventHandlers();
}
catch (Error e)
{
log.warn("Failed to load Discord library, Discord support will be disabled.");
}
this.discordRPC = discordRPC;
this.discordEventHandlers = discordEventHandlers;
}
/**
* Initializes the Discord service, sets up the event handlers and starts worker thread that will poll discord
* events every 2 seconds.
* Before closing the application it is recommended to call {@link #close()}
*/
public void init()
{
if (discordEventHandlers == null)
{
return;
}
log.info("Initializing Discord RPC service.");
discordEventHandlers.ready = this::ready;
discordEventHandlers.disconnected = this::disconnected;
discordEventHandlers.errored = this::errored;
discordEventHandlers.joinGame = this::joinGame;
discordEventHandlers.spectateGame = this::spectateGame;
discordEventHandlers.joinRequest = this::joinRequest;
discordRPC.Discord_Initialize(RuneLiteProperties.discordAppID, discordEventHandlers, true, null);
executorService.scheduleAtFixedRate(discordRPC::Discord_RunCallbacks, 0, 2, TimeUnit.SECONDS);
}
/**
* Shuts the RPC connection down.
* If not currently connected, this does nothing.
*/
@Override
public void close()
{
if (discordRPC != null)
{
discordRPC.Discord_Shutdown();
}
}
/**
* Updates the currently set presence of the logged in user.
* <br>Note that the client only updates its presence every <b>15 seconds</b>
* and queues all additional presence updates.
*
* @param discordPresence The new presence to use
*/
public void updatePresence(DiscordPresence discordPresence)
{
if (discordRPC == null)
{
return;
}
final DiscordRichPresence discordRichPresence = new DiscordRichPresence();
discordRichPresence.state = discordPresence.getState();
discordRichPresence.details = discordPresence.getDetails();
discordRichPresence.startTimestamp = discordPresence.getStartTimestamp() != null
? discordPresence.getStartTimestamp().getEpochSecond()
: 0;
discordRichPresence.endTimestamp = discordPresence.getEndTimestamp() != null
? discordPresence.getEndTimestamp().getEpochSecond()
: 0;
discordRichPresence.largeImageKey = Strings.isNullOrEmpty(discordPresence.getLargeImageKey())
? "default"
: discordPresence.getLargeImageKey();
discordRichPresence.largeImageText = discordPresence.getLargeImageText();
if (!Strings.isNullOrEmpty(discordPresence.getSmallImageKey()))
{
discordRichPresence.smallImageKey = discordPresence.getSmallImageKey();
}
discordRichPresence.smallImageText = discordPresence.getSmallImageText();
discordRichPresence.partyId = discordPresence.getPartyId();
discordRichPresence.partySize = discordPresence.getPartySize();
discordRichPresence.partyMax = discordPresence.getPartyMax();
discordRichPresence.matchSecret = discordPresence.getMatchSecret();
discordRichPresence.joinSecret = discordPresence.getJoinSecret();
discordRichPresence.spectateSecret = discordPresence.getSpectateSecret();
discordRichPresence.instance = (byte) (discordPresence.isInstance() ? 1 : 0);
log.debug("Sending presence update {}", discordPresence);
discordRPC.Discord_UpdatePresence(discordRichPresence);
}
/**
* Clears the currently set presence.
*/
public void clearPresence()
{
if (discordRPC != null)
{
discordRPC.Discord_ClearPresence();
}
}
/**
* Responds to the given user with the specified reply type.
*
* @param userId The id of the user to respond to
* @param reply The reply type
*/
public void respondToRequest(String userId, DiscordReplyType reply)
{
if (discordRPC != null)
{
discordRPC.Discord_Respond(userId, reply.ordinal());
}
}
private void ready(DiscordUser user)
{
log.info("Discord RPC service is ready with user {}.", user.username);
currentUser = user;
eventBus.post(new DiscordReady(
user.userId,
user.username,
user.discriminator,
user.avatar));
}
private void disconnected(int errorCode, String message)
{
eventBus.post(new DiscordDisconnected(errorCode, message));
}
private void errored(int errorCode, String message)
{
log.warn("Discord error: {} - {}", errorCode, message);
eventBus.post(new DiscordErrored(errorCode, message));
}
private void joinGame(String joinSecret)
{
eventBus.post(new DiscordJoinGame(joinSecret));
}
private void spectateGame(String spectateSecret)
{
eventBus.post(new DiscordSpectateGame(spectateSecret));
}
private void joinRequest(DiscordUser user)
{
eventBus.post(new DiscordJoinRequest(
user.userId,
user.username,
user.discriminator,
user.avatar));
}
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright (c) 2018, Tomas Slusny <slusnucky@gmail.com>
* 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.discord.events;
import lombok.Value;
/**
* Called when the RPC connection has been severed
*/
@Value
public class DiscordDisconnected
{
/**
* Discord error code
*/
private int errorCode;
/**
* Error message
*/
private String message;
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright (c) 2018, Tomas Slusny <slusnucky@gmail.com>
* 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.discord.events;
import lombok.Value;
/**
* Called when an internal error is caught within the SDK
*/
@Value
public class DiscordErrored
{
/**
* Discord error code.
*/
private int errorCode;
/**
* Error message
*/
private String message;
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright (c) 2018, Tomas Slusny <slusnucky@gmail.com>
* 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.discord.events;
import lombok.Value;
/**
* Called when the logged in user joined a game
*/
@Value
public class DiscordJoinGame
{
/**
* Obfuscated data of your choosing used as join secret
*/
private String joinSecret;
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright (c) 2018, Tomas Slusny <slusnucky@gmail.com>
* 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.discord.events;
import lombok.Value;
/**
* Called when another discord user wants to join the game of the logged in user
*/
@Value
public class DiscordJoinRequest
{
/**
* The userId for the user that requests to join
*/
private String userId;
/**
* The username of the user that requests to join
*/
private String username;
/**
* The discriminator of the user that requests to join
*/
private String discriminator;
/**
* The avatar of the user that requests to join
*/
private String avatar;
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright (c) 2018, Tomas Slusny <slusnucky@gmail.com>
* 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.discord.events;
import lombok.Value;
/**
* Called when the RPC connection has been established
*/
@Value
public class DiscordReady
{
/**
* The userId for the active user
*/
private String userId;
/**
* The username of the active user
*/
private String username;
/**
* The discriminator of the active user
*/
private String discriminator;
/**
* The avatar of the active user
*/
private String avatar;
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright (c) 2018, Tomas Slusny <slusnucky@gmail.com>
* 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.discord.events;
import lombok.Value;
/**
* Called when the logged in user joined to spectate a game
*/
@Value
public class DiscordSpectateGame
{
/**
* Obfuscated data of your choosing used as spectate secret
*/
private String spectateSecret;
}

View File

@@ -0,0 +1,249 @@
/*
* Copyright (c) 2018, Tomas Slusny <slusnucky@gmail.com>
* 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.eventbus;
import com.google.common.base.Preconditions;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.Multimap;
import java.lang.invoke.CallSite;
import java.lang.invoke.LambdaMetafactory;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.function.Consumer;
import javax.annotation.Nonnull;
import javax.annotation.concurrent.ThreadSafe;
import lombok.EqualsAndHashCode;
import lombok.RequiredArgsConstructor;
import lombok.Value;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@RequiredArgsConstructor
@ThreadSafe
public class EventBus
{
@FunctionalInterface
public interface SubscriberMethod
{
void invoke(Object event);
}
@Value
private static class Subscriber
{
private final Object object;
private final Method method;
@EqualsAndHashCode.Exclude
private final SubscriberMethod lamda;
void invoke(final Object arg) throws Exception
{
if (lamda != null)
{
lamda.invoke(arg);
}
else
{
method.invoke(object, arg);
}
}
}
private final Consumer<Throwable> exceptionHandler;
private ImmutableMultimap<Class, Subscriber> subscribers = ImmutableMultimap.of();
/**
* Instantiates EventBus with default exception handler
*/
public EventBus()
{
this((e) -> log.warn("Uncaught exception in event subscriber", e));
}
/**
* Registers subscriber to EventBus. All methods in subscriber and it's parent classes are checked for
* {@link Subscribe} annotation and then added to map of subscriptions.
*
* @param object subscriber to register
* @throws IllegalArgumentException in case subscriber method name is wrong (correct format is 'on' + EventName
*/
public synchronized void register(@Nonnull final Object object)
{
final ImmutableMultimap.Builder<Class, Subscriber> builder = ImmutableMultimap.builder();
if (subscribers != null)
{
builder.putAll(subscribers);
}
for (Class<?> clazz = object.getClass(); clazz != null; clazz = clazz.getSuperclass())
{
for (final Method method : clazz.getDeclaredMethods())
{
final Subscribe sub = method.getAnnotation(Subscribe.class);
if (sub == null)
{
continue;
}
Preconditions.checkArgument(method.getReturnType() == Void.TYPE, "@Subscribed method \"" + method + "\" cannot return a value");
Preconditions.checkArgument(method.getParameterCount() == 1, "@Subscribed method \"" + method + "\" must take exactly 1 argument");
Preconditions.checkArgument(!Modifier.isStatic(method.getModifiers()), "@Subscribed method \"" + method + "\" cannot be static");
final Class<?> parameterClazz = method.getParameterTypes()[0];
Preconditions.checkArgument(!parameterClazz.isPrimitive(), "@Subscribed method \"" + method + "\" cannot subscribe to primitives");
Preconditions.checkArgument((parameterClazz.getModifiers() & (Modifier.ABSTRACT | Modifier.INTERFACE)) == 0, "@Subscribed method \"" + method + "\" cannot subscribe to polymorphic classes");
for (Class<?> psc = parameterClazz.getSuperclass(); psc != null; psc = psc.getSuperclass())
{
if (subscribers.containsKey(psc))
{
throw new IllegalArgumentException("@Subscribed method \"" + method + "\" cannot subscribe to class which inherits from subscribed class \"" + psc + "\"");
}
}
final String preferredName = "on" + parameterClazz.getSimpleName();
Preconditions.checkArgument(method.getName().equals(preferredName), "Subscribed method " + method + " should be named " + preferredName);
method.setAccessible(true);
SubscriberMethod lambda = null;
try
{
final MethodHandles.Lookup caller = privateLookupIn(clazz);
final MethodType subscription = MethodType.methodType(void.class, parameterClazz);
final MethodHandle target = caller.findVirtual(clazz, method.getName(), subscription);
final CallSite site = LambdaMetafactory.metafactory(
caller,
"invoke",
MethodType.methodType(SubscriberMethod.class, clazz),
subscription.changeParameterType(0, Object.class),
target,
subscription);
final MethodHandle factory = site.getTarget();
lambda = (SubscriberMethod) factory.bindTo(object).invokeExact();
}
catch (Throwable e)
{
log.warn("Unable to create lambda for method {}", method, e);
}
final Subscriber subscriber = new Subscriber(object, method, lambda);
builder.put(parameterClazz, subscriber);
log.debug("Registering {} - {}", parameterClazz, subscriber);
}
}
subscribers = builder.build();
}
/**
* Unregisters all subscribed methods from provided subscriber object.
*
* @param object object to unsubscribe from
*/
public synchronized void unregister(@Nonnull final Object object)
{
if (subscribers == null)
{
return;
}
final Multimap<Class, Subscriber> map = HashMultimap.create();
map.putAll(subscribers);
for (Class<?> clazz = object.getClass(); clazz != null; clazz = clazz.getSuperclass())
{
for (final Method method : clazz.getDeclaredMethods())
{
final Subscribe sub = method.getAnnotation(Subscribe.class);
if (sub == null)
{
continue;
}
final Class<?> parameterClazz = method.getParameterTypes()[0];
map.remove(parameterClazz, new Subscriber(object, method, null));
}
}
subscribers = ImmutableMultimap.copyOf(map);
}
/**
* Posts provided event to all registered subscribers. Subscriber calls are invoked immediately and in order
* in which subscribers were registered.
*
* @param event event to post
*/
public void post(@Nonnull final Object event)
{
for (final Subscriber subscriber : subscribers.get(event.getClass()))
{
try
{
subscriber.invoke(event);
}
catch (Exception e)
{
exceptionHandler.accept(e);
}
}
}
private static MethodHandles.Lookup privateLookupIn(Class clazz) throws IllegalAccessException, NoSuchFieldException, InvocationTargetException
{
try
{
// Java 9+ has privateLookupIn method on MethodHandles, but since we are shipping and using Java 8
// we need to access it via reflection. This is preferred way because it's Java 9+ public api and is
// likely to not change
final Method privateLookupIn = MethodHandles.class.getMethod("privateLookupIn", Class.class, MethodHandles.Lookup.class);
return (MethodHandles.Lookup) privateLookupIn.invoke(null, clazz, MethodHandles.lookup());
}
catch (NoSuchMethodException e)
{
// In Java 8 we first do standard lookupIn class
final MethodHandles.Lookup lookupIn = MethodHandles.lookup().in(clazz);
// and then we mark it as trusted for private lookup via reflection on private field
final Field modes = MethodHandles.Lookup.class.getDeclaredField("allowedModes");
modes.setAccessible(true);
modes.setInt(lookupIn, -1); // -1 == TRUSTED
return lookupIn;
}
}
}

View File

@@ -0,0 +1,41 @@
/*
* 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.eventbus;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Marks a method as an event subscriber.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface Subscribe
{
}

View File

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

View File

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

View File

@@ -0,0 +1,34 @@
/*
* Copyright (c) 2018, Tomas Slusny <slusnucky@gmail.com>
* 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.events;
import lombok.Value;
import net.runelite.client.ui.NavigationButton;
@Value
public class NavigationButtonAdded
{
private NavigationButton button;
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright (c) 2018, Tomas Slusny <slusnucky@gmail.com>
* 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.events;
import lombok.Value;
import net.runelite.client.ui.NavigationButton;
@Value
public class NavigationButtonRemoved
{
private NavigationButton button;
}

View File

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

View File

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

View File

@@ -0,0 +1,34 @@
/*
* Copyright (c) 2018, Tomas Slusny <slusnucky@gmail.com>
* 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.events;
import java.util.UUID;
import lombok.Value;
@Value
public class PartyChanged
{
private final UUID partyId;
}

View File

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

View File

@@ -0,0 +1,35 @@
/*
* Copyright (c) 2017, Tomas Slusny <slusnucky@gmail.com>
* 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.events;
import lombok.Data;
import net.runelite.client.plugins.Plugin;
@Data
public class PluginChanged
{
private final Plugin plugin;
private final boolean loaded;
}

View File

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

View File

@@ -0,0 +1,40 @@
/*
* 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.client.events;
import lombok.Data;
/**
* An event where a new RuneLite account session has been closed,
* typically when logging out of the account.
* <p>
* Note: This event is not to be confused with a RuneScape session,
* it has nothing to do with whether an account is being logged out.
*/
@Data
public class SessionClose
{
}

View File

@@ -0,0 +1,40 @@
/*
* 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.client.events;
import lombok.Data;
/**
* An event where a new RuneLite account session has been opened
* with the server.
* <p>
* Note: This event is not to be confused with a RuneScape session,
* it has nothing to do with whether an account is being logged in.
*/
@Data
public class SessionOpen
{
}

View File

@@ -0,0 +1,248 @@
/*
* 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 static api.NullObjectID.*;
import static api.ObjectID.*;
import api.coords.WorldPoint;
import lombok.Getter;
@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, CASTLE_WALL),
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), WALL_17047),
NEITIZNOT_BRIDGE_REPAIR(40, "Bridge Repair - Quest", new WorldPoint(2315, 3828, 0), ROPE_BRIDGE_21306, ROPE_BRIDGE_21307),
NEITIZNOT_BRIDGE_SOUTHEAST(40, "Rope Bridge", null, ROPE_BRIDGE_21308, ROPE_BRIDGE_21309),
NEITIZNOT_BRIDGE_NORTHWEST(40, "Rope Bridge", null, ROPE_BRIDGE_21310, ROPE_BRIDGE_21311),
NEITIZNOT_BRIDGE_NORTH(40, "Rope Bridge", null, ROPE_BRIDGE_21312, ROPE_BRIDGE_21313),
NEITIZNOT_BRIDGE_NORTHEAST(40, "Broken Rope bridge", null, ROPE_BRIDGE_21314, ROPE_BRIDGE_21315),
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(1761, 3873, 0), ROCKS_34741),
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),
MOUNT_KARUULM_PIPE_SOUTH(88, "Pipe", new WorldPoint(1316, 10214, 0), MYSTERIOUS_PIPE),
MOUNT_KARUULM_PIPE_NORTH(88, "Pipe", new WorldPoint(1346, 10231, 0), MYSTERIOUS_PIPE),
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

@@ -0,0 +1,85 @@
/*
* 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 java.awt.image.BufferedImage;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
public class AsyncBufferedImage extends BufferedImage
{
private final List<Runnable> listeners = new CopyOnWriteArrayList<>();
public AsyncBufferedImage(int width, int height, int imageType)
{
super(width, height, imageType);
}
/**
* Call when the buffer has been changed
*/
public void changed()
{
for (Runnable r : listeners)
{
r.run();
}
}
/**
* Register a function to be ran when the buffer has changed
*/
public void onChanged(Runnable r)
{
listeners.add(r);
}
/**
* Calls setIcon on c, ensuring it is repainted when this changes
*/
public void addTo(JButton c)
{
c.setIcon(makeIcon(c));
}
/**
* Calls setIcon on c, ensuring it is repainted when this changes
*/
public void addTo(JLabel c)
{
c.setIcon(makeIcon(c));
}
private ImageIcon makeIcon(JComponent c)
{
listeners.add(c::repaint);
return new ImageIcon(this);
}
}

View File

@@ -0,0 +1,170 @@
/*
* Copyright (c) 2018, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.client.game;
import api.ClanMember;
import api.ClanMemberRank;
import api.Client;
import api.GameState;
import api.IndexedSprite;
import api.SpriteID;
import api.events.ClanChanged;
import api.events.GameStateChanged;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.image.BufferedImage;
import java.util.Arrays;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nonnull;
import javax.inject.Inject;
import javax.inject.Singleton;
import net.runelite.client.eventbus.Subscribe;
import net.runelite.client.util.ImageUtil;
import net.runelite.client.util.Text;
@Singleton
public class ClanManager
{
private static final int[] CLANCHAT_IMAGES =
{
SpriteID.CLAN_CHAT_RANK_SMILEY_FRIEND,
SpriteID.CLAN_CHAT_RANK_SINGLE_CHEVRON_RECRUIT,
SpriteID.CLAN_CHAT_RANK_DOUBLE_CHEVRON_CORPORAL,
SpriteID.CLAN_CHAT_RANK_TRIPLE_CHEVRON_SERGEANT,
SpriteID.CLAN_CHAT_RANK_BRONZE_STAR_LIEUTENANT,
SpriteID.CLAN_CHAT_RANK_SILVER_STAR_CAPTAIN,
SpriteID.CLAN_CHAT_RANK_GOLD_STAR_GENERAL,
SpriteID.CLAN_CHAT_RANK_KEY_CHANNEL_OWNER,
SpriteID.CLAN_CHAT_RANK_CROWN_JAGEX_MODERATOR,
};
private static final Dimension CLANCHAT_IMAGE_DIMENSION = new Dimension(11, 11);
private static final Color CLANCHAT_IMAGE_OUTLINE_COLOR = new Color(33, 33, 33);
private final Client client;
private final SpriteManager spriteManager;
private final BufferedImage[] clanChatImages = new BufferedImage[CLANCHAT_IMAGES.length];
private final LoadingCache<String, ClanMemberRank> clanRanksCache = CacheBuilder.newBuilder()
.maximumSize(100)
.expireAfterWrite(1, TimeUnit.MINUTES)
.build(new CacheLoader<String, ClanMemberRank>()
{
@Override
public ClanMemberRank load(@Nonnull String key)
{
final ClanMember[] clanMembersArr = client.getClanMembers();
if (clanMembersArr == null || clanMembersArr.length == 0)
{
return ClanMemberRank.UNRANKED;
}
return Arrays.stream(clanMembersArr)
.filter(Objects::nonNull)
.filter(clanMember -> sanitize(clanMember.getUsername()).equals(sanitize(key)))
.map(ClanMember::getRank)
.findAny()
.orElse(ClanMemberRank.UNRANKED);
}
});
private int modIconsLength;
@Inject
private ClanManager(Client client, SpriteManager spriteManager)
{
this.client = client;
this.spriteManager = spriteManager;
}
public ClanMemberRank getRank(String playerName)
{
return clanRanksCache.getUnchecked(playerName);
}
public BufferedImage getClanImage(final ClanMemberRank clanMemberRank)
{
if (clanMemberRank == ClanMemberRank.UNRANKED)
{
return null;
}
return clanChatImages[clanMemberRank.ordinal() - 1];
}
public int getIconNumber(final ClanMemberRank clanMemberRank)
{
return modIconsLength - CLANCHAT_IMAGES.length + clanMemberRank.ordinal() - 1;
}
@Subscribe
public void onGameStateChanged(GameStateChanged gameStateChanged)
{
if (gameStateChanged.getGameState() == GameState.LOGGED_IN
&& modIconsLength == 0)
{
loadClanChatIcons();
}
}
@Subscribe
public void onClanChanged(ClanChanged clanChanged)
{
clanRanksCache.invalidateAll();
}
private void loadClanChatIcons()
{
final IndexedSprite[] modIcons = client.getModIcons();
final IndexedSprite[] newModIcons = Arrays.copyOf(modIcons, modIcons.length + CLANCHAT_IMAGES.length);
int curPosition = newModIcons.length - CLANCHAT_IMAGES.length;
for (int i = 0; i < CLANCHAT_IMAGES.length; i++, curPosition++)
{
final int resource = CLANCHAT_IMAGES[i];
clanChatImages[i] = clanChatImageFromSprite(spriteManager.getSprite(resource, 0));
newModIcons[curPosition] = ImageUtil.getImageIndexedSprite(clanChatImages[i], client);
}
client.setModIcons(newModIcons);
modIconsLength = newModIcons.length;
}
private static String sanitize(String lookup)
{
final String cleaned = Text.removeTags(lookup);
return cleaned.replace('\u00A0', ' ');
}
private static BufferedImage clanChatImageFromSprite(final BufferedImage clanSprite)
{
final BufferedImage clanChatCanvas = ImageUtil.resizeCanvas(clanSprite, CLANCHAT_IMAGE_DIMENSION.width, CLANCHAT_IMAGE_DIMENSION.height);
return ImageUtil.outlineImage(clanChatCanvas, CLANCHAT_IMAGE_OUTLINE_COLOR);
}
}

View File

@@ -0,0 +1,88 @@
/*
* Copyright (c) 2018, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.client.game;
import com.google.common.cache.CacheLoader;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import java.io.IOException;
import java.util.concurrent.ScheduledExecutorService;
import lombok.extern.slf4j.Slf4j;
import static net.runelite.client.game.HiscoreManager.EMPTY;
import static net.runelite.client.game.HiscoreManager.NONE;
import net.runelite.http.api.hiscore.HiscoreClient;
import net.runelite.http.api.hiscore.HiscoreEndpoint;
import net.runelite.http.api.hiscore.HiscoreResult;
@Slf4j
class HiscoreLoader extends CacheLoader<HiscoreManager.HiscoreKey, HiscoreResult>
{
private final ListeningExecutorService executorService;
private final HiscoreClient hiscoreClient;
HiscoreLoader(ScheduledExecutorService executor, HiscoreClient client)
{
this.executorService = MoreExecutors.listeningDecorator(executor);
this.hiscoreClient = client;
}
@Override
public HiscoreResult load(HiscoreManager.HiscoreKey hiscoreKey) throws Exception
{
return EMPTY;
}
@Override
public ListenableFuture<HiscoreResult> reload(HiscoreManager.HiscoreKey hiscoreKey, HiscoreResult oldValue)
{
log.debug("Submitting hiscore lookup for {} type {}", hiscoreKey.getUsername(), hiscoreKey.getType());
return executorService.submit(() -> fetch(hiscoreKey));
}
private HiscoreResult fetch(HiscoreManager.HiscoreKey hiscoreKey)
{
String username = hiscoreKey.getUsername();
HiscoreEndpoint endpoint = hiscoreKey.getType();
try
{
HiscoreResult result = hiscoreClient.lookup(username, endpoint);
if (result == null)
{
return NONE;
}
return result;
}
catch (IOException ex)
{
log.warn("Unable to look up hiscore!", ex);
return NONE;
}
}
}

View File

@@ -0,0 +1,115 @@
/*
* Copyright (c) 2018, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.client.game;
import api.Client;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.LoadingCache;
import java.io.IOException;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import javax.inject.Singleton;
import lombok.AllArgsConstructor;
import lombok.Data;
import net.runelite.client.callback.ClientThread;
import net.runelite.http.api.hiscore.HiscoreClient;
import net.runelite.http.api.hiscore.HiscoreEndpoint;
import net.runelite.http.api.hiscore.HiscoreResult;
@Singleton
public class HiscoreManager
{
@AllArgsConstructor
@Data
static class HiscoreKey
{
String username;
HiscoreEndpoint type;
}
static final HiscoreResult EMPTY = new HiscoreResult();
static final HiscoreResult NONE = new HiscoreResult();
private final HiscoreClient hiscoreClient = new HiscoreClient();
private final LoadingCache<HiscoreKey, HiscoreResult> hiscoreCache;
@Inject
public HiscoreManager(Client client, ScheduledExecutorService executor, ClientThread clientThread)
{
hiscoreCache = CacheBuilder.newBuilder()
.maximumSize(128L)
.expireAfterWrite(1, TimeUnit.HOURS)
.build(new HiscoreLoader(executor, hiscoreClient));
}
/**
* Synchronously look up a players hiscore from a specified endpoint
*
* @param username Players username
* @param endpoint Hiscore endpoint
* @return HiscoreResult or null
* @throws IOException Upon error in fetching hiscore
*/
public HiscoreResult lookup(String username, HiscoreEndpoint endpoint) throws IOException
{
HiscoreKey hiscoreKey = new HiscoreKey(username, endpoint);
HiscoreResult hiscoreResult = hiscoreCache.getIfPresent(hiscoreKey);
if (hiscoreResult != null && hiscoreResult != EMPTY)
{
return hiscoreResult == NONE ? null : hiscoreResult;
}
hiscoreResult = hiscoreClient.lookup(username, endpoint);
if (hiscoreResult == null)
{
hiscoreCache.put(hiscoreKey, NONE);
return null;
}
hiscoreCache.put(hiscoreKey, hiscoreResult);
return hiscoreResult;
}
/**
* Asynchronously look up a players hiscore from a specified endpoint
*
* @param username Players username
* @param endpoint Hiscore endpoint
* @return HiscoreResult or null
*/
public HiscoreResult lookupAsync(String username, HiscoreEndpoint endpoint)
{
HiscoreKey hiscoreKey = new HiscoreKey(username, endpoint);
HiscoreResult hiscoreResult = hiscoreCache.getIfPresent(hiscoreKey);
if (hiscoreResult != null && hiscoreResult != EMPTY)
{
return hiscoreResult == NONE ? null : hiscoreResult;
}
hiscoreCache.refresh(hiscoreKey);
return null;
}
}

View File

@@ -0,0 +1,506 @@
/*
* 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.client.game;
import api.Client;
import static api.Constants.CLIENT_DEFAULT_ZOOM;
import static api.Constants.HIGH_ALCHEMY_CONSTANT;
import api.GameState;
import api.ItemDefinition;
import api.ItemID;
import static api.ItemID.*;
import api.Sprite;
import api.events.GameStateChanged;
import api.events.PostItemDefinition;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableMap;
import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.inject.Singleton;
import lombok.Value;
import lombok.extern.slf4j.Slf4j;
import net.runelite.client.callback.ClientThread;
import net.runelite.client.eventbus.Subscribe;
import net.runelite.http.api.item.ItemClient;
import net.runelite.http.api.item.ItemPrice;
import net.runelite.http.api.item.ItemStats;
@Singleton
@Slf4j
public class ItemManager
{
@Value
private static class ImageKey
{
private final int itemId;
private final int itemQuantity;
private final boolean stackable;
}
@Value
private static class OutlineKey
{
private final int itemId;
private final int itemQuantity;
private final Color outlineColor;
}
private final Client client;
private final ScheduledExecutorService scheduledExecutorService;
private final ClientThread clientThread;
private final ItemClient itemClient = new ItemClient();
private Map<Integer, ItemPrice> itemPrices = Collections.emptyMap();
private Map<Integer, ItemStats> itemStats = Collections.emptyMap();
private final LoadingCache<ImageKey, AsyncBufferedImage> itemImages;
private final LoadingCache<Integer, ItemDefinition> itemDefinitions;
private final LoadingCache<OutlineKey, BufferedImage> itemOutlines;
// Worn items with weight reducing property have a different worn and inventory ItemID
private static final ImmutableMap<Integer, Integer> WORN_ITEMS = ImmutableMap.<Integer, Integer>builder().
put(BOOTS_OF_LIGHTNESS_89, BOOTS_OF_LIGHTNESS).
put(PENANCE_GLOVES_10554, PENANCE_GLOVES).
put(GRACEFUL_HOOD_11851, GRACEFUL_HOOD).
put(GRACEFUL_CAPE_11853, GRACEFUL_CAPE).
put(GRACEFUL_TOP_11855, GRACEFUL_TOP).
put(GRACEFUL_LEGS_11857, GRACEFUL_LEGS).
put(GRACEFUL_GLOVES_11859, GRACEFUL_GLOVES).
put(GRACEFUL_BOOTS_11861, GRACEFUL_BOOTS).
put(GRACEFUL_HOOD_13580, GRACEFUL_HOOD_13579).
put(GRACEFUL_CAPE_13582, GRACEFUL_CAPE_13581).
put(GRACEFUL_TOP_13584, GRACEFUL_TOP_13583).
put(GRACEFUL_LEGS_13586, GRACEFUL_LEGS_13585).
put(GRACEFUL_GLOVES_13588, GRACEFUL_GLOVES_13587).
put(GRACEFUL_BOOTS_13590, GRACEFUL_BOOTS_13589).
put(GRACEFUL_HOOD_13592, GRACEFUL_HOOD_13591).
put(GRACEFUL_CAPE_13594, GRACEFUL_CAPE_13593).
put(GRACEFUL_TOP_13596, GRACEFUL_TOP_13595).
put(GRACEFUL_LEGS_13598, GRACEFUL_LEGS_13597).
put(GRACEFUL_GLOVES_13600, GRACEFUL_GLOVES_13599).
put(GRACEFUL_BOOTS_13602, GRACEFUL_BOOTS_13601).
put(GRACEFUL_HOOD_13604, GRACEFUL_HOOD_13603).
put(GRACEFUL_CAPE_13606, GRACEFUL_CAPE_13605).
put(GRACEFUL_TOP_13608, GRACEFUL_TOP_13607).
put(GRACEFUL_LEGS_13610, GRACEFUL_LEGS_13609).
put(GRACEFUL_GLOVES_13612, GRACEFUL_GLOVES_13611).
put(GRACEFUL_BOOTS_13614, GRACEFUL_BOOTS_13613).
put(GRACEFUL_HOOD_13616, GRACEFUL_HOOD_13615).
put(GRACEFUL_CAPE_13618, GRACEFUL_CAPE_13617).
put(GRACEFUL_TOP_13620, GRACEFUL_TOP_13619).
put(GRACEFUL_LEGS_13622, GRACEFUL_LEGS_13621).
put(GRACEFUL_GLOVES_13624, GRACEFUL_GLOVES_13623).
put(GRACEFUL_BOOTS_13626, GRACEFUL_BOOTS_13625).
put(GRACEFUL_HOOD_13628, GRACEFUL_HOOD_13627).
put(GRACEFUL_CAPE_13630, GRACEFUL_CAPE_13629).
put(GRACEFUL_TOP_13632, GRACEFUL_TOP_13631).
put(GRACEFUL_LEGS_13634, GRACEFUL_LEGS_13633).
put(GRACEFUL_GLOVES_13636, GRACEFUL_GLOVES_13635).
put(GRACEFUL_BOOTS_13638, GRACEFUL_BOOTS_13637).
put(GRACEFUL_HOOD_13668, GRACEFUL_HOOD_13667).
put(GRACEFUL_CAPE_13670, GRACEFUL_CAPE_13669).
put(GRACEFUL_TOP_13672, GRACEFUL_TOP_13671).
put(GRACEFUL_LEGS_13674, GRACEFUL_LEGS_13673).
put(GRACEFUL_GLOVES_13676, GRACEFUL_GLOVES_13675).
put(GRACEFUL_BOOTS_13678, GRACEFUL_BOOTS_13677).
put(GRACEFUL_HOOD_21063, GRACEFUL_HOOD_21061).
put(GRACEFUL_CAPE_21066, GRACEFUL_CAPE_21064).
put(GRACEFUL_TOP_21069, GRACEFUL_TOP_21067).
put(GRACEFUL_LEGS_21072, GRACEFUL_LEGS_21070).
put(GRACEFUL_GLOVES_21075, GRACEFUL_GLOVES_21073).
put(GRACEFUL_BOOTS_21078, GRACEFUL_BOOTS_21076).
put(MAX_CAPE_13342, MAX_CAPE).
put(SPOTTED_CAPE_10073, SPOTTED_CAPE).
put(SPOTTIER_CAPE_10074, SPOTTIER_CAPE).
put(AGILITY_CAPET_13341, AGILITY_CAPET).
put(AGILITY_CAPE_13340, AGILITY_CAPE).
build();
@Inject
public ItemManager(Client client, ScheduledExecutorService executor, ClientThread clientThread)
{
this.client = client;
this.scheduledExecutorService = executor;
this.clientThread = clientThread;
scheduledExecutorService.scheduleWithFixedDelay(this::loadPrices, 0, 30, TimeUnit.MINUTES);
scheduledExecutorService.submit(this::loadStats);
itemImages = CacheBuilder.newBuilder()
.maximumSize(128L)
.expireAfterAccess(1, TimeUnit.HOURS)
.build(new CacheLoader<ImageKey, AsyncBufferedImage>()
{
@Override
public AsyncBufferedImage load(ImageKey key) throws Exception
{
return loadImage(key.itemId, key.itemQuantity, key.stackable);
}
});
itemDefinitions = CacheBuilder.newBuilder()
.maximumSize(1024L)
.expireAfterAccess(1, TimeUnit.HOURS)
.build(new CacheLoader<Integer, ItemDefinition>()
{
@Override
public ItemDefinition load(Integer key) throws Exception
{
return client.getItemDefinition(key);
}
});
itemOutlines = CacheBuilder.newBuilder()
.maximumSize(128L)
.expireAfterAccess(1, TimeUnit.HOURS)
.build(new CacheLoader<OutlineKey, BufferedImage>()
{
@Override
public BufferedImage load(OutlineKey key) throws Exception
{
return loadItemOutline(key.itemId, key.itemQuantity, key.outlineColor);
}
});
}
private void loadPrices()
{
try
{
ItemPrice[] prices = itemClient.getPrices();
if (prices != null)
{
ImmutableMap.Builder<Integer, ItemPrice> map = ImmutableMap.builderWithExpectedSize(prices.length);
for (ItemPrice price : prices)
{
map.put(price.getId(), price);
}
itemPrices = map.build();
}
log.debug("Loaded {} prices", itemPrices.size());
}
catch (IOException e)
{
log.warn("error loading prices!", e);
}
}
private void loadStats()
{
try
{
final Map<Integer, ItemStats> stats = itemClient.getStats();
if (stats != null)
{
itemStats = ImmutableMap.copyOf(stats);
}
log.debug("Loaded {} stats", itemStats.size());
}
catch (IOException e)
{
log.warn("error loading stats!", e);
}
}
@Subscribe
public void onGameStateChanged(final GameStateChanged event)
{
if (event.getGameState() == GameState.HOPPING || event.getGameState() == GameState.LOGIN_SCREEN)
{
itemDefinitions.invalidateAll();
}
}
@Subscribe
public void onPostItemDefinition(PostItemDefinition event)
{
itemDefinitions.put(event.getItemDefinition().getId(), event.getItemDefinition());
}
/**
* Invalidates internal item manager item composition cache (but not client item composition cache)
*
* @see Client#getItemDefinitionCache()
*/
public void invalidateItemDefinitionCache()
{
itemDefinitions.invalidateAll();
}
/**
* Look up an item's price
*
* @param itemID item id
* @return item price
*/
public int getItemPrice(int itemID)
{
if (itemID == ItemID.COINS_995)
{
return 1;
}
if (itemID == ItemID.PLATINUM_TOKEN)
{
return 1000;
}
UntradeableItemMapping p = UntradeableItemMapping.map(ItemVariationMapping.map(itemID));
if (p != null)
{
return getItemPrice(p.getPriceID()) * p.getQuantity();
}
int price = 0;
for (int mappedID : ItemMapping.map(itemID))
{
ItemPrice ip = itemPrices.get(mappedID);
if (ip != null)
{
price += ip.getPrice();
}
}
return price;
}
public int getAlchValue(ItemDefinition composition)
{
if (composition.getId() == ItemID.COINS_995)
{
return 1;
}
if (composition.getId() == ItemID.PLATINUM_TOKEN)
{
return 1000;
}
return (int) Math.max(1, composition.getPrice() * HIGH_ALCHEMY_CONSTANT);
}
public int getAlchValue(int itemID)
{
if (itemID == ItemID.COINS_995)
{
return 1;
}
if (itemID == ItemID.PLATINUM_TOKEN)
{
return 1000;
}
return (int) Math.max(1, getItemDefinition(itemID).getPrice() * HIGH_ALCHEMY_CONSTANT);
}
/**
* Look up an item's stats
*
* @param itemId item id
* @return item stats
*/
@Nullable
public ItemStats getItemStats(int itemId, boolean allowNote)
{
ItemDefinition itemComposition = getItemDefinition(itemId);
if (itemComposition == null || itemComposition.getName() == null || (!allowNote && itemComposition.getNote() != -1))
{
return null;
}
return itemStats.get(canonicalize(itemId));
}
/**
* Search for tradeable items based on item name
*
* @param itemName item name
* @return
*/
public List<ItemPrice> search(String itemName)
{
itemName = itemName.toLowerCase();
List<ItemPrice> result = new ArrayList<>();
for (ItemPrice itemPrice : itemPrices.values())
{
final String name = itemPrice.getName();
if (name.toLowerCase().contains(itemName))
{
result.add(itemPrice);
}
}
return result;
}
/**
* Look up an item's composition
*
* @param itemId item id
* @return item composition
*/
public ItemDefinition getItemDefinition(int itemId)
{
assert client.isClientThread() : "getItemDefinition must be called on client thread";
return itemDefinitions.getUnchecked(itemId);
}
/**
* Get an item's un-noted, un-placeholdered ID
*/
public int canonicalize(int itemID)
{
ItemDefinition itemComposition = getItemDefinition(itemID);
if (itemComposition.getNote() != -1)
{
return itemComposition.getLinkedNoteId();
}
if (itemComposition.getPlaceholderTemplateId() != -1)
{
return itemComposition.getPlaceholderId();
}
return WORN_ITEMS.getOrDefault(itemID, itemID);
}
/**
* Loads item sprite from game, makes transparent, and generates image
*
* @param itemId
* @return
*/
private AsyncBufferedImage loadImage(int itemId, int quantity, boolean stackable)
{
AsyncBufferedImage img = new AsyncBufferedImage(36, 32, BufferedImage.TYPE_INT_ARGB);
clientThread.invoke(() ->
{
if (client.getGameState().ordinal() < GameState.LOGIN_SCREEN.ordinal())
{
return false;
}
Sprite sprite = client.createItemSprite(itemId, quantity, 1, Sprite.DEFAULT_SHADOW_COLOR,
stackable ? 1 : 0, false, CLIENT_DEFAULT_ZOOM);
if (sprite == null)
{
return false;
}
sprite.toBufferedImage(img);
img.changed();
return true;
});
return img;
}
/**
* Get item sprite image as BufferedImage.
* <p>
* This method may return immediately with a blank image if not called on the game thread.
* The image will be filled in later. If this is used for a UI label/button, it should be added
* using AsyncBufferedImage::addTo to ensure it is painted properly
*
* @param itemId
* @return
*/
public AsyncBufferedImage getImage(int itemId)
{
return getImage(itemId, 1, false);
}
/**
* Get item sprite image as BufferedImage.
* <p>
* This method may return immediately with a blank image if not called on the game thread.
* The image will be filled in later. If this is used for a UI label/button, it should be added
* using AsyncBufferedImage::addTo to ensure it is painted properly
*
* @param itemId
* @param quantity
* @return
*/
public AsyncBufferedImage getImage(int itemId, int quantity, boolean stackable)
{
try
{
return itemImages.get(new ImageKey(itemId, quantity, stackable));
}
catch (ExecutionException ex)
{
return null;
}
}
/**
* Create item sprite and applies an outline.
*
* @param itemId item id
* @param itemQuantity item quantity
* @param outlineColor outline color
* @return image
*/
private BufferedImage loadItemOutline(final int itemId, final int itemQuantity, final Color outlineColor)
{
final Sprite itemSprite = client.createItemSprite(itemId, itemQuantity, 1, 0, 0, true, 710);
return itemSprite.toBufferedOutline(outlineColor);
}
/**
* Get item outline with a specific color.
*
* @param itemId item id
* @param itemQuantity item quantity
* @param outlineColor outline color
* @return image
*/
public BufferedImage getItemOutline(final int itemId, final int itemQuantity, final Color outlineColor)
{
try
{
return itemOutlines.get(new OutlineKey(itemId, itemQuantity, outlineColor));
}
catch (ExecutionException e)
{
return null;
}
}
}

View File

@@ -0,0 +1,273 @@
/*
* Copyright (c) 2018, Tomas Slusny <slusnucky@gmail.com>
* Copyright (c) 2018, Seth <Sethtroll3@gmail.com>
* 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 static api.ItemID.*;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import java.util.Collection;
import java.util.Collections;
/**
* Converts untradeable items to it's tradeable counterparts
*/
public enum ItemMapping
{
// Barrows equipment
ITEM_AHRIMS_HOOD(AHRIMS_HOOD, AHRIMS_HOOD_25, AHRIMS_HOOD_50, AHRIMS_HOOD_75, AHRIMS_HOOD_100),
ITEM_AHRIMS_ROBETOP(AHRIMS_ROBETOP, AHRIMS_ROBETOP_25, AHRIMS_ROBETOP_50, AHRIMS_ROBETOP_75, AHRIMS_ROBETOP_100),
ITEM_AHRIMS_ROBEBOTTOM(AHRIMS_ROBESKIRT, AHRIMS_ROBESKIRT_25, AHRIMS_ROBESKIRT_50, AHRIMS_ROBESKIRT_75, AHRIMS_ROBESKIRT_100),
ITEM_AHRIMS_STAFF(AHRIMS_STAFF, AHRIMS_STAFF_25, AHRIMS_STAFF_50, AHRIMS_STAFF_75, AHRIMS_STAFF_100),
ITEM_KARILS_COIF(KARILS_COIF, KARILS_COIF_25, KARILS_COIF_50, KARILS_COIF_75, KARILS_COIF_100),
ITEM_KARILS_LEATHERTOP(KARILS_LEATHERTOP, KARILS_LEATHERTOP_25, KARILS_LEATHERTOP_50, KARILS_LEATHERTOP_75, KARILS_LEATHERTOP_100),
ITEM_KARILS_LEATHERSKIRT(KARILS_LEATHERSKIRT, KARILS_LEATHERSKIRT_25, KARILS_LEATHERSKIRT_50, KARILS_LEATHERSKIRT_75, KARILS_LEATHERSKIRT_100),
ITEM_KARILS_CROSSBOW(KARILS_CROSSBOW, KARILS_CROSSBOW_25, KARILS_CROSSBOW_50, KARILS_CROSSBOW_75, KARILS_CROSSBOW_100),
ITEM_DHAROKS_HELM(DHAROKS_HELM, DHAROKS_HELM_25, DHAROKS_HELM_50, DHAROKS_HELM_75, DHAROKS_HELM_100),
ITEM_DHAROKS_PLATEBODY(DHAROKS_PLATEBODY, DHAROKS_PLATEBODY_25, DHAROKS_PLATEBODY_50, DHAROKS_PLATEBODY_75, DHAROKS_PLATEBODY_100),
ITEM_DHAROKS_PLATELEGS(DHAROKS_PLATELEGS, DHAROKS_PLATELEGS_25, DHAROKS_PLATELEGS_50, DHAROKS_PLATELEGS_75, DHAROKS_PLATELEGS_100),
ITEM_DHARKS_GREATEAXE(DHAROKS_GREATAXE, DHAROKS_GREATAXE_25, DHAROKS_GREATAXE_50, DHAROKS_GREATAXE_75, DHAROKS_GREATAXE_100),
ITEM_GUTHANS_HELM(GUTHANS_HELM, GUTHANS_HELM_25, GUTHANS_HELM_50, GUTHANS_HELM_75, GUTHANS_HELM_100),
ITEM_GUTHANS_PLATEBODY(GUTHANS_PLATEBODY, GUTHANS_PLATEBODY_25, GUTHANS_PLATEBODY_50, GUTHANS_PLATEBODY_75, GUTHANS_PLATEBODY_100),
ITEM_GUTHANS_CHAINSKIRT(GUTHANS_CHAINSKIRT, GUTHANS_CHAINSKIRT_25, GUTHANS_CHAINSKIRT_50, GUTHANS_CHAINSKIRT_75, GUTHANS_CHAINSKIRT_100),
ITEM_GUTHANS_WARSPEAR(GUTHANS_WARSPEAR, GUTHANS_WARSPEAR_25, GUTHANS_WARSPEAR_50, GUTHANS_WARSPEAR_75, GUTHANS_WARSPEAR_100),
ITEM_TORAGS_HELM(TORAGS_HELM, TORAGS_HELM_25, TORAGS_HELM_50, TORAGS_HELM_75, TORAGS_HELM_100),
ITEM_TORAGS_PLATEBODY(TORAGS_PLATEBODY, TORAGS_PLATEBODY_25, TORAGS_PLATEBODY_50, TORAGS_PLATEBODY_75, TORAGS_PLATEBODY_100),
ITEM_TORAGS_PLATELEGS(TORAGS_PLATELEGS, TORAGS_PLATELEGS_25, TORAGS_PLATELEGS_50, TORAGS_PLATELEGS_75, TORAGS_PLATELEGS_100),
ITEM_TORAGS_HAMMERS(TORAGS_HAMMERS, TORAGS_HAMMERS_25, TORAGS_HAMMERS_50, TORAGS_HAMMERS_75, TORAGS_HAMMERS_100),
ITEM_VERACS_HELM(VERACS_HELM, VERACS_HELM_25, VERACS_HELM_50, VERACS_HELM_75, VERACS_HELM_100),
ITEM_VERACS_BRASSARD(VERACS_BRASSARD, VERACS_BRASSARD_25, VERACS_BRASSARD_50, VERACS_BRASSARD_75, VERACS_BRASSARD_100),
ITEM_VERACS_PLATESKIRT(VERACS_PLATESKIRT, VERACS_PLATESKIRT_25, VERACS_PLATESKIRT_50, VERACS_PLATESKIRT_75, VERACS_PLATESKIRT_100),
ITEM_VERACS_FLAIL(VERACS_FLAIL, VERACS_FLAIL_25, VERACS_FLAIL_50, VERACS_FLAIL_75, VERACS_FLAIL_100),
// Dragon equipment ornament kits
ITEM_DRAGON_SCIMITAR(DRAGON_SCIMITAR, DRAGON_SCIMITAR_OR),
ITEM_DRAGON_SCIMITAR_ORNAMENT_KIT(DRAGON_SCIMITAR_ORNAMENT_KIT, DRAGON_SCIMITAR_OR),
ITEM_DRAGON_DEFENDER(DRAGON_DEFENDER_ORNAMENT_KIT, DRAGON_DEFENDER_T),
ITEM_DRAGON_PICKAXE(DRAGON_PICKAXE, DRAGON_PICKAXE_12797),
ITEM_DRAGON_KITESHIELD(DRAGON_KITESHIELD, DRAGON_KITESHIELD_G),
ITEM_DRAGON_KITESHIELD_ORNAMENT_KIT(DRAGON_KITESHIELD_ORNAMENT_KIT, DRAGON_KITESHIELD_G),
ITEM_DRAGON_FULL_HELM(DRAGON_FULL_HELM, DRAGON_FULL_HELM_G),
ITEM_DRAGON_FULL_HELM_ORNAMENT_KIT(DRAGON_FULL_HELM_ORNAMENT_KIT, DRAGON_FULL_HELM_G),
ITEM_DRAGON_CHAINBODY(DRAGON_CHAINBODY_3140, DRAGON_CHAINBODY_G),
ITEM_DRAGON_CHAINBODY_ORNAMENT_KIT(DRAGON_CHAINBODY_ORNAMENT_KIT, DRAGON_CHAINBODY_G),
ITEM_DRAGON_PLATEBODY(DRAGON_PLATEBODY, DRAGON_PLATEBODY_G),
ITEM_DRAGON_PLATEBODY_ORNAMENT_KIT(DRAGON_PLATEBODY_ORNAMENT_KIT, DRAGON_PLATEBODY_G),
ITEM_DRAGON_PLATESKIRT(DRAGON_PLATESKIRT, DRAGON_PLATESKIRT_G),
ITEM_DRAGON_SKIRT_ORNAMENT_KIT(DRAGON_LEGSSKIRT_ORNAMENT_KIT, DRAGON_PLATESKIRT_G),
ITEM_DRAGON_PLATELEGS(DRAGON_PLATELEGS, DRAGON_PLATELEGS_G),
ITEM_DRAGON_LEGS_ORNAMENT_KIT(DRAGON_LEGSSKIRT_ORNAMENT_KIT, DRAGON_PLATELEGS_G),
ITEM_DRAGON_SQ_SHIELD(DRAGON_SQ_SHIELD, DRAGON_SQ_SHIELD_G),
ITEM_DRAGON_SQ_SHIELD_ORNAMENT_KIT(DRAGON_SQ_SHIELD_ORNAMENT_KIT, DRAGON_SQ_SHIELD_G),
ITEM_DRAGON_BOOTS(DRAGON_BOOTS, DRAGON_BOOTS_G),
ITEM_DRAGON_BOOTS_ORNAMENT_KIT(DRAGON_BOOTS_ORNAMENT_KIT, DRAGON_BOOTS_G),
// Godsword ornament kits
ITEM_ARMADYL_GODSWORD(ARMADYL_GODSWORD, ARMADYL_GODSWORD_OR),
ITEM_ARMADYL_GODSWORD_ORNAMENT_KIT(ARMADYL_GODSWORD_ORNAMENT_KIT, ARMADYL_GODSWORD_OR),
ITEM_BANDOS_GODSWORD(BANDOS_GODSWORD, BANDOS_GODSWORD_OR),
ITEM_BANDOS_GODSWORD_ORNAMENT_KIT(BANDOS_GODSWORD_ORNAMENT_KIT, BANDOS_GODSWORD_OR),
ITEM_ZAMORAK_GODSWORD(ZAMORAK_GODSWORD, ZAMORAK_GODSWORD_OR),
ITEM_ZAMORAK_GODSWORD_ORNAMENT_KIT(ZAMORAK_GODSWORD_ORNAMENT_KIT, ZAMORAK_GODSWORD_OR),
ITEM_SARADOMIN_GODSWORD(SARADOMIN_GODSWORD, SARADOMIN_GODSWORD_OR),
ITEM_SARADOMIN_GODSWORD_ORNAMENT_KIT(SARADOMIN_GODSWORD_ORNAMENT_KIT, SARADOMIN_GODSWORD_OR),
// Jewellery ornament kits
ITEM_AMULET_OF_TORTURE(AMULET_OF_TORTURE, AMULET_OF_TORTURE_OR),
ITEM_TORTURE_ORNAMENT_KIT(TORTURE_ORNAMENT_KIT, AMULET_OF_TORTURE_OR),
ITEM_NECKLACE_OF_ANGUISH(NECKLACE_OF_ANGUISH, NECKLACE_OF_ANGUISH_OR),
ITEM_ANGUISH_ORNAMENT_KIT(ANGUISH_ORNAMENT_KIT, NECKLACE_OF_ANGUISH_OR),
ITEM_OCCULT_NECKLACE(OCCULT_NECKLACE, OCCULT_NECKLACE_OR),
ITEM_OCCULT_ORNAMENT_KIT(OCCULT_ORNAMENT_KIT, OCCULT_NECKLACE_OR),
ITEM_AMULET_OF_FURY(AMULET_OF_FURY, AMULET_OF_FURY_OR),
ITEM_FURY_ORNAMENT_KIT(FURY_ORNAMENT_KIT, AMULET_OF_FURY_OR),
ITEM_TORMENTED_BRACELET(TORMENTED_BRACELET, TORMENTED_BRACELET_OR),
ITEM_TORMENTED_ORNAMENT_KIT(TORMENTED_ORNAMENT_KIT, TORMENTED_BRACELET_OR),
// Ensouled heads
ITEM_ENSOULED_GOBLIN_HEAD(ENSOULED_GOBLIN_HEAD_13448, ENSOULED_GOBLIN_HEAD),
ITEM_ENSOULED_MONKEY_HEAD(ENSOULED_MONKEY_HEAD_13451, ENSOULED_MONKEY_HEAD),
ITEM_ENSOULED_IMP_HEAD(ENSOULED_IMP_HEAD_13454, ENSOULED_IMP_HEAD),
ITEM_ENSOULED_MINOTAUR_HEAD(ENSOULED_MINOTAUR_HEAD_13457, ENSOULED_MINOTAUR_HEAD),
ITEM_ENSOULED_SCORPION_HEAD(ENSOULED_SCORPION_HEAD_13460, ENSOULED_SCORPION_HEAD),
ITEM_ENSOULED_BEAR_HEAD(ENSOULED_BEAR_HEAD_13463, ENSOULED_BEAR_HEAD),
ITEM_ENSOULED_UNICORN_HEAD(ENSOULED_UNICORN_HEAD_13466, ENSOULED_UNICORN_HEAD),
ITEM_ENSOULED_DOG_HEAD(ENSOULED_DOG_HEAD_13469, ENSOULED_DOG_HEAD),
ITEM_ENSOULED_CHAOS_DRUID_HEAD(ENSOULED_CHAOS_DRUID_HEAD_13472, ENSOULED_CHAOS_DRUID_HEAD),
ITEM_ENSOULED_GIANT_HEAD(ENSOULED_GIANT_HEAD_13475, ENSOULED_GIANT_HEAD),
ITEM_ENSOULED_OGRE_HEAD(ENSOULED_OGRE_HEAD_13478, ENSOULED_OGRE_HEAD),
ITEM_ENSOULED_ELF_HEAD(ENSOULED_ELF_HEAD_13481, ENSOULED_ELF_HEAD),
ITEM_ENSOULED_TROLL_HEAD(ENSOULED_TROLL_HEAD_13484, ENSOULED_TROLL_HEAD),
ITEM_ENSOULED_HORROR_HEAD(ENSOULED_HORROR_HEAD_13487, ENSOULED_HORROR_HEAD),
ITEM_ENSOULED_KALPHITE_HEAD(ENSOULED_KALPHITE_HEAD_13490, ENSOULED_KALPHITE_HEAD),
ITEM_ENSOULED_DAGANNOTH_HEAD(ENSOULED_DAGANNOTH_HEAD_13493, ENSOULED_DAGANNOTH_HEAD),
ITEM_ENSOULED_BLOODVELD_HEAD(ENSOULED_BLOODVELD_HEAD_13496, ENSOULED_BLOODVELD_HEAD),
ITEM_ENSOULED_TZHAAR_HEAD(ENSOULED_TZHAAR_HEAD_13499, ENSOULED_TZHAAR_HEAD),
ITEM_ENSOULED_DEMON_HEAD(ENSOULED_DEMON_HEAD_13502, ENSOULED_DEMON_HEAD),
ITEM_ENSOULED_AVIANSIE_HEAD(ENSOULED_AVIANSIE_HEAD_13505, ENSOULED_AVIANSIE_HEAD),
ITEM_ENSOULED_ABYSSAL_HEAD(ENSOULED_ABYSSAL_HEAD_13508, ENSOULED_ABYSSAL_HEAD),
ITEM_ENSOULED_DRAGON_HEAD(ENSOULED_DRAGON_HEAD_13511, ENSOULED_DRAGON_HEAD),
// Imbued rings
ITEM_BERSERKER_RING(BERSERKER_RING, BERSERKER_RING_I),
ITEM_SEERS_RING(SEERS_RING, SEERS_RING_I),
ITEM_WARRIOR_RING(WARRIOR_RING, WARRIOR_RING_I),
ITEM_ARCHERS_RING(ARCHERS_RING, ARCHERS_RING_I),
ITEM_TREASONOUS_RING(TREASONOUS_RING, TREASONOUS_RING_I),
ITEM_TYRANNICAL_RING(TYRANNICAL_RING, TYRANNICAL_RING_I),
ITEM_RING_OF_THE_GODS(RING_OF_THE_GODS, RING_OF_THE_GODS_I),
ITEM_RING_OF_SUFFERING(RING_OF_SUFFERING, RING_OF_SUFFERING_I, RING_OF_SUFFERING_R, RING_OF_SUFFERING_RI),
ITEM_GRANITE_RING(GRANITE_RING, GRANITE_RING_I),
// Bounty hunter
ITEM_GRANITE_MAUL(GRANITE_MAUL, GRANITE_MAUL_12848),
ITEM_MAGIC_SHORTBOW(MAGIC_SHORTBOW, MAGIC_SHORTBOW_I),
ITEM_SARADOMINS_BLESSED_SWORD(SARADOMINS_TEAR, SARADOMINS_BLESSED_SWORD),
// Jewellery with charges
ITEM_RING_OF_WEALTH(RING_OF_WEALTH, RING_OF_WEALTH_I, RING_OF_WEALTH_1, RING_OF_WEALTH_I1, RING_OF_WEALTH_2, RING_OF_WEALTH_I2, RING_OF_WEALTH_3, RING_OF_WEALTH_I3, RING_OF_WEALTH_4, RING_OF_WEALTH_I4, RING_OF_WEALTH_I5),
ITEM_AMULET_OF_GLORY(AMULET_OF_GLORY, AMULET_OF_GLORY1, AMULET_OF_GLORY2, AMULET_OF_GLORY3, AMULET_OF_GLORY5),
ITEM_AMULET_OF_GLORY_T(AMULET_OF_GLORY_T, AMULET_OF_GLORY_T1, AMULET_OF_GLORY_T2, AMULET_OF_GLORY_T3, AMULET_OF_GLORY_T5),
ITEM_SKILLS_NECKLACE(SKILLS_NECKLACE, SKILLS_NECKLACE1, SKILLS_NECKLACE2, SKILLS_NECKLACE3, SKILLS_NECKLACE5),
ITEM_RING_OF_DUELING(RING_OF_DUELING8, RING_OF_DUELING1, RING_OF_DUELING2, RING_OF_DUELING3, RING_OF_DUELING4, RING_OF_DUELING5, RING_OF_DUELING6, RING_OF_DUELING7),
ITEM_GAMES_NECKLACE(GAMES_NECKLACE8, GAMES_NECKLACE1, GAMES_NECKLACE2, GAMES_NECKLACE3, GAMES_NECKLACE4, GAMES_NECKLACE5, GAMES_NECKLACE6, GAMES_NECKLACE7),
// Degradable/charged weaponry/armour
ITEM_ABYSSAL_WHIP(ABYSSAL_WHIP, VOLCANIC_ABYSSAL_WHIP, FROZEN_ABYSSAL_WHIP),
ITEM_KRAKEN_TENTACLE(KRAKEN_TENTACLE, ABYSSAL_TENTACLE),
ITEM_TRIDENT_OF_THE_SEAS(UNCHARGED_TRIDENT, TRIDENT_OF_THE_SEAS),
ITEM_TRIDENT_OF_THE_SEAS_E(UNCHARGED_TRIDENT_E, TRIDENT_OF_THE_SEAS_E),
ITEM_TRIDENT_OF_THE_SWAMP(UNCHARGED_TOXIC_TRIDENT, TRIDENT_OF_THE_SWAMP),
ITEM_TRIDENT_OF_THE_SWAMP_E(UNCHARGED_TOXIC_TRIDENT_E, TRIDENT_OF_THE_SWAMP_E),
ITEM_TOXIC_BLOWPIPE(TOXIC_BLOWPIPE_EMPTY, TOXIC_BLOWPIPE),
ITEM_TOXIC_STAFF_OFF_THE_DEAD(TOXIC_STAFF_UNCHARGED, TOXIC_STAFF_OF_THE_DEAD),
ITEM_SERPENTINE_HELM(SERPENTINE_HELM_UNCHARGED, SERPENTINE_HELM, TANZANITE_HELM_UNCHARGED, TANZANITE_HELM, MAGMA_HELM_UNCHARGED, MAGMA_HELM),
ITEM_DRAGONFIRE_SHIELD(DRAGONFIRE_SHIELD_11284, DRAGONFIRE_SHIELD),
ITEM_DRAGONFIRE_WARD(DRAGONFIRE_WARD_22003, DRAGONFIRE_WARD),
ITEM_ANCIENT_WYVERN_SHIELD(ANCIENT_WYVERN_SHIELD_21634, ANCIENT_WYVERN_SHIELD),
ITEM_SANGUINESTI_STAFF(SANGUINESTI_STAFF_UNCHARGED, SANGUINESTI_STAFF),
ITEM_SCYTHE_OF_VITUR(SCYTHE_OF_VITUR_UNCHARGED, SCYTHE_OF_VITUR),
ITEM_TOME_OF_FIRE(TOME_OF_FIRE_EMPTY, TOME_OF_FIRE),
ITEM_CRAWS_BOW(CRAWS_BOW_U, CRAWS_BOW),
ITEM_VIGGORAS_CHAINMACE(VIGGORAS_CHAINMACE_U, VIGGORAS_CHAINMACE),
ITEM_THAMMARONS_SCEPTRE(THAMMARONS_SCEPTRE_U, THAMMARONS_SCEPTRE),
// Infinity colour kits
ITEM_INFINITY_TOP(INFINITY_TOP, INFINITY_TOP_10605, INFINITY_TOP_20574, DARK_INFINITY_TOP, LIGHT_INFINITY_TOP),
ITEM_INFINITY_TOP_LIGHT_COLOUR_KIT(LIGHT_INFINITY_COLOUR_KIT, LIGHT_INFINITY_TOP),
ITEM_INFINITY_TOP_DARK_COLOUR_KIT(DARK_INFINITY_COLOUR_KIT, DARK_INFINITY_TOP),
ITEM_INFINITY_BOTTOMS(INFINITY_BOTTOMS, INFINITY_BOTTOMS_20575, DARK_INFINITY_BOTTOMS, LIGHT_INFINITY_BOTTOMS),
ITEM_INFINITY_BOTTOMS_LIGHT_COLOUR_KIT(LIGHT_INFINITY_COLOUR_KIT, LIGHT_INFINITY_BOTTOMS),
ITEM_INFINITY_BOTTOMS_DARK_COLOUR_KIT(DARK_INFINITY_COLOUR_KIT, DARK_INFINITY_BOTTOMS),
ITEM_INFINITY_HAT(INFINITY_HAT, DARK_INFINITY_HAT, LIGHT_INFINITY_HAT),
ITEM_INFINITY_HAT_LIGHT_COLOUR_KIT(LIGHT_INFINITY_COLOUR_KIT, LIGHT_INFINITY_HAT),
ITEM_INFINITY_HAT_DARK_COLOUR_KIT(DARK_INFINITY_COLOUR_KIT, DARK_INFINITY_HAT),
// Miscellaneous ornament kits
ITEM_DARK_BOW(DARK_BOW, DARK_BOW_12765, DARK_BOW_12766, DARK_BOW_12767, DARK_BOW_12768, DARK_BOW_20408),
ITEM_ODIUM_WARD(ODIUM_WARD, ODIUM_WARD_12807),
ITEM_MALEDICTION_WARD(MALEDICTION_WARD, MALEDICTION_WARD_12806),
ITEM_STEAM_BATTLESTAFF(STEAM_BATTLESTAFF, STEAM_BATTLESTAFF_12795),
ITEM_LAVA_BATTLESTAFF(LAVA_BATTLESTAFF, LAVA_BATTLESTAFF_21198),
// Slayer helm/black mask
ITEM_BLACK_MASK(
BLACK_MASK, BLACK_MASK_I, BLACK_MASK_1, BLACK_MASK_1_I, BLACK_MASK_2, BLACK_MASK_2_I, BLACK_MASK_3, BLACK_MASK_3_I, BLACK_MASK_4, BLACK_MASK_4_I, BLACK_MASK_5,
BLACK_MASK_5_I, BLACK_MASK_6, BLACK_MASK_6_I, BLACK_MASK_7, BLACK_MASK_7_I, BLACK_MASK_8, BLACK_MASK_8_I, BLACK_MASK_9, BLACK_MASK_9_I, BLACK_MASK_10_I,
SLAYER_HELMET, SLAYER_HELMET_I, BLACK_SLAYER_HELMET, BLACK_SLAYER_HELMET_I, PURPLE_SLAYER_HELMET, PURPLE_SLAYER_HELMET_I, RED_SLAYER_HELMET, RED_SLAYER_HELMET_I,
GREEN_SLAYER_HELMET, GREEN_SLAYER_HELMET_I, TURQUOISE_SLAYER_HELMET, TURQUOISE_SLAYER_HELMET_I, HYDRA_SLAYER_HELMET, HYDRA_SLAYER_HELMET_I),
// Pharaoh's Sceptres
ITEM_PHARAOHS_SCEPTRE_1(PHARAOHS_SCEPTRE, PHARAOHS_SCEPTRE_1),
ITEM_PHARAOHS_SCEPTRE_2(PHARAOHS_SCEPTRE, PHARAOHS_SCEPTRE_2),
ITEM_PHARAOHS_SCEPTRE_4(PHARAOHS_SCEPTRE, PHARAOHS_SCEPTRE_4),
ITEM_PHARAOHS_SCEPTRE_5(PHARAOHS_SCEPTRE, PHARAOHS_SCEPTRE_5),
ITEM_PHARAOHS_SCEPTRE_6(PHARAOHS_SCEPTRE, PHARAOHS_SCEPTRE_6),
ITEM_PHARAOHS_SCEPTRE_7(PHARAOHS_SCEPTRE, PHARAOHS_SCEPTRE_7),
ITEM_PHARAOHS_SCEPTRE_8(PHARAOHS_SCEPTRE, PHARAOHS_SCEPTRE_8),
// Revertible items
ITEM_HYDRA_LEATHER(HYDRA_LEATHER, FEROCIOUS_GLOVES),
ITEM_HYDRA_TAIL(HYDRA_TAIL, BONECRUSHER_NECKLACE),
ITEM_DRAGONBONE_NECKLACE(DRAGONBONE_NECKLACE, BONECRUSHER_NECKLACE),
ITEM_BOTTOMLESS_COMPOST_BUCKET(BOTTOMLESS_COMPOST_BUCKET, BOTTOMLESS_COMPOST_BUCKET_22997);
private static final Multimap<Integer, Integer> MAPPINGS = HashMultimap.create();
private final int tradeableItem;
private final int[] untradableItems;
static
{
for (final ItemMapping item : values())
{
for (int itemId : item.untradableItems)
{
MAPPINGS.put(itemId, item.tradeableItem);
}
}
}
ItemMapping(int tradeableItem, int... untradableItems)
{
this.tradeableItem = tradeableItem;
this.untradableItems = untradableItems;
}
/**
* Get collection of items that are mapped from single item id.
*
* @param itemId the item id
* @return the collection
*/
public static Collection<Integer> map(int itemId)
{
final Collection<Integer> mapping = MAPPINGS.get(itemId);
if (mapping == null || mapping.isEmpty())
{
return Collections.singleton(itemId);
}
return mapping;
}
/**
* Map an item from its untradeable version to its tradeable version
*
* @param itemId
* @return
*/
public static int mapFirst(int itemId)
{
final Collection<Integer> mapping = MAPPINGS.get(itemId);
if (mapping == null || mapping.isEmpty())
{
return itemId;
}
return mapping.iterator().next();
}
}

View File

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

View File

@@ -0,0 +1,78 @@
/*
* Copyright (c) 2018, Ron Young <https://github.com/raiyni>
* 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 com.google.common.collect.ImmutableMap;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
/**
* Converts variation items to it's base item counterparts
*/
public class ItemVariationMapping
{
private static final Map<Integer, Integer> MAPPINGS;
static
{
final Gson gson = new Gson();
final TypeToken<Map<String, Collection<Integer>>> typeToken = new TypeToken<Map<String, Collection<Integer>>>()
{
};
final InputStream geLimitData = ItemVariationMapping.class.getResourceAsStream("/item_variations.json");
final Map<String, Collection<Integer>> itemVariations = gson.fromJson(new InputStreamReader(geLimitData), typeToken.getType());
ImmutableMap.Builder<Integer, Integer> builder = new ImmutableMap.Builder<>();
for (Collection<Integer> value : itemVariations.values())
{
final Iterator<Integer> iterator = value.iterator();
final int base = iterator.next();
while (iterator.hasNext())
{
builder.put(iterator.next(), base);
}
}
MAPPINGS = builder.build();
}
/**
* Get base item id for provided variation item id.
*
* @param itemId the item id
* @return the base item id
*/
public static int map(int itemId)
{
return MAPPINGS.getOrDefault(itemId, itemId);
}
}

View File

@@ -0,0 +1,319 @@
/*
* Copyright (c) 2018, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.client.game;
import api.AnimationID;
import api.Client;
import api.Item;
import api.ItemID;
import api.NPC;
import api.NpcID;
import api.Player;
import api.Tile;
import api.coords.LocalPoint;
import api.coords.WorldPoint;
import api.events.AnimationChanged;
import api.events.GameTick;
import api.events.ItemDespawned;
import api.events.ItemQuantityChanged;
import api.events.ItemSpawned;
import api.events.NpcDespawned;
import api.events.PlayerDespawned;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ListMultimap;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.inject.Inject;
import javax.inject.Singleton;
import lombok.extern.slf4j.Slf4j;
import net.runelite.client.eventbus.EventBus;
import net.runelite.client.eventbus.Subscribe;
import net.runelite.client.events.NpcLootReceived;
import net.runelite.client.events.PlayerLootReceived;
@Singleton
@Slf4j
public class LootManager
{
private static final Map<Integer, Integer> NPC_DEATH_ANIMATIONS = ImmutableMap.of(
NpcID.CAVE_KRAKEN, AnimationID.CAVE_KRAKEN_DEATH
);
private final EventBus eventBus;
private final Client client;
private final ListMultimap<Integer, ItemStack> itemSpawns = ArrayListMultimap.create();
private final Set<LocalPoint> killPoints = new HashSet<>();
private WorldPoint playerLocationLastTick;
private WorldPoint krakenPlayerLocation;
@Inject
private LootManager(EventBus eventBus, Client client)
{
this.eventBus = eventBus;
this.client = client;
}
@Subscribe
public void onNpcDespawned(NpcDespawned npcDespawned)
{
final NPC npc = npcDespawned.getNpc();
if (!npc.isDead())
{
int id = npc.getId();
switch (id)
{
case NpcID.GARGOYLE:
case NpcID.GARGOYLE_413:
case NpcID.GARGOYLE_1543:
case NpcID.MARBLE_GARGOYLE:
case NpcID.MARBLE_GARGOYLE_7408:
case NpcID.DUSK_7888:
case NpcID.DUSK_7889:
case NpcID.ROCKSLUG:
case NpcID.ROCKSLUG_422:
case NpcID.GIANT_ROCKSLUG:
case NpcID.SMALL_LIZARD:
case NpcID.SMALL_LIZARD_463:
case NpcID.DESERT_LIZARD:
case NpcID.DESERT_LIZARD_460:
case NpcID.DESERT_LIZARD_461:
case NpcID.LIZARD:
case NpcID.ZYGOMITE:
case NpcID.ZYGOMITE_474:
case NpcID.ANCIENT_ZYGOMITE:
// these monsters die with >0 hp, so we just look for coincident
// item spawn with despawn
break;
default:
return;
}
}
processNpcLoot(npc);
}
@Subscribe
public void onPlayerDespawned(PlayerDespawned playerDespawned)
{
final Player player = playerDespawned.getPlayer();
// Only care about dead Players
if (player.getHealthRatio() != 0)
{
return;
}
final LocalPoint location = LocalPoint.fromWorld(client, player.getWorldLocation());
if (location == null || killPoints.contains(location))
{
return;
}
final int x = location.getSceneX();
final int y = location.getSceneY();
final int packed = x << 8 | y;
final Collection<ItemStack> items = itemSpawns.get(packed);
if (items.isEmpty())
{
return;
}
killPoints.add(location);
eventBus.post(new PlayerLootReceived(player, items));
}
@Subscribe
public void onItemSpawned(ItemSpawned itemSpawned)
{
final Item item = itemSpawned.getItem();
final Tile tile = itemSpawned.getTile();
final LocalPoint location = tile.getLocalLocation();
final int packed = location.getSceneX() << 8 | location.getSceneY();
itemSpawns.put(packed, new ItemStack(item.getId(), item.getQuantity(), location));
log.debug("Item spawn {} ({}) location {},{}", item.getId(), item.getQuantity(), location);
}
@Subscribe
public void onItemDespawned(ItemDespawned itemDespawned)
{
final Item item = itemDespawned.getItem();
final LocalPoint location = itemDespawned.getTile().getLocalLocation();
log.debug("Item despawn {} ({}) location {},{}", item.getId(), item.getQuantity(), location);
}
@Subscribe
public void onItemQuantityChanged(ItemQuantityChanged itemQuantityChanged)
{
final Item item = itemQuantityChanged.getItem();
final Tile tile = itemQuantityChanged.getTile();
final LocalPoint location = tile.getLocalLocation();
final int packed = location.getSceneX() << 8 | location.getSceneY();
final int diff = itemQuantityChanged.getNewQuantity() - itemQuantityChanged.getOldQuantity();
if (diff <= 0)
{
return;
}
itemSpawns.put(packed, new ItemStack(item.getId(), diff, location));
}
@Subscribe
public void onAnimationChanged(AnimationChanged e)
{
if (!(e.getActor() instanceof NPC))
{
return;
}
final NPC npc = (NPC) e.getActor();
int id = npc.getId();
// We only care about certain NPCs
final Integer deathAnim = NPC_DEATH_ANIMATIONS.get(id);
// Current animation is death animation?
if (deathAnim != null && deathAnim == npc.getAnimation())
{
if (id == NpcID.CAVE_KRAKEN)
{
// Big Kraken drops loot wherever player is standing when animation starts.
krakenPlayerLocation = client.getLocalPlayer().getWorldLocation();
}
else
{
// These NPCs drop loot on death animation, which is right now.
processNpcLoot(npc);
}
}
}
@Subscribe
public void onGameTick(GameTick gameTick)
{
playerLocationLastTick = client.getLocalPlayer().getWorldLocation();
itemSpawns.clear();
killPoints.clear();
}
private void processNpcLoot(NPC npc)
{
final LocalPoint location = LocalPoint.fromWorld(client, getDropLocation(npc, npc.getWorldLocation()));
if (location == null || killPoints.contains(location))
{
return;
}
final int x = location.getSceneX();
final int y = location.getSceneY();
final int size = npc.getDefinition().getSize();
// Some NPCs drop items onto multiple tiles
final List<ItemStack> allItems = new ArrayList<>();
for (int i = 0; i < size; ++i)
{
for (int j = 0; j < size; ++j)
{
final int packed = (x + i) << 8 | (y + j);
final Collection<ItemStack> items = itemSpawns.get(packed);
allItems.addAll(items);
}
}
if (allItems.isEmpty())
{
return;
}
killPoints.add(location);
eventBus.post(new NpcLootReceived(npc, allItems));
}
private WorldPoint getDropLocation(NPC npc, WorldPoint worldLocation)
{
switch (npc.getId())
{
case NpcID.KRAKEN:
case NpcID.KRAKEN_6640:
case NpcID.KRAKEN_6656:
worldLocation = playerLocationLastTick;
break;
case NpcID.CAVE_KRAKEN:
worldLocation = krakenPlayerLocation;
break;
case NpcID.ZULRAH: // Green
case NpcID.ZULRAH_2043: // Red
case NpcID.ZULRAH_2044: // Blue
for (Map.Entry<Integer, ItemStack> entry : itemSpawns.entries())
{
if (entry.getValue().getId() == ItemID.ZULRAHS_SCALES)
{
int packed = entry.getKey();
int unpackedX = packed >> 8;
int unpackedY = packed & 0xFF;
worldLocation = WorldPoint.fromScene(client, unpackedX, unpackedY, worldLocation.getPlane());
break;
}
}
break;
case NpcID.VORKATH:
case NpcID.VORKATH_8058:
case NpcID.VORKATH_8059:
case NpcID.VORKATH_8060:
case NpcID.VORKATH_8061:
int x = worldLocation.getX() + 3;
int y = worldLocation.getY() + 3;
if (playerLocationLastTick.getX() < x)
{
x -= 4;
}
else if (playerLocationLastTick.getX() > x)
{
x += 4;
}
if (playerLocationLastTick.getY() < y)
{
y -= 4;
}
else if (playerLocationLastTick.getY() > y)
{
y += 4;
}
worldLocation = new WorldPoint(x, y, worldLocation.getPlane());
break;
}
return worldLocation;
}
}

View File

@@ -0,0 +1,103 @@
/*
* Copyright (c) 2018, Adam <Adam@sigterm.info>
* Copyright (c) 2019, TheStonedTurtle <https://github.com/TheStonedTurtle>
* 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 com.google.common.collect.ImmutableMap;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Type;
import java.util.Map;
import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.inject.Singleton;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Singleton
public class NPCManager
{
private final ImmutableMap<Integer, NPCStats> statsMap;
@Inject
private NPCManager()
{
final Gson gson = new Gson();
final Type typeToken = new TypeToken<Map<Integer, NPCStats>>()
{
}.getType();
final InputStream statsFile = getClass().getResourceAsStream("/npc_stats.json");
final Map<Integer, NPCStats> stats = gson.fromJson(new InputStreamReader(statsFile), typeToken);
statsMap = ImmutableMap.copyOf(stats);
}
/**
* Returns the {@link NPCStats} for target NPC id
* @param npcId NPC id
* @return the {@link NPCStats} or null if unknown
*/
@Nullable
public NPCStats getStats(final int npcId)
{
return statsMap.get(npcId);
}
/**
* Returns health for target NPC ID
* @param npcId NPC id
* @return health or null if unknown
*/
@Nullable
public Integer getHealth(final int npcId)
{
final NPCStats s = statsMap.get(npcId);
if (s == null || s.getHitpoints() == -1)
{
return null;
}
return s.getHitpoints();
}
/**
* Returns the exp modifier for target NPC ID based on its stats.
* @param npcId NPC id
* @return npcs exp modifier. Assumes default xp rate if npc stats are unknown (returns 1)
*/
public double getXpModifier(final int npcId)
{
final NPCStats s = statsMap.get(npcId);
if (s == null)
{
return 1;
}
return s.calculateXpModifier();
}
}

View File

@@ -0,0 +1,79 @@
/*
* Copyright (c) 2019, TheStonedTurtle <https://github.com/TheStonedTurtle>
* 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.Value;
@Value
public class NPCStats
{
private final String name;
private final int hitpoints;
private final int combatLevel;
private final int slayerLevel;
private final int attackLevel;
private final int strengthLevel;
private final int defenceLevel;
private final int rangeLevel;
private final int magicLevel;
private final int stab;
private final int slash;
private final int crush;
private final int range;
private final int magic;
private final int stabDef;
private final int slashDef;
private final int crushDef;
private final int rangeDef;
private final int magicDef;
private final int bonusAttack;
private final int bonusStrength;
private final int bonusRangeStrength;
private final int bonusMagicDamage;
private final boolean poisonImmune;
private final boolean venomImmune;
private final boolean dragon;
private final boolean demon;
private final boolean undead;
/**
* Based off the formula found here: http://services.runescape.com/m=forum/c=PLuJ4cy6gtA/forums.ws?317,318,712,65587452,209,337584542#209
* @return bonus XP modifier
*/
public double calculateXpModifier()
{
final double averageLevel = Math.floor((attackLevel + strengthLevel + defenceLevel + hitpoints) / 4);
final double averageDefBonus = Math.floor((stabDef + slashDef + crushDef) / 3);
return (1 + Math.floor(averageLevel * (averageDefBonus + bonusStrength + bonusAttack) / 5120) / 40);
}
}

View File

@@ -0,0 +1,64 @@
/*
* Copyright (c) 2018, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.client.game;
import api.Skill;
import java.awt.image.BufferedImage;
import javax.inject.Singleton;
import lombok.extern.slf4j.Slf4j;
import net.runelite.client.util.ImageUtil;
@Singleton
@Slf4j
public class SkillIconManager
{
// * 2 to account for the small version of each icon
private final BufferedImage[] imgCache = new BufferedImage[Skill.values().length * 2];
public BufferedImage getSkillImage(Skill skill, boolean small)
{
int skillIdx = skill.ordinal() + (small ? Skill.values().length : 0);
BufferedImage skillImage = null;
if (imgCache[skillIdx] != null)
{
return imgCache[skillIdx];
}
String skillIconPath = (small ? "/skill_icons_small/" : "/skill_icons/")
+ skill.getName().toLowerCase() + ".png";
log.debug("Loading skill icon from {}", skillIconPath);
skillImage = ImageUtil.getResourceStreamFromClass(getClass(), skillIconPath);
imgCache[skillIdx] = skillImage;
return skillImage;
}
public BufferedImage getSkillImage(Skill skill)
{
return getSkillImage(skill, false);
}
}

View File

@@ -0,0 +1,166 @@
/*
* 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 api.Client;
import api.GameState;
import api.Sprite;
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;
import javax.inject.Singleton;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;
import lombok.extern.slf4j.Slf4j;
import net.runelite.client.callback.ClientThread;
import net.runelite.client.util.ImageUtil;
@Slf4j
@Singleton
public class SpriteManager
{
@Inject
private Client client;
@Inject
private ClientThread clientThread;
public Cache<Long, BufferedImage> cache = CacheBuilder.newBuilder()
.maximumSize(128L)
.expireAfterAccess(1, TimeUnit.HOURS)
.build();
@Nullable
public BufferedImage getSprite(int archive, int file)
{
assert client.isClientThread();
if (client.getGameState().ordinal() < GameState.LOGIN_SCREEN.ordinal())
{
return null;
}
Long key = (long) archive << 32 | file;
BufferedImage cached = cache.getIfPresent(key);
if (cached != null)
{
return cached;
}
Sprite[] sp = client.getSprites(client.getIndexSprites(), archive, 0);
BufferedImage img = sp[file].toBufferedImage();
cache.put(key, img);
return img;
}
public void getSpriteAsync(int archive, int file, Consumer<BufferedImage> user)
{
BufferedImage cached = cache.getIfPresent((long) archive << 32 | file);
if (cached != null)
{
user.accept(cached);
return;
}
clientThread.invoke(() ->
{
BufferedImage img = getSprite(archive, file);
if (img == null)
{
// Cache isn't loaded yet
return false;
}
user.accept(img);
return true;
});
}
/**
* Calls setIcon on c, ensuring it is repainted when this changes
*/
public void addSpriteTo(JButton c, int archive, int file)
{
getSpriteAsync(archive, file, img ->
{
SwingUtilities.invokeLater(() ->
{
c.setIcon(new ImageIcon(img));
});
});
}
/**
* Calls setIcon on c, ensuring it is repainted when this changes
*/
public void addSpriteTo(JLabel c, int archive, int file)
{
getSpriteAsync(archive, file, img ->
{
SwingUtilities.invokeLater(() ->
{
c.setIcon(new ImageIcon(img));
});
});
}
public void addSpriteOverrides(SpriteOverride[] add)
{
if (add.length <= 0)
{
return;
}
clientThread.invokeLater(() ->
{
Map<Integer, Sprite> overrides = client.getSpriteOverrides();
Class<?> owner = add[0].getClass();
for (SpriteOverride o : add)
{
BufferedImage image = ImageUtil.getResourceStreamFromClass(owner, o.getFileName());
Sprite sp = ImageUtil.getImageSpritePixels(image, client);
overrides.put(o.getSpriteId(), sp);
}
});
}
public void removeSpriteOverrides(SpriteOverride[] remove)
{
clientThread.invokeLater(() ->
{
Map<Integer, Sprite> overrides = client.getSpriteOverrides();
for (SpriteOverride o : remove)
{
overrides.remove(o.getSpriteId());
}
});
}
}

View File

@@ -0,0 +1,41 @@
/*
* 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;
public interface SpriteOverride
{
/**
* An ID for a sprite. Negative numbers are used by RuneLite specific sprites
*
* @see api.SpriteID
*/
int getSpriteId();
/**
* The file name for the resource to be loaded, relative to the implementing class
*/
String getFileName();
}

View File

@@ -0,0 +1,72 @@
/*
* Copyright (c) 2018, TheStonedTurtle <https://github.com/TheStonedTurtle>
* 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 api.ItemID;
import com.google.common.collect.ImmutableMap;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Getter
@RequiredArgsConstructor
public enum UntradeableItemMapping
{
MARK_OF_GRACE(ItemID.MARK_OF_GRACE, 10, ItemID.AMYLASE_CRYSTAL),
GRACEFUL_HOOD(ItemID.GRACEFUL_HOOD, 28, ItemID.MARK_OF_GRACE),
GRACEFUL_TOP(ItemID.GRACEFUL_TOP, 44, ItemID.MARK_OF_GRACE),
GRACEFUL_LEGS(ItemID.GRACEFUL_LEGS, 48, ItemID.MARK_OF_GRACE),
GRACEFUL_GLOVES(ItemID.GRACEFUL_GLOVES, 24, ItemID.MARK_OF_GRACE),
GRACEFUL_BOOTS(ItemID.GRACEFUL_BOOTS, 32, ItemID.MARK_OF_GRACE),
GRACEFUL_CAPE(ItemID.GRACEFUL_CAPE, 32, ItemID.MARK_OF_GRACE),
// 10 golden nuggets = 100 soft clay
GOLDEN_NUGGET(ItemID.GOLDEN_NUGGET, 10, ItemID.SOFT_CLAY),
PROSPECTOR_HELMET(ItemID.PROSPECTOR_HELMET, 32, ItemID.GOLDEN_NUGGET),
PROSPECTOR_JACKET(ItemID.PROSPECTOR_JACKET, 48, ItemID.GOLDEN_NUGGET),
PROSPECTOR_LEGS(ItemID.PROSPECTOR_LEGS, 40, ItemID.GOLDEN_NUGGET),
PROSPECTOR_BOOTS(ItemID.PROSPECTOR_BOOTS, 24, ItemID.GOLDEN_NUGGET);
private static final ImmutableMap<Integer, UntradeableItemMapping> UNTRADEABLE_RECLAIM_MAP;
private final int itemID;
private final int quantity;
private final int priceID;
static
{
ImmutableMap.Builder<Integer, UntradeableItemMapping> map = ImmutableMap.builder();
for (UntradeableItemMapping p : values())
{
map.put(p.getItemID(), p);
}
UNTRADEABLE_RECLAIM_MAP = map.build();
}
public static UntradeableItemMapping map(int itemId)
{
return UNTRADEABLE_RECLAIM_MAP.get(itemId);
}
}

View File

@@ -0,0 +1,39 @@
/*
* 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.chatbox;
/**
* A modal input that lives in the chatbox panel.
*/
public abstract class ChatboxInput
{
protected void open()
{
}
protected void close()
{
}
}

View File

@@ -0,0 +1,202 @@
/*
* 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.chatbox;
import api.Client;
import api.GameState;
import api.ScriptID;
import api.VarClientInt;
import api.events.GameStateChanged;
import api.events.ScriptCallbackEvent;
import api.vars.InputType;
import api.widgets.JavaScriptCallback;
import api.widgets.Widget;
import api.widgets.WidgetInfo;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import net.runelite.client.callback.ClientThread;
import net.runelite.client.eventbus.EventBus;
import net.runelite.client.eventbus.Subscribe;
import net.runelite.client.input.KeyListener;
import net.runelite.client.input.KeyManager;
import net.runelite.client.input.MouseListener;
import net.runelite.client.input.MouseManager;
import net.runelite.client.input.MouseWheelListener;
@Singleton
@Slf4j
public class ChatboxPanelManager
{
private final Client client;
private final ClientThread clientThread;
private final EventBus eventBus;
private final KeyManager keyManager;
private final MouseManager mouseManager;
private final Provider<ChatboxTextMenuInput> chatboxTextMenuInputProvider;
private final Provider<ChatboxTextInput> chatboxTextInputProvider;
@Getter
private ChatboxInput currentInput = null;
@Inject
private ChatboxPanelManager(EventBus eventBus, Client client, ClientThread clientThread,
KeyManager keyManager, MouseManager mouseManager,
Provider<ChatboxTextMenuInput> chatboxTextMenuInputProvider, Provider<ChatboxTextInput> chatboxTextInputProvider)
{
this.client = client;
this.clientThread = clientThread;
this.eventBus = eventBus;
this.keyManager = keyManager;
this.mouseManager = mouseManager;
this.chatboxTextMenuInputProvider = chatboxTextMenuInputProvider;
this.chatboxTextInputProvider = chatboxTextInputProvider;
}
public void close()
{
clientThread.invokeLater(this::unsafeCloseInput);
}
private void unsafeCloseInput()
{
client.runScript(
ScriptID.RESET_CHATBOX_INPUT,
0,
1
);
if (currentInput != null)
{
killCurrentPanel();
}
}
private void unsafeOpenInput(ChatboxInput input)
{
client.runScript(ScriptID.CLEAR_CHATBOX_PANEL);
eventBus.register(input);
if (input instanceof KeyListener)
{
keyManager.registerKeyListener((KeyListener) input);
}
if (input instanceof MouseListener)
{
mouseManager.registerMouseListener((MouseListener) input);
}
if (input instanceof MouseWheelListener)
{
mouseManager.registerMouseWheelListener((MouseWheelListener) input);
}
if (currentInput != null)
{
killCurrentPanel();
}
currentInput = input;
client.setVar(VarClientInt.INPUT_TYPE, InputType.RUNELITE_CHATBOX_PANEL.getType());
client.getWidget(WidgetInfo.CHATBOX_TITLE).setHidden(true);
client.getWidget(WidgetInfo.CHATBOX_FULL_INPUT).setHidden(true);
Widget c = getContainerWidget();
c.deleteAllChildren();
c.setOnDialogAbortListener((JavaScriptCallback) ev -> this.unsafeCloseInput());
input.open();
}
public void openInput(ChatboxInput input)
{
clientThread.invokeLater(() -> unsafeOpenInput(input));
}
public ChatboxTextMenuInput openTextMenuInput(String title)
{
return chatboxTextMenuInputProvider.get()
.title(title);
}
public ChatboxTextInput openTextInput(String prompt)
{
return chatboxTextInputProvider.get()
.prompt(prompt);
}
@Subscribe
public void onScriptCallbackEvent(ScriptCallbackEvent ev)
{
if (currentInput != null && "resetChatboxInput".equals(ev.getEventName()))
{
killCurrentPanel();
}
}
@Subscribe
private void onGameStateChanged(GameStateChanged ev)
{
if (currentInput != null && ev.getGameState() == GameState.LOGIN_SCREEN)
{
killCurrentPanel();
}
}
private void killCurrentPanel()
{
try
{
currentInput.close();
}
catch (Exception e)
{
log.warn("Exception closing {}", currentInput.getClass(), e);
}
eventBus.unregister(currentInput);
if (currentInput instanceof KeyListener)
{
keyManager.unregisterKeyListener((KeyListener) currentInput);
}
if (currentInput instanceof MouseListener)
{
mouseManager.unregisterMouseListener((MouseListener) currentInput);
}
if (currentInput instanceof MouseWheelListener)
{
mouseManager.unregisterMouseWheelListener((MouseWheelListener) currentInput);
}
currentInput = null;
}
public Widget getContainerWidget()
{
return client.getWidget(WidgetInfo.CHATBOX_CONTAINER);
}
}

View File

@@ -0,0 +1,865 @@
/*
* 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.chatbox;
import api.FontID;
import api.FontTypeFace;
import api.widgets.JavaScriptCallback;
import api.widgets.Widget;
import api.widgets.WidgetPositionMode;
import api.widgets.WidgetSizeMode;
import api.widgets.WidgetTextAlignment;
import api.widgets.WidgetType;
import com.google.common.base.Strings;
import com.google.inject.Inject;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.StringSelection;
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.client.callback.ClientThread;
import net.runelite.client.input.KeyListener;
import net.runelite.client.input.MouseListener;
import net.runelite.client.util.MiscUtils;
import net.runelite.client.util.Text;
@Slf4j
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;
private static IntPredicate getDefaultCharValidator()
{
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 cursorStart = 0;
@Getter
private int cursorEnd = 0;
private int selectionStart = -1;
private int selectionEnd = -1;
@Getter
private IntPredicate charValidator = getDefaultCharValidator();
@Getter
private Runnable onClose = null;
@Getter
private Consumer<String> onDone = null;
@Getter
private Consumer<String> onChanged = null;
@Getter
private int fontID = FontID.QUILL_8;
@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)
{
this.chatboxPanelManager = chatboxPanelManager;
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;
if (built)
{
clientThread.invoke(this::update);
}
return this;
}
public ChatboxTextInput value(String value)
{
this.value = new StringBuffer(value);
cursorAt(this.value.length());
return this;
}
public ChatboxTextInput cursorAt(int index)
{
return cursorAt(index, index);
}
public ChatboxTextInput cursorAt(int indexA, int indexB)
{
if (indexA < 0)
{
indexA = 0;
}
if (indexB < 0)
{
indexB = 0;
}
if (indexA > value.length())
{
indexA = value.length();
}
if (indexB > value.length())
{
indexB = value.length();
}
int start = indexA;
int end = indexB;
if (start > end)
{
int v = start;
start = end;
end = v;
}
this.cursorStart = start;
this.cursorEnd = end;
if (built)
{
clientThread.invoke(this::update);
}
return this;
}
public String getValue()
{
return value.toString();
}
public ChatboxTextInput charValidator(IntPredicate val)
{
if (val == null)
{
val = getDefaultCharValidator();
}
this.charValidator = val;
return this;
}
public ChatboxTextInput onClose(Runnable onClose)
{
this.onClose = onClose;
return this;
}
public ChatboxTextInput onDone(Consumer<String> onDone)
{
this.onDone = onDone;
return this;
}
public ChatboxTextInput onChanged(Consumer<String> onChanged)
{
this.onChanged = onChanged;
return this;
}
public ChatboxTextInput fontID(int fontID)
{
this.fontID = fontID;
return this;
}
protected void update()
{
Widget container = chatboxPanelManager.getContainerWidget();
container.deleteAllChildren();
Widget promptWidget = container.createChild(-1, WidgetType.TEXT);
promptWidget.setText(this.prompt);
promptWidget.setTextColor(0x800000);
promptWidget.setFontId(fontID);
promptWidget.setXPositionMode(WidgetPositionMode.ABSOLUTE_CENTER);
promptWidget.setOriginalX(0);
promptWidget.setYPositionMode(WidgetPositionMode.ABSOLUTE_TOP);
promptWidget.setOriginalY(8);
promptWidget.setOriginalHeight(24);
promptWidget.setXTextAlignment(WidgetTextAlignment.CENTER);
promptWidget.setYTextAlignment(WidgetTextAlignment.CENTER);
promptWidget.setWidthMode(WidgetSizeMode.MINUS);
promptWidget.revalidate();
buildEdit(0, 50, container.getWidth(), 0);
}
protected void buildEdit(int x, int y, int w, int h)
{
final List<Line> editLines = new ArrayList<>();
Widget container = chatboxPanelManager.getContainerWidget();
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();
}
final int oy = y;
final int ox = x;
final int oh = h;
int breakIndex = -1;
final StringBuilder sb = new StringBuilder();
for (int i = 0; i < value.length(); i++)
{
int count = i - sb.length();
final String c = value.charAt(i) + "";
sb.append(c);
if (BREAK_MATCHER.matcher(c).matches())
{
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(), "");
}
}
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();
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 = MiscUtils.clamp(cursorStart - line.start, 0, len);
final int ceIdx = MiscUtils.clamp(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;
}
api.Point ccl = container.getCanvasLocation();
isInBounds = ev -> bounds.contains(new Point(ev.getX() - ccl.getX(), ev.getY() - ccl.getY()));
getPointCharOffset = p ->
{
if (bounds.width <= 0)
{
return 0;
}
int cx = p.x - ccl.getX() - ox;
int cy = p.y - ccl.getY() - oy;
int currentLine = MiscUtils.clamp(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--)
{
int lcx = charIndex > 0 ? font.getTextWidth(Text.escapeJagex(tsValue.substring(0, charIndex - 1))) : 0;
int mcx = font.getTextWidth(Text.escapeJagex(tsValue.substring(0, charIndex)));
int rcx = charIndex + 1 <= tsValue.length() ? font.getTextWidth(Text.escapeJagex(tsValue.substring(0, charIndex + 1))) : mcx;
int leftBound = (lcx + mcx) / 2;
int rightBound = (mcx + rcx) / 2;
if (cx < leftBound)
{
charIndex--;
continue;
}
if (cx > rightBound)
{
charIndex++;
continue;
}
break;
}
charIndex = MiscUtils.clamp(charIndex, 0, tsValue.length());
return line.start + charIndex;
};
getLineOffset = code ->
{
if (editLines.size() < 2)
{
return cursorStart;
}
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();
}
@Override
protected void close()
{
if (this.onClose != null)
{
this.onClose.run();
}
}
public ChatboxTextInput build()
{
if (prompt == null)
{
throw new IllegalStateException("prompt must be non-null");
}
chatboxPanelManager.openInput(this);
return this;
}
@Override
public void keyTyped(KeyEvent e)
{
char c = e.getKeyChar();
if (charValidator.test(c))
{
if (cursorStart != cursorEnd)
{
value.delete(cursorStart, cursorEnd);
}
value.insert(cursorStart, c);
cursorAt(cursorStart + 1);
if (onChanged != null)
{
onChanged.accept(getValue());
}
}
}
@Override
public void keyPressed(KeyEvent ev)
{
int code = ev.getKeyCode();
if (ev.isControlDown())
{
switch (code)
{
case KeyEvent.VK_X:
case KeyEvent.VK_C:
if (cursorStart != cursorEnd)
{
String s = value.substring(cursorStart, cursorEnd);
if (code == KeyEvent.VK_X)
{
value.delete(cursorStart, cursorEnd);
cursorAt(cursorStart);
}
Toolkit.getDefaultToolkit()
.getSystemClipboard()
.setContents(new StringSelection(s), null);
}
return;
case KeyEvent.VK_V:
try
{
String s = Toolkit.getDefaultToolkit()
.getSystemClipboard()
.getData(DataFlavor.stringFlavor)
.toString();
if (cursorStart != cursorEnd)
{
value.delete(cursorStart, cursorEnd);
}
for (int i = 0; i < s.length(); i++)
{
char ch = s.charAt(i);
if (charValidator.test(ch))
{
value.insert(cursorStart, ch);
cursorStart++;
}
}
cursorAt(cursorStart);
if (onChanged != null)
{
onChanged.accept(getValue());
}
}
catch (IOException | UnsupportedFlavorException ex)
{
log.warn("Unable to get clipboard", ex);
}
return;
}
return;
}
int newPos = cursorStart;
if (ev.isShiftDown())
{
if (selectionEnd == -1 || selectionStart == -1)
{
selectionStart = cursorStart;
selectionEnd = cursorStart;
}
newPos = selectionEnd;
}
else
{
selectionStart = -1;
selectionEnd = -1;
}
switch (code)
{
case KeyEvent.VK_DELETE:
if (cursorStart != cursorEnd)
{
value.delete(cursorStart, cursorEnd);
cursorAt(cursorStart);
if (onChanged != null)
{
onChanged.accept(getValue());
}
return;
}
if (cursorStart < value.length())
{
value.deleteCharAt(cursorStart);
cursorAt(cursorStart);
if (onChanged != null)
{
onChanged.accept(getValue());
}
}
return;
case KeyEvent.VK_BACK_SPACE:
if (cursorStart != cursorEnd)
{
value.delete(cursorStart, cursorEnd);
cursorAt(cursorStart);
if (onChanged != null)
{
onChanged.accept(getValue());
}
return;
}
if (cursorStart > 0)
{
value.deleteCharAt(cursorStart - 1);
cursorAt(cursorStart - 1);
if (onChanged != null)
{
onChanged.accept(getValue());
}
}
return;
case KeyEvent.VK_LEFT:
ev.consume();
newPos--;
break;
case KeyEvent.VK_RIGHT:
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;
break;
case KeyEvent.VK_END:
ev.consume();
newPos = value.length();
break;
case KeyEvent.VK_ENTER:
ev.consume();
if (onDone != null)
{
onDone.accept(getValue());
}
chatboxPanelManager.close();
return;
case KeyEvent.VK_ESCAPE:
ev.consume();
if (cursorStart != cursorEnd)
{
cursorAt(cursorStart);
return;
}
chatboxPanelManager.close();
return;
default:
return;
}
if (newPos > value.length())
{
newPos = value.length();
}
if (newPos < 0)
{
newPos = 0;
}
if (ev.isShiftDown())
{
selectionEnd = newPos;
cursorAt(selectionStart, newPos);
}
else
{
cursorAt(newPos);
}
}
@Override
public void keyReleased(KeyEvent e)
{
}
@Override
public MouseEvent mouseClicked(MouseEvent mouseEvent)
{
return mouseEvent;
}
@Override
public MouseEvent mousePressed(MouseEvent mouseEvent)
{
if (mouseEvent.getButton() != MouseEvent.BUTTON1)
{
return mouseEvent;
}
if (isInBounds == null || !isInBounds.test(mouseEvent))
{
if (cursorStart != cursorEnd)
{
selectionStart = -1;
selectionEnd = -1;
cursorAt(getCharOffset(mouseEvent));
}
return mouseEvent;
}
int nco = getCharOffset(mouseEvent);
if (mouseEvent.isShiftDown() && selectionEnd != -1)
{
selectionEnd = nco;
cursorAt(selectionStart, selectionEnd);
}
else
{
selectionStart = nco;
cursorAt(nco);
}
return mouseEvent;
}
@Override
public MouseEvent mouseReleased(MouseEvent mouseEvent)
{
return mouseEvent;
}
@Override
public MouseEvent mouseEntered(MouseEvent mouseEvent)
{
return mouseEvent;
}
@Override
public MouseEvent mouseExited(MouseEvent mouseEvent)
{
return mouseEvent;
}
@Override
public MouseEvent mouseDragged(MouseEvent mouseEvent)
{
if (!SwingUtilities.isLeftMouseButton(mouseEvent))
{
return mouseEvent;
}
int nco = getCharOffset(mouseEvent);
if (selectionStart != -1)
{
selectionEnd = nco;
cursorAt(selectionStart, selectionEnd);
}
return mouseEvent;
}
@Override
public MouseEvent mouseMoved(MouseEvent mouseEvent)
{
return mouseEvent;
}
}

View File

@@ -0,0 +1,213 @@
/*
* 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.chatbox;
import api.FontID;
import api.widgets.JavaScriptCallback;
import api.widgets.Widget;
import api.widgets.WidgetPositionMode;
import api.widgets.WidgetSizeMode;
import api.widgets.WidgetTextAlignment;
import api.widgets.WidgetType;
import com.google.inject.Inject;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import net.runelite.client.input.KeyListener;
@Slf4j
public class ChatboxTextMenuInput extends ChatboxInput implements KeyListener
{
@Data
@AllArgsConstructor
private static final class Entry
{
private String text;
private Runnable callback;
}
private final ChatboxPanelManager chatboxPanelManager;
@Getter
private String title;
@Getter
private List<Entry> options = new ArrayList<>();
@Getter
private Runnable onClose;
@Inject
protected ChatboxTextMenuInput(ChatboxPanelManager chatboxPanelManager)
{
this.chatboxPanelManager = chatboxPanelManager;
}
public ChatboxTextMenuInput title(String title)
{
this.title = title;
return this;
}
public ChatboxTextMenuInput option(String text, Runnable callback)
{
options.add(new Entry(text, callback));
return this;
}
public ChatboxTextMenuInput onClose(Runnable onClose)
{
this.onClose = onClose;
return this;
}
public ChatboxTextMenuInput build()
{
if (title == null)
{
throw new IllegalStateException("Title must be set");
}
if (options.size() < 1)
{
throw new IllegalStateException("You must have atleast 1 option");
}
chatboxPanelManager.openInput(this);
return this;
}
@Override
protected void open()
{
Widget container = chatboxPanelManager.getContainerWidget();
Widget prompt = container.createChild(-1, WidgetType.TEXT);
prompt.setText(title);
prompt.setTextColor(0x800000);
prompt.setFontId(FontID.QUILL_8);
prompt.setXPositionMode(WidgetPositionMode.ABSOLUTE_CENTER);
prompt.setOriginalX(0);
prompt.setYPositionMode(WidgetPositionMode.ABSOLUTE_TOP);
prompt.setOriginalY(8);
prompt.setOriginalHeight(24);
prompt.setXTextAlignment(WidgetTextAlignment.CENTER);
prompt.setYTextAlignment(WidgetTextAlignment.CENTER);
prompt.setWidthMode(WidgetSizeMode.MINUS);
prompt.revalidate();
int y = prompt.getRelativeX() + prompt.getHeight() + 6;
int height = container.getHeight() - y - 8;
int step = height / options.size();
int maxStep = options.size() >= 3 ? 25 : 30;
if (step > maxStep)
{
int ds = step - maxStep;
step = maxStep;
y += (ds * options.size()) / 2;
}
for (Entry option : options)
{
Widget optWidget = container.createChild(-1, WidgetType.TEXT);
optWidget.setText(option.text);
optWidget.setFontId(FontID.QUILL_8);
optWidget.setXPositionMode(WidgetPositionMode.ABSOLUTE_CENTER);
optWidget.setOriginalX(0);
optWidget.setYPositionMode(WidgetPositionMode.ABSOLUTE_TOP);
optWidget.setOriginalY(y);
optWidget.setOriginalHeight(24);
optWidget.setXTextAlignment(WidgetTextAlignment.CENTER);
optWidget.setYTextAlignment(WidgetTextAlignment.CENTER);
optWidget.setWidthMode(WidgetSizeMode.MINUS);
optWidget.setAction(0, "Continue");
optWidget.setOnOpListener((JavaScriptCallback) ev -> callback(option));
optWidget.setOnMouseOverListener((JavaScriptCallback) ev -> optWidget.setTextColor(0xFFFFFF));
optWidget.setOnMouseLeaveListener((JavaScriptCallback) ev -> optWidget.setTextColor(0));
optWidget.setHasListener(true);
optWidget.revalidate();
y += step;
}
}
private void callback(Entry entry)
{
Widget container = chatboxPanelManager.getContainerWidget();
container.setOnKeyListener((Object[]) null);
chatboxPanelManager.close();
entry.callback.run();
}
@Override
protected void close()
{
if (onClose != null)
{
onClose.run();
}
}
@Override
public void keyTyped(KeyEvent e)
{
char c = e.getKeyChar();
if (c == '\033')
{
chatboxPanelManager.close();
e.consume();
return;
}
int n = c - '1';
if (n >= 0 && n < options.size())
{
callback(options.get(n));
e.consume();
}
}
@Override
public void keyPressed(KeyEvent e)
{
if (e.getKeyCode() == KeyEvent.VK_ESCAPE)
{
e.consume();
}
}
@Override
public void keyReleased(KeyEvent e)
{
}
}

View File

@@ -0,0 +1,973 @@
/*
* Copyright (c) 2018, Woox <https://github.com/wooxsolo>
* 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.graphics;
import api.Client;
import api.MainBufferProvider;
import api.Model;
import api.NPC;
import api.NPCDefinition;
import api.Perspective;
import api.Player;
import api.coords.LocalPoint;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.awt.Color;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import net.runelite.client.task.Schedule;
@Singleton
public class ModelOutlineRenderer
{
/*
* This class doesn't really "need" static variables, but they are
* static for performance reasons. Arrays are kept outside methods
* to avoid frequent big allocations. Arrays should mostly be seen
* as ArrayLists. The size of them is increased whenever they need
* to become bigger.
*/
private final Client client;
private boolean isReset;
private boolean usedSinceLastCheck;
// Dimensions of the underlying image
private int imageWidth;
private int imageHeight;
// Boundaries for the current rasterization
private int clipX1;
private int clipY1;
private int clipX2;
private int clipY2;
// Pixel points that would be rendered to
private int[] visited;
private int currentVisitedNumber = 0;
// Transformed vertex positions
private int[] projectedVerticesX;
private int[] projectedVerticesY;
private boolean[] projectedVerticesRenderable;
// An array of pixel points to raster onto the image. These are checked against
// clip boundaries and the visited array to prevent drawing on top of the model
// and outside the scene area. They are grouped per distance to the closest pixel
// drawn on the model.
private int[][] outlinePixels;
private int[] outlinePixelsLengths; // outlinePixelsLength[i] is the used length of outlinePixels[i]
private int outlineArrayWidth;
// A list of pixel distances ordered from shortest to longest distance for
// each outline width. These are calculated once upon first usage and then
// stored here to prevent reevaluation.
private List<List<PixelDistanceAlpha>> precomputedDistancePriorities;
@Inject
private ModelOutlineRenderer(Client client)
{
this.client = client;
reset();
}
@Schedule(period = 5, unit = ChronoUnit.SECONDS)
public void checkUsage()
{
if (!isReset && !usedSinceLastCheck)
{
// Reset memory allocated when the rasterizer becomes inactive
reset();
}
usedSinceLastCheck = false;
}
/**
* Reset memory used by the rasterizer
*/
private void reset()
{
visited = new int[0];
projectedVerticesX = new int[0];
projectedVerticesY = new int[0];
projectedVerticesRenderable = new boolean[0];
outlinePixels = new int[0][];
outlinePixelsLengths = new int[0];
precomputedDistancePriorities = new ArrayList<>(0);
isReset = true;
}
/**
* Calculate the next power of two of a value
*
* @param value The value to find the next power of two of
* @return Returns the next power of two
*/
private static int nextPowerOfTwo(int value)
{
value--;
value |= value >> 1;
value |= value >> 2;
value |= value >> 4;
value |= value >> 8;
value |= value >> 16;
value++;
return value;
}
/**
* Determine if a triangle goes counter clockwise
*
* @return Returns true if the triangle goes counter clockwise and should be culled, otherwise false
*/
private static boolean cullFace(int x1, int y1, int x2, int y2, int x3, int y3)
{
return
(y2 - y1) * (x3 - x2) -
(x2 - x1) * (y3 - y2) < 0;
}
/**
* Gets the list of pixel distances ordered by distance from closest pixel for a specific outline width.
*
* @param outlineWidth The outline width
* @return Returns the list of pixel distances
*/
private List<PixelDistanceAlpha> getPriorityList(int outlineWidth)
{
while (precomputedDistancePriorities.size() <= outlineWidth)
{
precomputedDistancePriorities.add(null);
}
// Grab the cached outline width if we have one
if (precomputedDistancePriorities.get(outlineWidth) != null)
{
return precomputedDistancePriorities.get(outlineWidth);
}
List<PixelDistanceAlpha> ps = new ArrayList<>();
for (int x = 0; x <= outlineWidth; x++)
{
for (int y = 0; y <= outlineWidth; y++)
{
if (x == 0 && y == 0)
{
continue;
}
double dist = Math.sqrt(x * x + y * y);
if (dist > outlineWidth)
{
continue;
}
int outerAlpha = outlineWidth == 1 ? 255 // For preventing division by 0
: (int) (255 * (dist - 1) / (outlineWidth - 1));
ps.add(new PixelDistanceAlpha(outerAlpha, x + y * outlineArrayWidth));
}
}
ps.sort(Comparator.comparingDouble(PixelDistanceAlpha::getOuterAlpha));
precomputedDistancePriorities.set(outlineWidth, ps);
return ps;
}
/**
* Checks that the size of outlinePixels is big enough to hold a specific
* amount of elements. This is used to reduce the amount of if checks needed
* when adding elements to outlinePixels.
*
* @param distArrayPos The position in the array
* @param additionalMinimumSize The additional minimum size required
*/
private void ensureMinimumOutlineQueueSize(int distArrayPos, int additionalMinimumSize)
{
int minimumSize = outlinePixelsLengths[distArrayPos] + additionalMinimumSize;
while (outlinePixels[distArrayPos].length < minimumSize)
{
int[] newArr = new int[nextPowerOfTwo(minimumSize)];
System.arraycopy(outlinePixels[distArrayPos], 0, newArr, 0,
outlinePixels[distArrayPos].length);
outlinePixels[distArrayPos] = newArr;
}
}
/**
* Resets the visited flag for a specific amount of pixels
*
* @param pixelAmount The amount of pixels to reset
*/
private void resetVisited(int pixelAmount)
{
// The visited array is essentially a boolean array, but by
// making it an int array and checking if visited[i] == currentVisitedNumber
// and changing currentVisitedNumber for every new outline, we can essentially
// reset the whole array without having to iterate over every element
if (visited.length < pixelAmount)
{
visited = new int[nextPowerOfTwo(pixelAmount)];
currentVisitedNumber = 0;
}
currentVisitedNumber++;
}
/**
* Resets the pixels that are queued for outlining
*
* @param outlineWidth The width of the outline to reset pixels for
*/
private void resetOutline(int outlineWidth)
{
outlineArrayWidth = outlineWidth + 2;
int arraySizes = outlineArrayWidth * outlineArrayWidth;
if (outlinePixels.length < arraySizes)
{
outlinePixels = new int[arraySizes][];
outlinePixelsLengths = new int[arraySizes];
for (int i = 0; i < arraySizes; i++)
{
outlinePixels[i] = new int[4];
}
}
else
{
for (int i = 0; i < arraySizes; i++)
{
outlinePixelsLengths[i] = 0;
}
}
}
/**
* Simulates a horizontal line rasterization and adds the pixels to the left
* and to the right to the outline queue if they are within the clip area.
*
* @param pixelPos The pixel position in the line where x == 0
* @param x1 The starting x position
* @param x2 The ending x position
*/
private void simulateHorizontalLineRasterizationForOutline(
int pixelPos, int x1, int x2)
{
if (x2 > clipX2)
{
x2 = clipX2;
}
if (x1 < clipX1)
{
x1 = clipX1;
}
if (x1 >= x2)
{
return;
}
// Queue the pixel positions to the left and to the right of the line
ensureMinimumOutlineQueueSize(1, 2);
if (x2 < clipX2)
{
outlinePixels[1][outlinePixelsLengths[1]++] = pixelPos + x2;
}
if (x1 > clipX1)
{
outlinePixels[1][outlinePixelsLengths[1]++] = pixelPos + x1 - 1;
}
// Divide by 4 to account for loop unrolling
int xDist = x2 - x1 >> 2;
pixelPos += x1;
// This loop could run over 100m times per second without loop unrolling in some cases,
// so unrolling it can give a noticeable performance boost.
while (xDist-- > 0)
{
visited[pixelPos++] = currentVisitedNumber;
visited[pixelPos++] = currentVisitedNumber;
visited[pixelPos++] = currentVisitedNumber;
visited[pixelPos++] = currentVisitedNumber;
}
// Draw up to 3 more pixels if there were any left
xDist = (x2 - x1) & 3;
while (xDist-- > 0)
{
visited[pixelPos++] = currentVisitedNumber;
}
}
/**
* Queues the pixel positions above and below two horizontal lines, excluding those
* where the x positions of the lines intersect.
*
* @param pixelPos The pixel position at x == 0 of the second line
* @param x1 The starting x position of the first line
* @param x2 The ending x position of the first line
* @param x3 The starting x position of the second line
* @param x4 The ending x position of the second line
*/
private void outlineAroundHorizontalLine(
int pixelPos, int x1, int x2, int x3, int x4)
{
if (x1 < clipX1)
{
x1 = clipX1;
}
if (x2 < clipX1)
{
x2 = clipX1;
}
if (x3 < clipX1)
{
x3 = clipX1;
}
if (x4 < clipX1)
{
x4 = clipX1;
}
if (x1 > clipX2)
{
x1 = clipX2;
}
if (x2 > clipX2)
{
x2 = clipX2;
}
if (x3 > clipX2)
{
x3 = clipX2;
}
if (x4 > clipX2)
{
x4 = clipX2;
}
if (x1 < x3)
{
ensureMinimumOutlineQueueSize(outlineArrayWidth, x3 - x1);
for (int x = x1; x < x3; x++)
{
outlinePixels[outlineArrayWidth][outlinePixelsLengths[outlineArrayWidth]++] = pixelPos - imageWidth + x;
}
}
else
{
ensureMinimumOutlineQueueSize(outlineArrayWidth, x1 - x3);
for (int x = x3; x < x1; x++)
{
outlinePixels[outlineArrayWidth][outlinePixelsLengths[outlineArrayWidth]++] = pixelPos + x;
}
}
if (x2 < x4)
{
ensureMinimumOutlineQueueSize(outlineArrayWidth, x4 - x2);
for (int x = x2; x < x4; x++)
{
outlinePixels[outlineArrayWidth][outlinePixelsLengths[outlineArrayWidth]++] = pixelPos + x;
}
}
else
{
ensureMinimumOutlineQueueSize(outlineArrayWidth, x2 - x4);
for (int x = x4; x < x2; x++)
{
outlinePixels[outlineArrayWidth][outlinePixelsLengths[outlineArrayWidth]++] = pixelPos - imageWidth + x;
}
}
}
/**
* Simulates rasterization of a triangle and adds every pixel outside the triangle
* to the outline queue.
*
* @param x1 The x position of the first vertex in the triangle
* @param y1 The y position of the first vertex in the triangle
* @param x2 The x position of the second vertex in the triangle
* @param y2 The y position of the second vertex in the triangle
* @param x3 The x position of the third vertex in the triangle
* @param y3 The y position of the third vertex in the triangle
*/
private void simulateTriangleRasterizationForOutline(
int x1, int y1, int x2, int y2, int x3, int y3)
{
// Swap vertices so y1 <= y2 <= y3 using bubble sort
if (y1 > y2)
{
int yp = y1;
int xp = x1;
y1 = y2;
y2 = yp;
x1 = x2;
x2 = xp;
}
if (y2 > y3)
{
int yp = y2;
int xp = x2;
y2 = y3;
y3 = yp;
x2 = x3;
x3 = xp;
}
if (y1 > y2)
{
int yp = y1;
int xp = x1;
y1 = y2;
y2 = yp;
x1 = x2;
x2 = xp;
}
if (y1 > clipY2)
{
// All points are outside clip boundaries
return;
}
int slope1 = 0;
if (y1 != y2)
{
slope1 = (x2 - x1 << 14) / (y2 - y1);
}
int slope2 = 0;
if (y3 != y2)
{
slope2 = (x3 - x2 << 14) / (y3 - y2);
}
int slope3 = 0;
if (y1 != y3)
{
slope3 = (x1 - x3 << 14) / (y1 - y3);
}
if (y2 > clipY2)
{
y2 = clipY2;
}
if (y3 > clipY2)
{
y3 = clipY2;
}
if (y1 == y3 || y3 < 0)
{
return;
}
x1 <<= 14;
x2 <<= 14;
x3 = x1;
if (y1 < 0)
{
x3 -= y1 * slope3;
x1 -= y1 * slope1;
y1 = 0;
}
if (y2 < 0)
{
x2 -= slope2 * y2;
y2 = 0;
}
int pixelPos = y1 * imageWidth;
int currX1;
int currX2;
if (y1 != y2 && slope3 < slope1 || y1 == y2 && slope3 > slope2)
{
int height1 = y2 - y1;
int height2 = y3 - y2;
int prevX1;
int prevX2;
if (height1 <= 0)
{
prevX1 = x3 >> 14;
prevX2 = x2 >> 14;
}
else
{
prevX1 = x3 >> 14;
prevX2 = x1 >> 14;
}
outlineAroundHorizontalLine(pixelPos, prevX1, prevX2, prevX2, prevX2);
while (height1-- > 0)
{
currX1 = x3 >> 14;
currX2 = x1 >> 14;
outlineAroundHorizontalLine(pixelPos, currX1, currX2, prevX1, prevX2);
simulateHorizontalLineRasterizationForOutline(pixelPos, currX1, currX2);
x3 += slope3;
x1 += slope1;
pixelPos += imageWidth;
prevX1 = currX1;
prevX2 = currX2;
}
while (height2-- > 0)
{
currX1 = x3 >> 14;
currX2 = x2 >> 14;
outlineAroundHorizontalLine(pixelPos, currX1, currX2, prevX1, prevX2);
simulateHorizontalLineRasterizationForOutline(pixelPos, currX1, currX2);
x3 += slope3;
x2 += slope2;
pixelPos += imageWidth;
prevX1 = currX1;
prevX2 = currX2;
}
outlineAroundHorizontalLine(pixelPos, prevX1, prevX1, prevX1, prevX2);
}
else
{
int height1 = y2 - y1;
int height2 = y3 - y2;
int prevX1;
int prevX2;
if (height1 <= 0)
{
prevX1 = x2 >> 14;
prevX2 = x3 >> 14;
}
else
{
prevX1 = x1 >> 14;
prevX2 = x3 >> 14;
}
outlineAroundHorizontalLine(pixelPos, prevX1, prevX2, prevX2, prevX2);
while (height1-- > 0)
{
currX1 = x1 >> 14;
currX2 = x3 >> 14;
outlineAroundHorizontalLine(pixelPos, currX1, currX2, prevX1, prevX2);
simulateHorizontalLineRasterizationForOutline(pixelPos, currX1, currX2);
x1 += slope1;
x3 += slope3;
pixelPos += imageWidth;
prevX1 = currX1;
prevX2 = currX2;
}
while (height2-- > 0)
{
currX1 = x2 >> 14;
currX2 = x3 >> 14;
outlineAroundHorizontalLine(pixelPos, currX1, currX2, prevX1, prevX2);
simulateHorizontalLineRasterizationForOutline(pixelPos, currX1, currX2);
x3 += slope3;
x2 += slope2;
pixelPos += imageWidth;
prevX1 = currX1;
prevX2 = currX2;
}
outlineAroundHorizontalLine(pixelPos, prevX1, prevX1, prevX1, prevX2);
}
}
/**
* Translates the vertices 3D points to the screen canvas 2D points
*
* @param localX The local x position of the vertices
* @param localY The local y position of the vertices
* @param localZ The local z position of the vertices
* @param vertexOrientation The orientation of the vertices
* @return Returns true if any of them are inside the clip area, otherwise false
*/
private boolean projectVertices(Model model,
final int localX, final int localY, final int localZ, final int vertexOrientation)
{
final int cameraX = client.getCameraX();
final int cameraY = client.getCameraY();
final int cameraZ = client.getCameraZ();
final int cameraYaw = client.getCameraYaw();
final int cameraPitch = client.getCameraPitch();
final int scale = client.getScale();
final int orientationSin = Perspective.SINE[vertexOrientation];
final int orientationCos = Perspective.COSINE[vertexOrientation];
final int pitchSin = Perspective.SINE[cameraPitch];
final int pitchCos = Perspective.COSINE[cameraPitch];
final int yawSin = Perspective.SINE[cameraYaw];
final int yawCos = Perspective.COSINE[cameraYaw];
final int vertexCount = model.getVerticesCount();
final int[] verticesX = model.getVerticesX();
final int[] verticesY = model.getVerticesY();
final int[] verticesZ = model.getVerticesZ();
boolean anyVisible = false;
// Make sure the arrays are big enough
while (projectedVerticesX.length < vertexCount)
{
int newSize = nextPowerOfTwo(vertexCount);
projectedVerticesX = new int[newSize];
projectedVerticesY = new int[newSize];
projectedVerticesRenderable = new boolean[newSize];
}
for (int i = 0; i < vertexCount; i++)
{
int vx = verticesX[i];
int vy = verticesZ[i];
int vz = verticesY[i];
int vh; // Value holder
// Rotate based on orientation
vh = vx * orientationCos + vy * orientationSin >> 16;
vy = vy * orientationCos - vx * orientationSin >> 16;
vx = vh;
// Translate to local coords
vx += localX;
vy += localY;
vz += localZ;
// Translate to camera
vx -= cameraX;
vy -= cameraY;
vz -= cameraZ;
// Transform to canvas
vh = vx * yawCos + vy * yawSin >> 16;
vy = vy * yawCos - vx * yawSin >> 16;
vx = vh;
vh = vz * pitchCos - vy * pitchSin >> 16;
vz = vz * pitchSin + vy * pitchCos >> 16;
vy = vh;
if (vz >= 50)
{
projectedVerticesX[i] = (clipX1 + clipX2) / 2 + vx * scale / vz;
projectedVerticesY[i] = (clipY1 + clipY2) / 2 + vy * scale / vz;
projectedVerticesRenderable[i] = true;
anyVisible |=
projectedVerticesX[i] >= clipX1 && projectedVerticesX[i] < clipX2 &&
projectedVerticesY[i] >= clipY1 && projectedVerticesY[i] < clipY2;
}
else
{
projectedVerticesRenderable[i] = false;
}
}
return anyVisible;
}
/**
* Simulate rendering of the model and puts every pixel of the wireframe of
* the non-culled and non-transparent faces into the outline pixel queue.
*/
private void simulateModelRasterizationForOutline(Model model)
{
final int triangleCount = model.getTrianglesCount();
final int[] indices1 = model.getTrianglesX();
final int[] indices2 = model.getTrianglesY();
final int[] indices3 = model.getTrianglesZ();
final byte[] triangleTransparencies = model.getTriangleTransparencies();
for (int i = 0; i < triangleCount; i++)
{
if (projectedVerticesRenderable[indices1[i]] &&
projectedVerticesRenderable[indices2[i]] &&
projectedVerticesRenderable[indices3[i]] &&
// 254 and 255 counts as fully transparent
(triangleTransparencies == null || (triangleTransparencies[i] & 255) < 254))
{
final int index1 = indices1[i];
final int index2 = indices2[i];
final int index3 = indices3[i];
final int v1x = projectedVerticesX[index1];
final int v1y = projectedVerticesY[index1];
final int v2x = projectedVerticesX[index2];
final int v2y = projectedVerticesY[index2];
final int v3x = projectedVerticesX[index3];
final int v3y = projectedVerticesY[index3];
if (!cullFace(v1x, v1y, v2x, v2y, v3x, v3y))
{
simulateTriangleRasterizationForOutline(
v1x, v1y, v2x, v2y, v3x, v3y);
}
}
}
}
/**
* Draws an outline of the pixels in the outline queue to an image
*
* @param image The image to draw the outline to
* @param outlineWidth The width of the outline
* @param innerColor The color of the pixels of the outline closest to the model
* @param outerColor The color of the pixels of the outline furthest away from the model
*/
private void renderOutline(BufferedImage image, int outlineWidth,
Color innerColor, Color outerColor)
{
int[] imageData = ((DataBufferInt) image.getRaster().getDataBuffer()).getData();
List<PixelDistanceAlpha> ps = getPriorityList(outlineWidth);
for (PixelDistanceAlpha p : ps)
{
int color;
int alpha;
if (outlineWidth == 1)
{
color =
((innerColor.getRed() + outerColor.getRed()) << 15) |
((innerColor.getGreen() + outerColor.getGreen() << 7)) |
((innerColor.getBlue() + outerColor.getBlue() >> 1));
alpha = (innerColor.getAlpha() + outerColor.getAlpha()) >> 1;
}
else
{
int outerAlpha = p.getOuterAlpha();
int innerAlpha = 255 - outerAlpha;
int innerAlphaFraction = (innerAlpha * innerColor.getAlpha()) / 255;
int outerAlphaFraction = (outerAlpha * outerColor.getAlpha()) / 255;
alpha = innerAlphaFraction + outerAlphaFraction;
if (alpha != 0)
{
color =
((innerColor.getRed() * innerAlphaFraction +
outerColor.getRed() * outerAlphaFraction) / alpha << 16) |
((innerColor.getGreen() * innerAlphaFraction +
outerColor.getGreen() * outerAlphaFraction) / alpha << 8) |
((innerColor.getBlue() * innerAlphaFraction +
outerColor.getBlue() * outerAlphaFraction) / alpha);
}
else
{
color = 0;
}
}
final int distArrayPos = p.getDistArrayPos();
final int nextDistArrayPosY = distArrayPos + outlineArrayWidth;
final int nextDistArrayPosX = distArrayPos + 1;
ensureMinimumOutlineQueueSize(nextDistArrayPosX, outlinePixelsLengths[distArrayPos] * 2);
ensureMinimumOutlineQueueSize(nextDistArrayPosY, outlinePixelsLengths[distArrayPos] * 2);
// The following 3 branches do the same thing, but when the requirements are simple,
// there are less checks needed which can give a performance boost.
if (alpha == 255)
{
if (outlineWidth == 1)
{
for (int i2 = 0; i2 < outlinePixelsLengths[distArrayPos]; i2++)
{
int pixelPos = outlinePixels[distArrayPos][i2];
int x = pixelPos % imageWidth;
int y = pixelPos / imageWidth;
if (x < clipX1 || x >= clipX2 ||
y < clipY1 || y >= clipY2 ||
visited[pixelPos] == currentVisitedNumber)
{
continue;
}
imageData[pixelPos] = color;
}
}
else
{
for (int i2 = 0; i2 < outlinePixelsLengths[distArrayPos]; i2++)
{
int pixelPos = outlinePixels[distArrayPos][i2];
int x = pixelPos % imageWidth;
int y = pixelPos / imageWidth;
if (x < clipX1 || x >= clipX2 ||
y < clipY1 || y >= clipY2 ||
visited[pixelPos] == currentVisitedNumber)
{
continue;
}
visited[pixelPos] = currentVisitedNumber;
imageData[pixelPos] = color;
if (pixelPos % imageWidth != 0)
{
outlinePixels[nextDistArrayPosX][outlinePixelsLengths[nextDistArrayPosX]++] = pixelPos - 1;
}
if ((pixelPos + 1) % imageWidth != 0)
{
outlinePixels[nextDistArrayPosX][outlinePixelsLengths[nextDistArrayPosX]++] = pixelPos + 1;
}
outlinePixels[nextDistArrayPosY][outlinePixelsLengths[nextDistArrayPosY]++] = pixelPos - imageWidth;
outlinePixels[nextDistArrayPosY][outlinePixelsLengths[nextDistArrayPosY]++] = pixelPos + imageWidth;
}
}
}
else
{
for (int i2 = 0; i2 < outlinePixelsLengths[distArrayPos]; i2++)
{
int pixelPos = outlinePixels[distArrayPos][i2];
int x = pixelPos % imageWidth;
int y = pixelPos / imageWidth;
if (x < clipX1 || x >= clipX2 ||
y < clipY1 || y >= clipY2 ||
visited[pixelPos] == currentVisitedNumber)
{
continue;
}
visited[pixelPos] = currentVisitedNumber;
imageData[pixelPos] =
((((color & 0xFF0000) * alpha + (imageData[pixelPos] & 0xFF0000) * (255 - alpha)) / 255) & 0xFF0000) +
((((color & 0xFF00) * alpha + (imageData[pixelPos] & 0xFF00) * (255 - alpha)) / 255) & 0xFF00) +
((((color & 0xFF) * alpha + (imageData[pixelPos] & 0xFF) * (255 - alpha)) / 255) & 0xFF);
if (pixelPos % imageWidth != 0)
{
outlinePixels[nextDistArrayPosX][outlinePixelsLengths[nextDistArrayPosX]++] = pixelPos - 1;
}
if ((pixelPos + 1) % imageWidth != 0)
{
outlinePixels[nextDistArrayPosX][outlinePixelsLengths[nextDistArrayPosX]++] = pixelPos + 1;
}
outlinePixels[nextDistArrayPosY][outlinePixelsLengths[nextDistArrayPosY]++] = pixelPos - imageWidth;
outlinePixels[nextDistArrayPosY][outlinePixelsLengths[nextDistArrayPosY]++] = pixelPos + imageWidth;
}
}
}
}
/**
* Draws an outline around a model to an image
*
* @param localX The local x position of the model
* @param localY The local y position of the model
* @param localZ The local z position of the model
* @param orientation The orientation of the model
* @param outlineWidth The width of the outline
* @param innerColor The color of the pixels of the outline closest to the model
* @param outerColor The color of the pixels of the outline furthest away from the model
*/
private void drawModelOutline(Model model,
int localX, int localY, int localZ, int orientation,
int outlineWidth, Color innerColor, Color outerColor)
{
if (outlineWidth <= 0)
{
return;
}
isReset = false;
usedSinceLastCheck = true;
MainBufferProvider bufferProvider = (MainBufferProvider) client.getBufferProvider();
BufferedImage image = (BufferedImage) bufferProvider.getImage();
clipX1 = client.getViewportXOffset();
clipY1 = client.getViewportYOffset();
clipX2 = client.getViewportWidth() + clipX1;
clipY2 = client.getViewportHeight() + clipY1;
imageWidth = image.getWidth();
imageHeight = image.getHeight();
final int pixelAmount = imageWidth * imageHeight;
resetVisited(pixelAmount);
resetOutline(outlineWidth);
if (!projectVertices(model,
localX, localY, localZ, orientation))
{
// No vertex of the model is visible on the screen, so we can
// assume there are no parts of the model to outline.
return;
}
simulateModelRasterizationForOutline(model);
renderOutline(image, outlineWidth, innerColor, outerColor);
}
public void drawOutline(NPC npc, int outlineWidth, Color color)
{
drawOutline(npc, outlineWidth, color, color);
}
public void drawOutline(NPC npc, int outlineWidth,
Color innerColor, Color outerColor)
{
int size = 1;
NPCDefinition composition = npc.getTransformedDefinition();
if (composition != null)
{
size = composition.getSize();
}
LocalPoint lp = npc.getLocalLocation();
if (lp != null)
{
// NPCs z position are calculated based on the tile height of the northeastern tile
final int northEastX = lp.getX() + Perspective.LOCAL_TILE_SIZE * (size - 1) / 2;
final int northEastY = lp.getY() + Perspective.LOCAL_TILE_SIZE * (size - 1) / 2;
final LocalPoint northEastLp = new LocalPoint(northEastX, northEastY);
drawModelOutline(npc.getModel(), lp.getX(), lp.getY(),
Perspective.getTileHeight(client, northEastLp, client.getPlane()),
npc.getOrientation(), outlineWidth, innerColor, outerColor);
}
}
public void drawOutline(Player player, int outlineWidth, Color color)
{
drawOutline(player, outlineWidth, color, color);
}
public void drawOutline(Player player, int outlineWidth,
Color innerColor, Color outerColor)
{
LocalPoint lp = player.getLocalLocation();
if (lp != null)
{
drawModelOutline(player.getModel(), lp.getX(), lp.getY(),
Perspective.getTileHeight(client, lp, client.getPlane()),
player.getOrientation(), outlineWidth, innerColor, outerColor);
}
}
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright (c) 2018, Woox <https://github.com/wooxsolo>
* 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.graphics;
import lombok.RequiredArgsConstructor;
import lombok.Value;
@Value
@RequiredArgsConstructor
class PixelDistanceAlpha
{
private final int outerAlpha;
private final int distArrayPos;
}

View File

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

View File

@@ -0,0 +1,73 @@
/*
* Copyright (c) 2018, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.client.input;
import java.awt.event.KeyEvent;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.inject.Singleton;
@Singleton
public class KeyManager
{
private final List<KeyListener> keyListeners = new CopyOnWriteArrayList<>();
public void registerKeyListener(KeyListener keyListener)
{
if (!keyListeners.contains(keyListener))
{
keyListeners.add(keyListener);
}
}
public void unregisterKeyListener(KeyListener keyListener)
{
keyListeners.remove(keyListener);
}
public void processKeyPressed(KeyEvent keyEvent)
{
for (KeyListener keyListener : keyListeners)
{
keyListener.keyPressed(keyEvent);
}
}
public void processKeyReleased(KeyEvent keyEvent)
{
for (KeyListener keyListener : keyListeners)
{
keyListener.keyReleased(keyEvent);
}
}
public void processKeyTyped(KeyEvent keyEvent)
{
for (KeyListener keyListener : keyListeners)
{
keyListener.keyTyped(keyEvent);
}
}
}

View File

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

View File

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

View File

@@ -0,0 +1,146 @@
/*
* Copyright (c) 2018, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.client.input;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.inject.Singleton;
@Singleton
public class MouseManager
{
private final List<MouseListener> mouseListeners = new CopyOnWriteArrayList<>();
private final List<MouseWheelListener> mouseWheelListeners = new CopyOnWriteArrayList<>();
public void registerMouseListener(MouseListener mouseListener)
{
if (!mouseListeners.contains(mouseListener))
{
mouseListeners.add(mouseListener);
}
}
public void registerMouseListener(int position, MouseListener mouseListener)
{
mouseListeners.add(position, mouseListener);
}
public void unregisterMouseListener(MouseListener mouseListener)
{
mouseListeners.remove(mouseListener);
}
public void registerMouseWheelListener(MouseWheelListener mouseWheelListener)
{
if (!mouseWheelListeners.contains(mouseWheelListener))
{
mouseWheelListeners.add(mouseWheelListener);
}
}
public void registerMouseWheelListener(int position, MouseWheelListener mouseWheelListener)
{
mouseWheelListeners.add(position, mouseWheelListener);
}
public void unregisterMouseWheelListener(MouseWheelListener mouseWheelListener)
{
mouseWheelListeners.remove(mouseWheelListener);
}
public MouseEvent processMousePressed(MouseEvent mouseEvent)
{
for (MouseListener mouseListener : mouseListeners)
{
mouseEvent = mouseListener.mousePressed(mouseEvent);
}
return mouseEvent;
}
public MouseEvent processMouseReleased(MouseEvent mouseEvent)
{
for (MouseListener mouseListener : mouseListeners)
{
mouseEvent = mouseListener.mouseReleased(mouseEvent);
}
return mouseEvent;
}
public MouseEvent processMouseClicked(MouseEvent mouseEvent)
{
for (MouseListener mouseListener : mouseListeners)
{
mouseEvent = mouseListener.mouseClicked(mouseEvent);
}
return mouseEvent;
}
public MouseEvent processMouseEntered(MouseEvent mouseEvent)
{
for (MouseListener mouseListener : mouseListeners)
{
mouseEvent = mouseListener.mouseEntered(mouseEvent);
}
return mouseEvent;
}
public MouseEvent processMouseExited(MouseEvent mouseEvent)
{
for (MouseListener mouseListener : mouseListeners)
{
mouseEvent = mouseListener.mouseExited(mouseEvent);
}
return mouseEvent;
}
public MouseEvent processMouseDragged(MouseEvent mouseEvent)
{
for (MouseListener mouseListener : mouseListeners)
{
mouseEvent = mouseListener.mouseDragged(mouseEvent);
}
return mouseEvent;
}
public MouseEvent processMouseMoved(MouseEvent mouseEvent)
{
for (MouseListener mouseListener : mouseListeners)
{
mouseEvent = mouseListener.mouseMoved(mouseEvent);
}
return mouseEvent;
}
public MouseWheelEvent processMouseWheelMoved(MouseWheelEvent mouseWheelEvent)
{
for (MouseWheelListener mouseWheelListener : mouseWheelListeners)
{
mouseWheelEvent = mouseWheelListener.mouseWheelMoved(mouseWheelEvent);
}
return mouseWheelEvent;
}
}

View File

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

View File

@@ -0,0 +1,116 @@
/*
* Copyright (c) 2019, Lucas <https://github.com/Lucwousin>
* 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.menus;
import api.MenuEntry;
import joptsimple.internal.Strings;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import static net.runelite.client.menus.MenuManager.LEVEL_PATTERN;
import net.runelite.client.util.Text;
@EqualsAndHashCode
public class ComparableEntry
{
@Getter
private String option;
@Getter
private String target;
@Getter
private int id;
@Getter
private int type;
@Getter
private boolean strictOption;
@Getter
private boolean strictTarget;
public ComparableEntry(String option, String target)
{
this(option, target, -1, -1, true, true);
}
public ComparableEntry(String option, String target, boolean strictTarget)
{
this(option, target, -1, -1, true, strictTarget);
}
public ComparableEntry(String option, String target, int id, int type, boolean strictOption, boolean strictTarget)
{
this.option = option;
this.target = target;
this.id = id;
this.type = type;
this.strictOption = strictOption;
this.strictTarget = strictTarget;
}
boolean matches(MenuEntry entry)
{
String opt = Text.standardize(entry.getOption());
if (strictOption && !opt.equals(option) || !strictOption && !opt.contains(option))
{
return false;
}
if (strictTarget || !Strings.isNullOrEmpty(target))
{
String tgt = Text.standardize(LEVEL_PATTERN.matcher(entry.getTarget()).replaceAll(""));
if (strictTarget && !tgt.equals(target) || !strictTarget && !tgt.contains(target))
{
return false;
}
}
if (id != -1)
{
int id = entry.getIdentifier();
if (this.id != id)
{
return false;
}
}
if (type != -1)
{
int type = entry.getType();
if (this.type != type)
{
return false;
}
}
return true;
}
}

View File

@@ -0,0 +1,814 @@
/*
* Copyright (c) 2017, Robin <robin.weymans@gmail.com>
* Copyright (c) 2019, Lucas <https://github.com/Lucwousin>
* 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.menus;
import api.Client;
import api.MenuAction;
import api.MenuEntry;
import api.NPCDefinition;
import api.ObjectDefinition;
import api.events.MenuEntryAdded;
import api.events.MenuOptionClicked;
import api.events.NpcActionChanged;
import api.events.PlayerMenuOptionClicked;
import api.events.PlayerMenuOptionsChanged;
import api.events.WidgetMenuOptionClicked;
import api.widgets.WidgetInfo;
import com.google.common.base.Preconditions;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import javax.inject.Inject;
import javax.inject.Singleton;
import joptsimple.internal.Strings;
import lombok.extern.slf4j.Slf4j;
import net.runelite.client.eventbus.EventBus;
import net.runelite.client.eventbus.Subscribe;
import net.runelite.client.util.Text;
@Singleton
@Slf4j
public class MenuManager
{
/*
* The index needs to be between 4 and 7,
*/
private static final int IDX_LOWER = 4;
private static final int IDX_UPPER = 8;
static final Pattern LEVEL_PATTERN = Pattern.compile("\\(level-[0-9]*\\)");
private static MenuEntry CANCEL()
{
MenuEntry cancel = new MenuEntry();
cancel.setOption("Cancel");
cancel.setTarget("");
cancel.setIdentifier(0);
cancel.setType(MenuAction.CANCEL.getId());
cancel.setParam0(0);
cancel.setParam1(0);
return cancel;
}
private final Client client;
private final EventBus eventBus;
//Maps the indexes that are being used to the menu option.
private final Map<Integer, String> playerMenuIndexMap = new HashMap<>();
//Used to manage custom non-player menu options
private final Multimap<Integer, WidgetMenuOption> managedMenuOptions = HashMultimap.create();
private final Set<String> npcMenuOptions = new HashSet<>();
private final Set<ComparableEntry> priorityEntries = new HashSet<>();
private final Set<MenuEntry> currentPriorityEntries = new HashSet<>();
private final Map<ComparableEntry, ComparableEntry> swaps = new HashMap<>();
private final Set<MenuEntry> originalTypes = new HashSet<>();
private final Set<Integer> leftClickObjects = new HashSet<>();
@Inject
private MenuManager(Client client, EventBus eventBus)
{
this.client = client;
this.eventBus = eventBus;
}
/**
* Adds a CustomMenuOption to the list of managed menu options.
*
* @param customMenuOption The custom menu to add
*/
public void addManagedCustomMenu(WidgetMenuOption customMenuOption)
{
WidgetInfo widget = customMenuOption.getWidget();
managedMenuOptions.put(widget.getId(), customMenuOption);
}
/**
* Removes a CustomMenuOption from the list of managed menu options.
*
* @param customMenuOption The custom menu to add
*/
public void removeManagedCustomMenu(WidgetMenuOption customMenuOption)
{
WidgetInfo widget = customMenuOption.getWidget();
managedMenuOptions.remove(widget.getId(), customMenuOption);
}
private boolean menuContainsCustomMenu(WidgetMenuOption customMenuOption)
{
for (MenuEntry menuEntry : client.getMenuEntries())
{
String option = menuEntry.getOption();
String target = menuEntry.getTarget();
if (option.equals(customMenuOption.getMenuOption()) && target.equals(customMenuOption.getMenuTarget()))
{
return true;
}
}
return false;
}
@Subscribe
public void onMenuEntryAdded(MenuEntryAdded event)
{
int widgetId = event.getActionParam1();
Collection<WidgetMenuOption> options = managedMenuOptions.get(widgetId);
MenuEntry[] menuEntries = client.getMenuEntries();
if (menuEntries.length == 1)
{
// Menu entries reset, so priority entries should reset as well
currentPriorityEntries.clear();
originalTypes.clear();
}
for (WidgetMenuOption currentMenu : options)
{
if (!menuContainsCustomMenu(currentMenu))//Don't add if we have already added it to this widget
{
menuEntries = Arrays.copyOf(menuEntries, menuEntries.length + 1);
MenuEntry menuEntry = menuEntries[menuEntries.length - 1] = new MenuEntry();
menuEntry.setOption(currentMenu.getMenuOption());
menuEntry.setParam1(widgetId);
menuEntry.setTarget(currentMenu.getMenuTarget());
menuEntry.setType(MenuAction.RUNELITE.getId());
client.setMenuEntries(menuEntries);
}
}
final MenuEntry newestEntry = menuEntries[menuEntries.length - 1];
boolean isPrio = false;
for (ComparableEntry p : priorityEntries)
{
if (p.matches(newestEntry))
{
isPrio = true;
break;
}
}
// If the last entry was a priority entry, keep track of it
if (isPrio)
{
currentPriorityEntries.add(newestEntry);
}
// Make a copy of the menu entries, cause you can't remove from Arrays.asList()
List<MenuEntry> copy = new ArrayList<>(Arrays.asList(menuEntries));
// If there are entries we want to prioritize, we have to remove the rest
if (!currentPriorityEntries.isEmpty())
{
copy.retainAll(currentPriorityEntries);
copy.add(0, CANCEL());
}
// Find the current entry in the swaps map
ComparableEntry swapEntry = null;
for (ComparableEntry e : swaps.keySet())
{
if (e.matches(newestEntry))
{
swapEntry = e;
break;
}
}
if (swapEntry != null)
{
ComparableEntry swapTarget = swaps.get(swapEntry);
// Find the target for the swap in current menu entries
MenuEntry foundSwap = null;
for (MenuEntry entry : Lists.reverse(copy))
{
if (swapTarget.matches(entry))
{
foundSwap = entry;
break;
}
}
if (foundSwap != null)
{
// This is the menu entry added last's type
final int otherType = foundSwap.getType();
// MenuActions with an id of over 1000 get shifted to the back of the menu entry array
// They have different id's in the packet buffer though, so we got to modify them back on click
// I couldn't get this to work with objects, so we're using modified objectcomposition for that
final boolean shouldModifyType = otherType == MenuAction.EXAMINE_ITEM_BANK_EQ.getId();
if (shouldModifyType)
{
foundSwap.setType(MenuAction.WIDGET_DEFAULT.getId());
originalTypes.add(foundSwap);
}
// Swap
int index = copy.indexOf(foundSwap);
int newIndex = copy.indexOf(newestEntry);
copy.set(index, newestEntry);
copy.set(newIndex, foundSwap);
}
}
client.setMenuEntries(copy.toArray(new MenuEntry[0]));
}
public void addPlayerMenuItem(String menuText)
{
Preconditions.checkNotNull(menuText);
int playerMenuIndex = findEmptyPlayerMenuIndex();
if (playerMenuIndex == IDX_UPPER)
{
return; // no more slots
}
addPlayerMenuItem(playerMenuIndex, menuText);
}
public void removePlayerMenuItem(String menuText)
{
Preconditions.checkNotNull(menuText);
for (Map.Entry<Integer, String> entry : playerMenuIndexMap.entrySet())
{
if (entry.getValue().equalsIgnoreCase(menuText))
{
removePlayerMenuItem(entry.getKey());
break;
}
}
}
public boolean toggleLeftClick(String menuText, int objectID, boolean reset)
{
Preconditions.checkNotNull(menuText);
if (client == null)
{
return false;
}
ObjectDefinition oc = client.getObjectDefinition(objectID);
if (oc == null)
{
return false;
}
ObjectDefinition impostor = oc.getImpostorIds() != null ? oc.getImpostor() : null;
if (impostor != null)
{
if (toggleLeftClick(menuText, impostor.getId(), reset))
{
// Sorry about this
leftClickObjects.remove(impostor.getId());
if (reset)
{
leftClickObjects.remove(objectID);
}
else
{
leftClickObjects.add(objectID);
}
return true;
}
}
String[] options = oc.getActions();
if (options == null)
{
return false;
}
boolean hasOption5 = !Strings.isNullOrEmpty(options[options.length - 1]);
boolean hasOption1 = !Strings.isNullOrEmpty(options[0]);
if (hasOption5 || hasOption1)
{
String option1 = options[0];
String option5 = options[options.length - 1];
if (reset && !hasOption1 // Won't have to reset anything cause
|| reset && !menuText.equalsIgnoreCase(option1) // theres nothing to reset
|| hasOption5 && !menuText.equalsIgnoreCase(option5))
{
return false;
}
options[0] = option5;
options[options.length - 1] = option1;
}
else
{
return false;
}
if (reset)
{
leftClickObjects.remove(objectID);
}
else
{
leftClickObjects.add(objectID);
}
return true;
}
@Subscribe
public void onPlayerMenuOptionsChanged(PlayerMenuOptionsChanged event)
{
int idx = event.getIndex();
String menuText = playerMenuIndexMap.get(idx);
if (menuText == null)
{
return; // not our menu
}
// find new index for this option
int newIdx = findEmptyPlayerMenuIndex();
if (newIdx == IDX_UPPER)
{
log.debug("Client has updated player menu index {} where option {} was, and there are no more free slots available", idx, menuText);
return;
}
log.debug("Client has updated player menu index {} where option {} was, moving to index {}", idx, menuText, newIdx);
playerMenuIndexMap.remove(idx);
addPlayerMenuItem(newIdx, menuText);
}
@Subscribe
public void onNpcActionChanged(NpcActionChanged event)
{
NPCDefinition composition = event.getNpcDefinition();
for (String npcOption : npcMenuOptions)
{
addNpcOption(composition, npcOption);
}
}
private void addNpcOption(NPCDefinition composition, String npcOption)
{
String[] actions = composition.getActions();
int unused = -1;
for (int i = 0; i < actions.length; ++i)
{
if (actions[i] == null && unused == -1)
{
unused = i;
}
else if (actions[i] != null && actions[i].equals(npcOption))
{
return;
}
}
if (unused == -1)
{
return;
}
actions[unused] = npcOption;
}
private void removeNpcOption(NPCDefinition composition, String npcOption)
{
String[] actions = composition.getActions();
if (composition.getActions() == null)
{
return;
}
for (int i = 0; i < actions.length; ++i)
{
if (actions[i] != null && actions[i].equals(npcOption))
{
actions[i] = null;
}
}
}
@Subscribe
public void onMenuOptionClicked(MenuOptionClicked event)
{
if (!event.getMenuTarget().equals("do not edit") &&
!originalTypes.isEmpty() &&
event.getMenuAction() == MenuAction.WIDGET_DEFAULT)
{
for (MenuEntry e : originalTypes)
{
// Honestly, I was about to write a huge ass rant about
// how I hate whoever wrote the menuoptionclicked class
// but I decided that that'd be un-nice to them, and they
// probably spent over 24 hours writing it. Not because
// it was that difficult to write, of course, but because
// they must have the fucking iq of a retarded, under developed,
// braindead, basically good-for-nothing, idiotic chimp.
//
// Just kidding, of course, that would be too big of an
// insult towards those poor chimps. It's not their fault
// some dumbass is the way they are, right? Why should they
// feel bad for something they can't do anything about?
//
// Whoever wrote that class though, should actually feel
// 100% terrible. If they aren't depressed, I really wish
// they become depressed very, very soon. What the fuck
// were they even thinking.
if (event.getMenuAction().getId() != e.getType()
|| event.getId() != e.getIdentifier()
|| !event.getMenuOption().equals(e.getOption()))
{
continue;
}
//todo once bytecodes work again, re-enable
/* event.consume();
client.invokeMenuAction(
event.getActionParam(),
event.getWidgetId(),
MenuAction.EXAMINE_ITEM_BANK_EQ.getId(),
event.getId(),
event.getMenuOption(),
"do not edit",
client.getMouseCanvasPosition().getX(),
client.getMouseCanvasPosition().getY()
);*/
break;
}
}
if (!event.getMenuTarget().equals("do not edit") &&
!leftClickObjects.isEmpty() &&
event.getMenuAction() == MenuAction.GAME_OBJECT_FIRST_OPTION &&
(
leftClickObjects.contains(event.getId())
||
client.getObjectDefinition(event.getId()) != null &&
client.getObjectDefinition(event.getId()).getImpostorIds() != null &&
client.getObjectDefinition(event.getId()).getImpostor() != null &&
client.getObjectDefinition(event.getId()).getImpostor().getId() == event.getId()))
{
//todo once bytecodes work again, re-enable
/* event.consume();
client.invokeMenuAction(
event.getActionParam(),
event.getWidgetId(),
MenuAction.GAME_OBJECT_FIFTH_OPTION.getId(),
event.getId(),
event.getMenuOption(),
"do not edit",
client.getMouseCanvasPosition().getX(),
client.getMouseCanvasPosition().getY()
);*/
}
if (event.getMenuAction() != MenuAction.RUNELITE)
{
return; // not a player menu
}
int widgetId = event.getWidgetId();
Collection<WidgetMenuOption> options = managedMenuOptions.get(widgetId);
for (WidgetMenuOption curMenuOption : options)
{
if (curMenuOption.getMenuTarget().equals(event.getMenuTarget())
&& curMenuOption.getMenuOption().equals(event.getMenuOption()))
{
WidgetMenuOptionClicked customMenu = new WidgetMenuOptionClicked();
customMenu.setMenuOption(event.getMenuOption());
customMenu.setMenuTarget(event.getMenuTarget());
customMenu.setWidget(curMenuOption.getWidget());
eventBus.post(customMenu);
return; // don't continue because it's not a player option
}
}
String target = event.getMenuTarget();
// removes tags and level from player names for example:
// <col=ffffff>username<col=40ff00> (level-42) or <col=ffffff><img=2>username</col>
String username = Text.removeTags(target).split("[(]")[0].trim();
PlayerMenuOptionClicked playerMenuOptionClicked = new PlayerMenuOptionClicked();
playerMenuOptionClicked.setMenuOption(event.getMenuOption());
playerMenuOptionClicked.setMenuTarget(username);
eventBus.post(playerMenuOptionClicked);
}
private void addPlayerMenuItem(int playerOptionIndex, String menuText)
{
client.getPlayerOptions()[playerOptionIndex] = menuText;
client.getPlayerOptionsPriorities()[playerOptionIndex] = true;
client.getPlayerMenuTypes()[playerOptionIndex] = MenuAction.RUNELITE.getId();
playerMenuIndexMap.put(playerOptionIndex, menuText);
}
private void removePlayerMenuItem(int playerOptionIndex)
{
client.getPlayerOptions()[playerOptionIndex] = null;
playerMenuIndexMap.remove(playerOptionIndex);
}
/**
* Find the next empty player menu slot index
*/
private int findEmptyPlayerMenuIndex()
{
int index = IDX_LOWER;
String[] playerOptions = client.getPlayerOptions();
while (index < IDX_UPPER && playerOptions[index] != null)
{
index++;
}
return index;
}
/**
* Adds to the set of menu entries which when present, will remove all entries except for this one
*/
public void addPriorityEntry(String option, String target)
{
option = Text.standardize(option);
target = Text.standardize(target);
ComparableEntry entry = new ComparableEntry(option, target);
priorityEntries.add(entry);
}
public void removePriorityEntry(String option, String target)
{
option = Text.standardize(option);
target = Text.standardize(target);
ComparableEntry entry = new ComparableEntry(option, target);
Set<ComparableEntry> toRemove = new HashSet<>();
for (ComparableEntry priorityEntry : priorityEntries)
{
if (entry.equals(priorityEntry))
{
toRemove.add(entry);
}
}
for (ComparableEntry e : toRemove)
{
priorityEntries.remove(e);
}
}
/**
* Adds to the set of menu entries which when present, will remove all entries except for this one
* This method will add one with strict option, but not-strict target (contains for target, equals for option)
*/
public void addPriorityEntry(String option)
{
option = Text.standardize(option);
ComparableEntry entry = new ComparableEntry(option, "", false);
priorityEntries.add(entry);
}
public void removePriorityEntry(String option)
{
option = Text.standardize(option);
ComparableEntry entry = new ComparableEntry(option, "", false);
Set<ComparableEntry> toRemove = new HashSet<>();
for (ComparableEntry priorityEntry : priorityEntries)
{
if (entry.equals(priorityEntry))
{
toRemove.add(entry);
}
}
for (ComparableEntry e : toRemove)
{
priorityEntries.remove(e);
}
}
/**
* Adds to the map of swaps. Strict options, not strict target but target1=target2
*/
public void addSwap(String option, String target, String option2)
{
addSwap(option, target, option2, target, true, false);
}
/**
* Adds to the map of swaps.
*/
public void addSwap(String option, String target, String option2, String target2, boolean strictOption, boolean strictTarget)
{
option = Text.standardize(option);
target = Text.standardize(target);
option2 = Text.standardize(option2);
target2 = Text.standardize(target2);
ComparableEntry swapFrom = new ComparableEntry(option, target, -1, -1, strictOption, strictTarget);
ComparableEntry swapTo = new ComparableEntry(option2, target2, -1, -1, strictOption, strictTarget);
if (swapTo.equals(swapFrom))
{
log.warn("You shouldn't try swapping an entry for itself");
return;
}
swaps.put(swapFrom, swapTo);
}
public void removeSwap(String option, String target, String option2, String target2, boolean strictOption, boolean strictTarget)
{
option = Text.standardize(option);
target = Text.standardize(target);
option2 = Text.standardize(option2);
target2 = Text.standardize(target2);
ComparableEntry swapFrom = new ComparableEntry(option, target, -1, -1, strictOption, strictTarget);
ComparableEntry swapTo = new ComparableEntry(option2, target2, -1, -1, strictOption, strictTarget);
removeSwap(swapFrom, swapTo);
}
/**
* Adds to the map of swaps. - Strict option + target
*/
public void addSwap(String option, String target, String option2, String target2)
{
addSwap(option, target, option2, target2, false, false);
}
public void removeSwap(String option, String target, String option2, String target2)
{
removeSwap(option, target, option2, target2, false, false);
}
/**
* Adds to the map of swaps - Pre-baked Abstract entry
*/
public void addSwap(ComparableEntry swapFrom, ComparableEntry swapTo)
{
if (swapTo.equals(swapFrom))
{
log.warn("You shouldn't try swapping an entry for itself");
return;
}
swaps.put(swapFrom, swapTo);
}
/**
* Adds to the map of swaps - Non-strict option/target, but with type & id
* ID's of -1 are ignored in matches()!
*/
public void addSwap(String option, String target, int id, int type, String option2, String target2, int id2, int type2)
{
option = Text.standardize(option);
target = Text.standardize(target);
option2 = Text.standardize(option2);
target2 = Text.standardize(target2);
ComparableEntry swapFrom = new ComparableEntry(option, target, id, type, false, false);
ComparableEntry swapTo = new ComparableEntry(option2, target2, id2, type2, false, false);
if (swapTo.equals(swapFrom))
{
log.warn("You shouldn't try swapping an entry for itself");
return;
}
swaps.put(swapFrom, swapTo);
}
public void removeSwap(String option, String target, int id, int type, String option2, String target2, int id2, int type2)
{
option = Text.standardize(option);
target = Text.standardize(target);
option2 = Text.standardize(option2);
target2 = Text.standardize(target2);
ComparableEntry swapFrom = new ComparableEntry(option, target, id, type, false, false);
ComparableEntry swapTo = new ComparableEntry(option2, target2, id2, type2, false, false);
Set<ComparableEntry> toRemove = new HashSet<>();
for (Map.Entry<ComparableEntry, ComparableEntry> e : swaps.entrySet())
{
if (e.getKey().equals(swapFrom) && e.getValue().equals(swapTo))
{
toRemove.add(e.getKey());
}
}
for (ComparableEntry entry : toRemove)
{
swaps.remove(entry);
}
}
public void removeSwap(ComparableEntry swapFrom, ComparableEntry swapTo)
{
Set<ComparableEntry> toRemove = new HashSet<>();
for (Map.Entry<ComparableEntry, ComparableEntry> e : swaps.entrySet())
{
if (e.getKey().equals(swapFrom) && e.getValue().equals(swapTo))
{
toRemove.add(e.getKey());
}
}
for (ComparableEntry entry : toRemove)
{
swaps.remove(entry);
}
}
/**
* Removes all swaps with target
*/
public void removeSwaps(String withTarget)
{
withTarget = Text.standardize(withTarget);
Set<ComparableEntry> toRemove = new HashSet<>();
for (ComparableEntry e : swaps.keySet())
{
if (e.getTarget().equals(withTarget))
{
toRemove.add(e);
}
}
for (ComparableEntry entry : toRemove)
{
swaps.remove(entry);
}
}
}

View File

@@ -0,0 +1,105 @@
/*
* Copyright (c) 2017, Robin <robin.weymans@gmail.com>
* 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.menus;
import api.widgets.WidgetInfo;
import java.awt.Color;
import net.runelite.client.ui.JagexColors;
import net.runelite.client.util.ColorUtil;
public final class WidgetMenuOption
{
/**
* The left hand text to be displayed on the menu option. Ex. the menuOption of "Drop Bones" is "Drop"
*/
private String menuOption;
/**
* The right hand text to be displayed on the menu option Ex. the menuTarget of "Drop Bones" is "Bones"
*/
private String menuTarget;
/**
* The color that the menuTarget should be. Defaults to the brownish color that most menu options have.
*/
private Color color = JagexColors.MENU_TARGET;
/**
* The widget to add the option to
*/
private final WidgetInfo widget;
/**
* Creates a menu to be added to right click menus. The menu will only be added if match is found within the menu options
*
* @param menuOption Option text of this right click option
* @param menuTarget Target text of this right click option
* @param widget The widget to attach this option to
*/
public WidgetMenuOption(String menuOption, String menuTarget, WidgetInfo widget)
{
this.menuOption = menuOption;
setMenuTarget(menuTarget);
this.widget = widget;
}
public void setMenuOption(String option)
{
menuOption = option;
}
/**
* Sets the target of the menu option. Color code will be added on to target
*
* @param target The target text without color code.
*/
public void setMenuTarget(String target)
{
menuTarget = ColorUtil.wrapWithColorTag(target, color);
}
public String getMenuOption()
{
return menuOption;
}
public String getMenuTarget()
{
return menuTarget;
}
public WidgetInfo getWidget()
{
return widget;
}
public Color getColor()
{
return color;
}
public void setColor(Color col)
{
color = col;
}
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright (c) 2016-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.client.plugins;
import com.google.inject.Binder;
import com.google.inject.Injector;
import com.google.inject.Module;
import java.io.File;
public abstract class Plugin implements Module
{
protected Injector injector;
public File file;
public PluginClassLoader loader;
@Override
public void configure(Binder binder)
{
}
protected void startUp() throws Exception
{
}
protected void shutDown() throws Exception
{
}
public final Injector getInjector()
{
return injector;
}
}

View File

@@ -0,0 +1,67 @@
/*
* Copyright (c) 2016-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.client.plugins;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
/**
* A classloader for external plugins
*
* @author Adam
*/
public class PluginClassLoader extends URLClassLoader
{
private final ClassLoader parent;
public PluginClassLoader(File plugin, ClassLoader parent) throws MalformedURLException
{
super(
new URL[]
{
plugin.toURI().toURL()
},
null // null or else class path scanning includes everything from the main class loader
);
this.parent = parent;
}
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException
{
try
{
return super.loadClass(name);
}
catch (ClassNotFoundException ex)
{
// fall back to main class loader
return parent.loadClass(name);
}
}
}

View File

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

View File

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

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