Merge pull request #3140 from Owain94/upstream-0903

project: Merge upstream
This commit is contained in:
Owain van Brakel
2022-03-09 23:10:38 +01:00
committed by GitHub
32 changed files with 897 additions and 248 deletions

View File

@@ -25,9 +25,9 @@
object ProjectVersions {
const val launcherVersion = "2.2.0"
const val rlVersion = "1.8.12"
const val rlVersion = "1.8.13"
const val openosrsVersion = "4.20.2"
const val openosrsVersion = "4.20.3"
const val rsversion = 203
const val cacheversion = 165

View File

@@ -38,8 +38,8 @@ public class TextureDefinition
public int[] field1780;
public int[] field1781;
public int[] field1786;
public int field1782;
public int field1783;
public int animationSpeed;
public int animationDirection;
public transient int[] pixels;

View File

@@ -26,13 +26,9 @@ package net.runelite.cache.definitions.loaders;
import net.runelite.cache.definitions.TextureDefinition;
import net.runelite.cache.io.InputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class TextureLoader
{
private static final Logger logger = LoggerFactory.getLogger(TextureLoader.class);
public TextureDefinition load(int id, byte[] b)
{
TextureDefinition def = new TextureDefinition();
@@ -77,8 +73,8 @@ public class TextureLoader
def.field1786[var3] = is.readInt();
}
def.field1783 = is.readUnsignedByte();
def.field1782 = is.readUnsignedByte();
def.animationDirection = is.readUnsignedByte();
def.animationSpeed = is.readUnsignedByte();
return def;
}

View File

@@ -25,7 +25,7 @@
package net.runelite.cache.script.assembler;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -267,7 +267,7 @@ public class ScriptWriter extends rs2asmBaseListener
continue;
}
Map<Integer, Integer> map = maps[index++] = new HashMap<>();
Map<Integer, Integer> map = maps[index++] = new LinkedHashMap<>();
for (LookupCase scase : lswitch.getCases())
{

View File

@@ -445,6 +445,20 @@ public interface Client extends GameEngine
@Nullable
SpritePixels createItemSprite(int itemId, int quantity, int border, int shadowColor, @MagicConstant(valuesFromClass = ItemQuantityMode.class) int stackable, boolean noted, int scale);
/**
* Get the item model cache. These models are used for drawing widgets of type {@link net.runelite.api.widgets.WidgetType#MODEL}
* and inventory item icons
* @return
*/
NodeCache getItemModelCache();
/**
* Get the item sprite cache. These are 2d SpritePixels which are used to raster item images on the inventory and
* on widgets of type {@link net.runelite.api.widgets.WidgetType#GRAPHIC}
* @return
*/
NodeCache getItemSpriteCache();
/**
* Loads and creates the sprite images of the passed archive and file IDs.
*
@@ -2042,6 +2056,18 @@ public interface Client extends GameEngine
*/
void setSpellSelected(boolean selected);
/**
* Get if an item is selected with "Use"
* @return 1 if selected, else 0
*/
int getSelectedItem();
/**
* If an item is selected, this is the item index in the inventory.
* @return
*/
int getSelectedItemIndex();
/**
* Returns client item composition cache
*/

View File

@@ -8,14 +8,15 @@ import javax.annotation.Nullable;
public interface ItemComposition extends ParamHolder
{
/**
* Gets the items name.
* Gets the item's name.
*
* @return the name of the item
*/
String getName();
/**
* Sets the items name.
* Sets the item's name.
* @param name the new name
*/
void setName(String name);
@@ -141,20 +142,109 @@ public interface ItemComposition extends ParamHolder
int getInventoryModel();
/**
* Since the client reuses item models, it stores colors that can be replaced.
* This returns what colors the item model will be replaced with.
*
* Set the model ID of the inventory item. You will also need to flush the item model cache and the item
* sprite cache to have the changes fully propagated after changing this value.
* @see Client#getItemModelCache()
* @see Client#getItemSpriteCache()
*/
void setInventoryModel(int model);
/**
* Get the colors to be replaced on this item's model for this item.
* @see JagexColor
* @see ItemComposition#getColorToReplaceWith()
* @return the colors to be replaced
*/
@Nullable
short[] getColorToReplace();
/**
* Set the colors to be replaced on this item's model for this item.
* @see JagexColor
* @see ItemComposition#setColorToReplaceWith(short[])
*/
void setColorToReplace(short[] colorsToReplace);
/**
* Get the colors applied to this item's model for this item.
* @see JagexColor
* @see ItemComposition#getColorToReplace()
* @return the colors to replace with
*/
@Nullable
short[] getColorToReplaceWith();
/**
* Since the client reuses item models, it stores textures that can be replaced.
* This returns what textures the item model will be replaced with.
*
* Set the colors applied to this item's model for this item.
* @see JagexColor
* @see ItemComposition#setColorToReplace(short[])
*/
void setColorToReplaceWith(short[] colorToReplaceWith);
/**
* Get the textures to be replaced on this item's model for this item.
* @see ItemComposition#getTextureToReplaceWith()
* @return the textures to be replaced
*/
@Nullable
short[] getTextureToReplace();
/**
* Set the textures to be replaced on this item's model for this item.
* @see ItemComposition#setTextureToReplaceWith(short[])
*/
void setTextureToReplace(short[] textureToFind);
/**
* Get the textures applied to this item's model for this item.
* @see ItemComposition#getTextureToReplace()
* @return the textures to replace with
*/
@Nullable
short[] getTextureToReplaceWith();
/**
* Set the textures applied to this item's model for this item.
* @see ItemComposition#setTextureToReplace(short[])
*/
void setTextureToReplaceWith(short[] textureToReplaceWith);
/**
* Get the x angle for 2d item sprites used in the inventory.
* @see net.runelite.api.coords.Angle
* @return
*/
int getXan2d();
/**
* Get the y angle for 2d item sprites used in the inventory.
* @see net.runelite.api.coords.Angle
* @return
*/
int getYan2d();
/**
* Get the z angle for 2d item sprites used in the inventory.
* @see net.runelite.api.coords.Angle
* @return
*/
int getZan2d();
/**
* Set the x angle for 2d item sprites used in the inventory.
* @see net.runelite.api.coords.Angle
*/
void setXan2d(int angle);
/**
* Set the y angle for 2d item sprites used in the inventory.
* @see net.runelite.api.coords.Angle
*/
void setYan2d(int angle);
/**
* Set the z angle for 2d item sprites used in the inventory.
* @see net.runelite.api.coords.Angle
*/
void setZan2d(int angle);
}

View File

@@ -48,8 +48,6 @@ public interface NPCComposition extends ParamHolder
boolean isClickable();
boolean isFollower();
/**
* NPC can be interacting with via menu options
* @return
@@ -98,4 +96,11 @@ public interface NPCComposition extends ParamHolder
* Gets the displayed overhead icon of the NPC.
*/
HeadIcon getOverheadIcon();
}
/**
* If the npc is a follower, such as a pet. Is affected by the
* "Move follower options lower down" setting.
* @return
*/
boolean isFollower();
}

View File

@@ -173,7 +173,7 @@ public class RuneLite
{
Locale.setDefault(Locale.ENGLISH);
final OptionParser parser = new OptionParser();
final OptionParser parser = new OptionParser(false);
parser.accepts("developer-mode", "Enable developer tools");
parser.accepts("debug", "Show extra debugging output");
parser.accepts("safe-mode", "Disables external plugins and the GPU plugin");
@@ -389,6 +389,7 @@ public class RuneLite
// Load the plugins, but does not start them yet.
// This will initialize configuration
pluginManager.loadCorePlugins();
pluginManager.loadSideLoadPlugins();
oprsExternalPluginManager.loadPlugins();

View File

@@ -374,7 +374,27 @@ public class ConfigManager
public List<String> getConfigurationKeys(String prefix)
{
return properties.keySet().stream().filter(v -> ((String) v).startsWith(prefix)).map(String.class::cast).collect(Collectors.toList());
return properties.keySet().stream()
.map(String.class::cast)
.filter(k -> k.startsWith(prefix))
.collect(Collectors.toList());
}
public List<String> getRSProfileConfigurationKeys(String group, String profile, String keyPrefix)
{
if (profile == null)
{
return Collections.emptyList();
}
assert profile.startsWith(RSPROFILE_GROUP);
String prefix = group + "." + profile + "." + keyPrefix;
return properties.keySet().stream()
.map(String.class::cast)
.filter(k -> k.startsWith(prefix))
.map(k -> splitKey(k)[KEY_SPLITTER_KEY])
.collect(Collectors.toList());
}
public static String getWholeKey(String groupName, String profile, String key)
@@ -534,6 +554,8 @@ public class ConfigManager
RuneScapeProfile prof = findRSProfile(getRSProfiles(), username, RuneScapeProfileType.getCurrent(client), displayName, true);
rsProfileKey = prof.getKey();
this.rsProfileKey = rsProfileKey;
eventBus.post(new RuneScapeProfileChanged());
}
setConfiguration(groupName, rsProfileKey, key, value);
}
@@ -982,7 +1004,10 @@ public class ConfigManager
return methods;
}
@Subscribe(priority = 100)
@Subscribe(
// run after plugins, in the event they save config on shutdown
priority = -100
)
private void onClientShutdown(ClientShutdown e)
{
Future<Void> f = sendConfig();

View File

@@ -38,5 +38,9 @@ import java.lang.annotation.Target;
@Documented
public @interface Subscribe
{
/**
* Priority relative to other event subscribers. Higher priorities run first.
* @return
*/
float priority() default 0;
}

View File

@@ -365,8 +365,7 @@ public class ItemManager
{
return wikiPrice;
}
int d = jagPrice - (int) (jagPrice * activePriceThreshold);
return wikiPrice >= jagPrice - d && wikiPrice <= jagPrice + d ? wikiPrice : jagPrice;
return wikiPrice < jagPrice * activePriceThreshold ? wikiPrice : jagPrice;
}
/**

View File

@@ -0,0 +1,57 @@
/*
* 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;
class PluginClassLoader extends URLClassLoader
{
private final ClassLoader parent;
PluginClassLoader(File plugin, ClassLoader parent) throws MalformedURLException
{
// null parent classloader, or else class path scanning includes everything from the main class loader
super(new URL[]{plugin.toURI().toURL()}, null);
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

@@ -37,6 +37,7 @@ import com.google.inject.CreationException;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Module;
import java.io.File;
import java.io.IOException;
import java.lang.invoke.CallSite;
import java.lang.invoke.LambdaMetafactory;
@@ -89,6 +90,7 @@ public class PluginManager
* Base package where the core plugins are
*/
private static final String PLUGIN_PACKAGE = "net.runelite.client.plugins";
private static final File SIDELOADED_PLUGINS = new File(RuneLite.RUNELITE_DIR, "sideloaded-plugins");
private final boolean developerMode;
private final boolean safeMode;
@@ -309,6 +311,45 @@ public class PluginManager
SplashScreen.stage(.60, .70, null, "Loading Plugins", loaded, total, false));
}
public void loadSideLoadPlugins()
{
if (!developerMode)
{
return;
}
File[] files = SIDELOADED_PLUGINS.listFiles();
if (files == null)
{
return;
}
for (File f : files)
{
if (f.getName().endsWith(".jar"))
{
log.info("Side-loading plugin {}", f);
try
{
ClassLoader classLoader = new PluginClassLoader(f, getClass().getClassLoader());
List<Class<?>> plugins = ClassPath.from(classLoader)
.getAllClasses()
.stream()
.map(ClassInfo::load)
.collect(Collectors.toList());
loadPlugins(plugins, null);
}
catch (PluginInstantiationException | IOException ex)
{
log.error("error sideloading plugin", ex);
}
}
}
}
public List<Plugin> loadPlugins(List<Class<?>> plugins, BiConsumer<Integer, Integer> onPluginLoaded) throws PluginInstantiationException
{
MutableGraph<Class<? extends Plugin>> graph = GraphBuilder

View File

@@ -153,7 +153,7 @@ public class CrypticClue extends ClueScroll implements TextClueScroll, NpcClueSc
new CrypticClue("Search the crates in the Port Sarim Fishing shop.", CRATE_9534, new WorldPoint(3012, 3222, 0), "Search the crates, by the door, in Gerrant's Fishy Business in Port Sarim."),
new CrypticClue("Speak to The Lady of the Lake.", "The Lady of the Lake", new WorldPoint(2924, 3405, 0), "Talk to The Lady of the Lake in Taverley."),
new CrypticClue("Rotting next to a ditch. Dig next to the fish.", new WorldPoint(3547, 3183, 0), "Dig next to a fishing spot on the south-east side of Burgh de Rott."),
new CrypticClue("The King's magic won't be wasted by me.", "Guardian Mummy", new WorldPoint(1934, 4427, 0), "Talk to the Guardian mummy inside the Pyramid Plunder minigame in Sophanem."),
new CrypticClue("The King's magic won't be wasted by me.", "Guardian mummy", new WorldPoint(1934, 4427, 0), "Talk to the Guardian mummy inside the Pyramid Plunder minigame in Sophanem."),
new CrypticClue("Dig where the forces of Zamorak and Saradomin collide.", new WorldPoint(3049, 4839, 0), "Dig next to the law rift in the Abyss."),
new CrypticClue("Search the boxes in the goblin house near Lumbridge.", BOXES, new WorldPoint(3245, 3245, 0), "Goblin house on the eastern side of the river outside of Lumbridge."),
new CrypticClue("W marks the spot.", new WorldPoint(2867, 3546, 0), "Dig in the middle of the Warriors' Guild entrance hall."),

View File

@@ -223,7 +223,6 @@ public class GpuPlugin extends Plugin implements DrawCallbacks
private int textureArrayId;
private final GLBuffer uniformBuffer = new GLBuffer();
private final float[] textureOffsets = new float[256];
private GpuIntBuffer vertexBuffer;
private GpuFloatBuffer uvBuffer;
@@ -291,12 +290,13 @@ public class GpuPlugin extends Plugin implements DrawCallbacks
private int uniTexTargetDimensions;
private int uniUiAlphaOverlay;
private int uniTextures;
private int uniTextureOffsets;
private int uniTextureAnimations;
private int uniBlockSmall;
private int uniBlockLarge;
private int uniBlockMain;
private int uniSmoothBanding;
private int uniTextureLightMode;
private int uniTick;
private int needsReset;
@@ -665,6 +665,7 @@ public class GpuPlugin extends Plugin implements DrawCallbacks
uniDrawDistance = gl.glGetUniformLocation(glProgram, "drawDistance");
uniColorBlindMode = gl.glGetUniformLocation(glProgram, "colorBlindMode");
uniTextureLightMode = gl.glGetUniformLocation(glProgram, "textureLightMode");
uniTick = gl.glGetUniformLocation(glProgram, "tick");
uniTex = gl.glGetUniformLocation(glUiProgram, "tex");
uniTexSamplingMode = gl.glGetUniformLocation(glUiProgram, "samplingMode");
@@ -673,7 +674,7 @@ public class GpuPlugin extends Plugin implements DrawCallbacks
uniUiColorBlindMode = gl.glGetUniformLocation(glUiProgram, "colorBlindMode");
uniUiAlphaOverlay = gl.glGetUniformLocation(glUiProgram, "alphaOverlay");
uniTextures = gl.glGetUniformLocation(glProgram, "textures");
uniTextureOffsets = gl.glGetUniformLocation(glProgram, "textureOffsets");
uniTextureAnimations = gl.glGetUniformLocation(glProgram, "textureAnimations");
uniBlockSmall = gl.glGetUniformBlockIndex(glSmallComputeProgram, "uniforms");
uniBlockLarge = gl.glGetUniformBlockIndex(glComputeProgram, "uniforms");
@@ -1222,18 +1223,25 @@ public class GpuPlugin extends Plugin implements DrawCallbacks
gl.glClear(gl.GL_COLOR_BUFFER_BIT);
// Draw 3d scene
final TextureProvider textureProvider = client.getTextureProvider();
final GameState gameState = client.getGameState();
if (textureProvider != null && gameState.getState() >= GameState.LOADING.getState())
if (gameState.getState() >= GameState.LOADING.getState())
{
final TextureProvider textureProvider = client.getTextureProvider();
if (textureArrayId == -1)
{
// lazy init textures as they may not be loaded at plugin start.
// this will return -1 and retry if not all textures are loaded yet, too.
textureArrayId = textureManager.initTextureArray(textureProvider, gl);
if (textureArrayId > -1)
{
// if texture upload is successful, compute and set texture animations
float[] texAnims = textureManager.computeTextureAnimations(textureProvider);
gl.glUseProgram(glProgram);
gl.glUniform2fv(uniTextureAnimations, texAnims.length, texAnims, 0);
gl.glUseProgram(0);
}
}
final Texture[] textures = textureProvider.getTextures();
int renderWidthOff = viewportOffsetX;
int renderHeightOff = viewportOffsetY;
int renderCanvasHeight = canvasHeight;
@@ -1285,6 +1293,11 @@ public class GpuPlugin extends Plugin implements DrawCallbacks
gl.glUniform1f(uniSmoothBanding, config.smoothBanding() ? 0f : 1f);
gl.glUniform1i(uniColorBlindMode, config.colorBlindMode().ordinal());
gl.glUniform1f(uniTextureLightMode, config.brightTextures() ? 1f : 0f);
if (gameState == GameState.LOGGED_IN)
{
// avoid textures animating during loading
gl.glUniform1i(uniTick, client.getGameCycle());
}
// Calculate projection matrix
Matrix4 projectionMatrix = new Matrix4();
@@ -1295,24 +1308,9 @@ public class GpuPlugin extends Plugin implements DrawCallbacks
projectionMatrix.translate(-client.getCameraX2(), -client.getCameraY2(), -client.getCameraZ2());
gl.glUniformMatrix4fv(uniProjectionMatrix, 1, false, projectionMatrix.getMatrix(), 0);
for (int id = 0; id < textures.length; ++id)
{
Texture texture = textures[id];
if (texture == null)
{
continue;
}
textureProvider.load(id); // trips the texture load flag which lets textures animate
textureOffsets[id * 2] = texture.getU();
textureOffsets[id * 2 + 1] = texture.getV();
}
// Bind uniforms
gl.glUniformBlockBinding(glProgram, uniBlockMain, 0);
gl.glUniform1i(uniTextures, 1); // texture sampler array is bound to texture1
gl.glUniform2fv(uniTextureOffsets, textureOffsets.length, textureOffsets, 0);
// We just allow the GL to do face culling. Note this requires the priority renderer
// to have logic to disregard culled faces in the priority depth testing.
@@ -1518,7 +1516,7 @@ public class GpuPlugin extends Plugin implements DrawCallbacks
@Override
public void animate(Texture texture, int diff)
{
textureManager.animate(texture, diff);
// texture animation happens on gpu
}
@Subscribe

View File

@@ -35,9 +35,6 @@ import net.runelite.api.TextureProvider;
@Slf4j
class TextureManager
{
private static final float PERC_64 = 1f / 64f;
private static final float PERC_128 = 1f / 128f;
private static final int TEXTURE_SIZE = 128;
int initTextureArray(TextureProvider textureProvider, GL4 gl)
@@ -207,64 +204,42 @@ class TextureManager
return pixels;
}
/**
* Animate the given texture
*
* @param texture
* @param diff Number of elapsed client ticks since last animation
*/
void animate(Texture texture, int diff)
float[] computeTextureAnimations(TextureProvider textureProvider)
{
final int[] pixels = texture.getPixels();
if (pixels == null)
Texture[] textures = textureProvider.getTextures();
float[] anims = new float[TEXTURE_SIZE * 2];
for (int i = 0; i < textures.length; ++i)
{
return;
Texture texture = textures[i];
if (texture == null)
{
continue;
}
float u = 0f, v = 0f;
switch (texture.getAnimationDirection())
{
case 1:
v = -1f;
break;
case 3:
v = 1f;
break;
case 2:
u = -1f;
break;
case 4:
u = 1f;
break;
}
int speed = texture.getAnimationSpeed();
u *= speed;
v *= speed;
anims[i * 2] = u;
anims[i * 2 + 1] = v;
}
final int animationSpeed = texture.getAnimationSpeed();
final float uvdiff = pixels.length == 4096 ? PERC_64 : PERC_128;
float u = texture.getU();
float v = texture.getV();
int offset = animationSpeed * diff;
float d = (float) offset * uvdiff;
switch (texture.getAnimationDirection())
{
case 1:
v -= d;
if (v < 0f)
{
v += 1f;
}
break;
case 3:
v += d;
if (v > 1f)
{
v -= 1f;
}
break;
case 2:
u -= d;
if (u < 0f)
{
u += 1f;
}
break;
case 4:
u += d;
if (u > 1f)
{
u -= 1f;
}
break;
default:
return;
}
texture.setU(u);
texture.setV(v);
return anims;
}
}

View File

@@ -60,7 +60,8 @@ import net.runelite.client.plugins.PluginDescriptor;
@PluginDescriptor(
name = "Idle Notifier",
description = "Send a notification when going idle, or when HP/Prayer reaches a threshold",
tags = {"health", "hitpoints", "notifications", "prayer"}
tags = {"health", "hitpoints", "notifications", "prayer"},
enabledByDefault = false
)
public class IdleNotifierPlugin extends Plugin
{

View File

@@ -317,6 +317,10 @@ enum ItemIdentification
FISHING(Type.POTION, "Fishing", "Fi", ItemID.FISHING_POTION4, ItemID.FISHING_POTION3, ItemID.FISHING_POTION2, ItemID.FISHING_POTION1),
HUNTER(Type.POTION, "Hunter", "Hu", ItemID.HUNTER_POTION4, ItemID.HUNTER_POTION3, ItemID.HUNTER_POTION2, ItemID.HUNTER_POTION1),
GOBLIN(Type.POTION, "Goblin", "G", ItemID.GOBLIN_POTION4, ItemID.GOBLIN_POTION3, ItemID.GOBLIN_POTION2, ItemID.GOBLIN_POTION1),
MAGIC_ESS(Type.POTION, "MagEss", "M.E", ItemID.MAGIC_ESSENCE4, ItemID.MAGIC_ESSENCE3, ItemID.MAGIC_ESSENCE2, ItemID.MAGIC_ESSENCE1),
REJUVENATION(Type.POTION, "Rejuv", "Rj", ItemID.REJUVENATION_POTION_4, ItemID.REJUVENATION_POTION_3, ItemID.REJUVENATION_POTION_2, ItemID.REJUVENATION_POTION_1),
// Unfinished Potions
GUAM_POTION(Type.POTION, "Guam", "G", ItemID.GUAM_POTION_UNF),
MARRENTILL_POTION(Type.POTION, "Marren", "M", ItemID.MARRENTILL_POTION_UNF),

View File

@@ -0,0 +1,73 @@
/*
* Copyright (c) 2022, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.client.plugins.loottracker;
import java.time.Instant;
import java.util.Arrays;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import net.runelite.http.api.loottracker.LootRecordType;
@Data
@NoArgsConstructor
@EqualsAndHashCode(of = {"type", "name"})
class ConfigLoot
{
LootRecordType type;
String name;
int kills;
Instant first = Instant.now();
Instant last;
int[] drops;
ConfigLoot(LootRecordType type, String name)
{
this.type = type;
this.name = name;
this.drops = new int[0];
}
void add(int id, int qty)
{
for (int i = 0; i < drops.length; i += 2)
{
if (drops[i] == id)
{
drops[i + 1] += qty;
return;
}
}
drops = Arrays.copyOf(drops, drops.length + 2);
drops[drops.length - 2] = id;
drops[drops.length - 1] = qty;
}
int numDrops()
{
return drops.length / 2;
}
}

View File

@@ -72,6 +72,7 @@ class LootTrackerBox extends JPanel
private final ItemManager itemManager;
@Getter(AccessLevel.PACKAGE)
private final String id;
@Getter(AccessLevel.PACKAGE)
private final LootRecordType lootRecordType;
private final LootTrackerPriceType priceType;
private final boolean showPriceType;
@@ -316,6 +317,7 @@ class LootTrackerBox extends JPanel
itemContainer.removeAll();
itemContainer.setLayout(new GridLayout(rowSize, ITEMS_PER_ROW, 1, 1));
final EmptyBorder emptyBorder = new EmptyBorder(5, 5, 5, 5);
for (int i = 0; i < rowSize * ITEMS_PER_ROW; i++)
{
final JPanel slotContainer = new JPanel();
@@ -350,7 +352,7 @@ class LootTrackerBox extends JPanel
// Create popup menu
final JPopupMenu popupMenu = new JPopupMenu();
popupMenu.setBorder(new EmptyBorder(5, 5, 5, 5));
popupMenu.setBorder(emptyBorder);
slotContainer.setComponentPopupMenu(popupMenu);
final JMenuItem toggle = new JMenuItem("Toggle item");

View File

@@ -30,9 +30,11 @@ import net.runelite.client.config.ConfigGroup;
import net.runelite.client.config.ConfigItem;
import net.runelite.client.config.ConfigSection;
@ConfigGroup("loottracker")
@ConfigGroup(LootTrackerConfig.GROUP)
public interface LootTrackerConfig extends Config
{
String GROUP = "loottracker";
@ConfigSection(
name = "Ignored Entries",
description = "The Ignore items and Ignore groups options",
@@ -79,28 +81,6 @@ public interface LootTrackerConfig extends Config
return false;
}
@ConfigItem(
keyName = "saveLoot",
name = "Submit loot tracker data",
description = "Submit loot tracker data"
)
default boolean saveLoot()
{
return true;
}
@ConfigItem(
keyName = "syncPanel",
name = "Synchronize panel contents",
description = "Synchronize your local loot tracker with your server data (requires being signed in).<br/>" +
" This means the panel is filled with portions of your remote data on startup<br/>" +
" and deleting data in the panel also deletes it on the server."
)
default boolean syncPanel()
{
return true;
}
@ConfigItem(
keyName = "ignoredEvents",
name = "Ignored Loot Sources",

View File

@@ -87,10 +87,12 @@ class LootTrackerPanel extends PluginPanel
private static final String HTML_LABEL_TEMPLATE =
"<html><body style='color:%s'>%s<span style='color:white'>%s</span></body></html>";
private static final String SYNC_RESET_ALL_WARNING_TEXT =
"This will permanently delete the current loot from both the client and the RuneLite website.";
private static final String NO_SYNC_RESET_ALL_WARNING_TEXT =
"This will permanently delete the current loot from the client.";
private static final String RESET_ALL_WARNING_TEXT =
"<html>This will permanently delete <b>all</b> loot.</html>";
private static final String RESET_CURRENT_WARNING_TEXT =
"This will permanently delete \"%s\" loot.";
private static final String RESET_ONE_WARNING_TEXT =
"This will delete one kill.";
// When there is no loot, display this
private final PluginErrorPanel errorPanel = new PluginErrorPanel();
@@ -99,13 +101,13 @@ class LootTrackerPanel extends PluginPanel
private final JPanel logsContainer = new JPanel();
// Handle overall session data
private final JPanel overallPanel = new JPanel();
private final JPanel overallPanel;
private final JLabel overallKillsLabel = new JLabel();
private final JLabel overallGpLabel = new JLabel();
private final JLabel overallIcon = new JLabel();
// Details and navigation
private final JPanel actionsContainer = new JPanel();
private final JPanel actionsPanel;
private final JLabel detailsTitle = new JLabel();
private final JButton backBtn = new JButton();
private final JToggleButton viewHiddenBtn = new JToggleButton();
@@ -175,6 +177,28 @@ class LootTrackerPanel extends PluginPanel
layoutPanel.setLayout(new BoxLayout(layoutPanel, BoxLayout.Y_AXIS));
add(layoutPanel, BorderLayout.NORTH);
actionsPanel = buildActionsPanel();
overallPanel = buildOverallPanel();
// Create loot boxes wrapper
logsContainer.setLayout(new BoxLayout(logsContainer, BoxLayout.Y_AXIS));
layoutPanel.add(actionsPanel);
layoutPanel.add(overallPanel);
layoutPanel.add(logsContainer);
// Add error pane
errorPanel.setContent("Loot tracker", "You have not received any loot yet.");
add(errorPanel);
}
/**
* The actions panel includes the back/title label for the current view,
* as well as the view controls panel which includes hidden, single/grouped, and
* collapse buttons.
*/
private JPanel buildActionsPanel()
{
final JPanel actionsContainer = new JPanel();
actionsContainer.setLayout(new BorderLayout());
actionsContainer.setBackground(ColorScheme.DARKER_GRAY_COLOR);
actionsContainer.setPreferredSize(new Dimension(0, 30));
@@ -252,7 +276,13 @@ class LootTrackerPanel extends PluginPanel
actionsContainer.add(viewControls, BorderLayout.EAST);
actionsContainer.add(leftTitleContainer, BorderLayout.WEST);
return actionsContainer;
}
private JPanel buildOverallPanel()
{
// Create panel that will contain overall data
final JPanel overallPanel = new JPanel();
overallPanel.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createMatteBorder(5, 0, 0, 0, ColorScheme.DARK_GRAY_COLOR),
BorderFactory.createEmptyBorder(8, 10, 8, 10)
@@ -277,10 +307,8 @@ class LootTrackerPanel extends PluginPanel
final JMenuItem reset = new JMenuItem("Reset All");
reset.addActionListener(e ->
{
final LootTrackerClient client = plugin.getLootTrackerClient();
final boolean syncLoot = client.getUuid() != null && config.syncPanel();
final int result = JOptionPane.showOptionDialog(overallPanel,
syncLoot ? SYNC_RESET_ALL_WARNING_TEXT : NO_SYNC_RESET_ALL_WARNING_TEXT,
currentView == null ? RESET_ALL_WARNING_TEXT : String.format(RESET_CURRENT_WARNING_TEXT, currentView),
"Are you sure?", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE,
null, new String[]{"Yes", "No"}, "No");
@@ -298,9 +326,14 @@ class LootTrackerPanel extends PluginPanel
logsContainer.repaint();
// Delete all loot, or loot matching the current view
if (syncLoot)
if (currentView != null)
{
client.delete(currentView);
assert currentType != null;
plugin.removeLootConfig(currentType, currentView);
}
else
{
plugin.removeAllLoot();
}
});
@@ -310,15 +343,7 @@ class LootTrackerPanel extends PluginPanel
popupMenu.add(reset);
overallPanel.setComponentPopupMenu(popupMenu);
// Create loot boxes wrapper
logsContainer.setLayout(new BoxLayout(logsContainer, BoxLayout.Y_AXIS));
layoutPanel.add(actionsContainer);
layoutPanel.add(overallPanel);
layoutPanel.add(logsContainer);
// Add error pane
errorPanel.setContent("Loot tracker", "You have not received any loot yet.");
add(errorPanel);
return overallPanel;
}
void updateCollapseText()
@@ -370,6 +395,14 @@ class LootTrackerPanel extends PluginPanel
}
}
/**
* Clear all records in the panel
*/
void clearRecords()
{
aggregateRecords.clear();
}
/**
* Adds a Collection of records to the panel
*/
@@ -511,7 +544,7 @@ class LootTrackerPanel extends PluginPanel
// Show main view
remove(errorPanel);
actionsContainer.setVisible(true);
actionsPanel.setVisible(true);
overallPanel.setVisible(true);
// Create box
@@ -553,10 +586,8 @@ class LootTrackerPanel extends PluginPanel
final JMenuItem reset = new JMenuItem("Reset");
reset.addActionListener(e ->
{
final LootTrackerClient client = plugin.getLootTrackerClient();
final boolean syncLoot = client.getUuid() != null && config.syncPanel();
final int result = JOptionPane.showOptionDialog(overallPanel,
syncLoot ? SYNC_RESET_ALL_WARNING_TEXT : NO_SYNC_RESET_ALL_WARNING_TEXT,
final int result = JOptionPane.showOptionDialog(box,
groupLoot ? String.format(RESET_CURRENT_WARNING_TEXT, box.getId()) : RESET_ONE_WARNING_TEXT,
"Are you sure?", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE,
null, new String[]{"Yes", "No"}, "No");
@@ -578,9 +609,9 @@ class LootTrackerPanel extends PluginPanel
logsContainer.repaint();
// Without loot being grouped we have no way to identify single kills to be deleted
if (client.getUuid() != null && groupLoot && config.syncPanel())
if (groupLoot)
{
client.delete(box.getId());
plugin.removeLootConfig(box.getLootRecordType(), box.getId());
}
});

View File

@@ -26,6 +26,7 @@
package net.runelite.client.plugins.loottracker;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
import com.google.common.collect.HashMultiset;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap;
@@ -33,9 +34,11 @@ import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multiset;
import com.google.common.collect.Multisets;
import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
import com.google.inject.Provides;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
@@ -43,6 +46,7 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
@@ -96,6 +100,7 @@ import net.runelite.client.events.ClientShutdown;
import net.runelite.client.events.ConfigChanged;
import net.runelite.client.events.NpcLootReceived;
import net.runelite.client.events.PlayerLootReceived;
import net.runelite.client.events.RuneScapeProfileChanged;
import net.runelite.client.events.SessionClose;
import net.runelite.client.events.SessionOpen;
import net.runelite.client.game.ItemManager;
@@ -119,12 +124,14 @@ import org.apache.commons.text.WordUtils;
@PluginDescriptor(
name = "Loot Tracker",
description = "Tracks loot from monsters and minigames",
tags = {"drops"},
enabledByDefault = false
tags = {"drops"}
)
@Slf4j
public class LootTrackerPlugin extends Plugin
{
private static final int MAX_DROPS = 1024;
private static final Duration MAX_AGE = Duration.ofDays(365L);
// Activity/Event loot handling
private static final Pattern CLUE_SCROLL_PATTERN = Pattern.compile("You have completed [0-9]+ ([a-z]+) Treasure Trails?\\.");
private static final int THEATRE_OF_BLOOD_REGION = 12867;
@@ -291,6 +298,12 @@ public class LootTrackerPlugin extends Plugin
@Inject
private LootManager lootManager;
@Inject
private ConfigManager configManager;
@Inject
private Gson gson;
private LootTrackerPanel panel;
private NavigationButton navButton;
@VisibleForTesting
@@ -310,6 +323,8 @@ public class LootTrackerPlugin extends Plugin
@Inject
private LootTrackerClient lootTrackerClient;
private final List<LootRecord> queuedLoots = new ArrayList<>();
private String profileKey;
private Instant lastLootImport = Instant.now().minus(1, ChronoUnit.MINUTES);
private static Collection<ItemStack> stack(Collection<ItemStack> items)
{
@@ -367,20 +382,118 @@ public class LootTrackerPlugin extends Plugin
lootTrackerClient.setUuid(null);
}
@Subscribe
public void onRuneScapeProfileChanged(RuneScapeProfileChanged e)
{
final String profileKey = configManager.getRSProfileKey();
if (profileKey == null)
{
return;
}
if (profileKey.equals(this.profileKey))
{
return;
}
log.debug("Profile changed to {}", profileKey);
switchProfile(profileKey);
}
private void switchProfile(String profileKey)
{
executor.execute(() ->
{
// Current queued loot is for the previous profile, so save it first with the current profile key
submitLoot();
this.profileKey = profileKey;
log.debug("Switched to profile {}", profileKey);
int drops = 0;
List<ConfigLoot> loots = new ArrayList<>();
Instant old = Instant.now().minus(MAX_AGE);
for (String key : configManager.getRSProfileConfigurationKeys(LootTrackerConfig.GROUP, profileKey, "drops_"))
{
String json = configManager.getConfiguration(LootTrackerConfig.GROUP, profileKey, key);
ConfigLoot configLoot;
try
{
configLoot = gson.fromJson(json, ConfigLoot.class);
}
catch (JsonSyntaxException ex)
{
log.warn("Removing loot with malformed json: {}", json, ex);
configManager.unsetConfiguration(LootTrackerConfig.GROUP, profileKey, key);
continue;
}
if (configLoot.last.isBefore(old))
{
log.debug("Removing old loot for {} {}", configLoot.type, configLoot.name);
configManager.unsetConfiguration(LootTrackerConfig.GROUP, profileKey, key);
continue;
}
if (drops >= MAX_DROPS && !loots.isEmpty() && loots.get(0).last.isAfter(configLoot.last))
{
// fast drop
continue;
}
sortedInsert(loots, configLoot, Comparator.comparing(ConfigLoot::getLast));
drops += configLoot.numDrops();
if (drops >= MAX_DROPS)
{
ConfigLoot top = loots.remove(0);
drops -= top.numDrops();
}
}
log.debug("Loaded {} records", loots.size());
clientThread.invokeLater(() ->
{
// convertToLootTrackerRecord must be called on client thread
List<LootTrackerRecord> records = loots.stream()
.map(this::convertToLootTrackerRecord)
.collect(Collectors.toList());
SwingUtilities.invokeLater(() ->
{
panel.clearRecords();
panel.addRecords(records);
});
});
});
}
private static <T> void sortedInsert(List<T> list, T value, Comparator<? super T> c)
{
int idx = Collections.binarySearch(list, value, c);
list.add(idx < 0 ? -idx - 1 : idx, value);
}
@Subscribe
public void onConfigChanged(ConfigChanged event)
{
if (event.getGroup().equals("loottracker"))
if (event.getGroup().equals(LootTrackerConfig.GROUP))
{
ignoredItems = Text.fromCSV(config.getIgnoredItems());
ignoredEvents = Text.fromCSV(config.getIgnoredEvents());
SwingUtilities.invokeLater(panel::updateIgnoredRecords);
if ("ignoredItems".equals(event.getKey()) || "ignoredEvents".equals(event.getKey()))
{
ignoredItems = Text.fromCSV(config.getIgnoredItems());
ignoredEvents = Text.fromCSV(config.getIgnoredEvents());
SwingUtilities.invokeLater(panel::updateIgnoredRecords);
}
}
}
@Override
protected void startUp() throws Exception
{
profileKey = null;
ignoredItems = Text.fromCSV(config.getIgnoredItems());
ignoredEvents = Text.fromCSV(config.getIgnoredEvents());
panel = new LootTrackerPanel(this, itemManager, config);
@@ -401,44 +514,12 @@ public class LootTrackerPlugin extends Plugin
if (accountSession != null)
{
lootTrackerClient.setUuid(accountSession.getUuid());
}
clientThread.invokeLater(() ->
{
switch (client.getGameState())
{
case STARTING:
case UNKNOWN:
return false;
}
executor.submit(() ->
{
if (!config.syncPanel())
{
return;
}
Collection<LootAggregate> lootRecords;
try
{
lootRecords = lootTrackerClient.get();
}
catch (IOException e)
{
log.debug("Unable to look up loot", e);
return;
}
log.debug("Loaded {} data entries", lootRecords.size());
clientThread.invokeLater(() ->
{
Collection<LootTrackerRecord> records = convertToLootTrackerRecord(lootRecords);
SwingUtilities.invokeLater(() -> panel.addRecords(records));
});
});
return true;
});
String profileKey = configManager.getRSProfileKey();
if (profileKey != null)
{
switchProfile(profileKey);
}
}
@@ -475,13 +556,10 @@ public class LootTrackerPlugin extends Plugin
final LootTrackerItem[] entries = buildEntries(stack(items));
SwingUtilities.invokeLater(() -> panel.add(name, type, combatLevel, entries));
if (config.saveLoot())
LootRecord lootRecord = new LootRecord(name, type, metadata, toGameItems(items), Instant.now(), getLootWorldId());
synchronized (queuedLoots)
{
LootRecord lootRecord = new LootRecord(name, type, metadata, toGameItems(items), Instant.now(), getLootWorldId());
synchronized (queuedLoots)
{
queuedLoots.add(lootRecord);
}
queuedLoots.add(lootRecord);
}
eventBus.post(new LootReceived(name, combatLevel, type, items));
@@ -932,16 +1010,53 @@ public class LootTrackerPlugin extends Plugin
queuedLoots.clear();
}
if (!config.saveLoot())
{
return null;
}
saveLoot(copy);
log.debug("Submitting {} loot records", copy.size());
return lootTrackerClient.submit(copy);
}
private Collection<ConfigLoot> combine(List<LootRecord> records)
{
Map<ConfigLoot, ConfigLoot> map = new HashMap<>();
for (LootRecord record : records)
{
ConfigLoot key = new ConfigLoot(record.getType(), record.getEventId());
ConfigLoot loot = map.computeIfAbsent(key, k -> key);
loot.kills++;
for (GameItem item : record.getDrops())
{
loot.add(item.getId(), item.getQty());
}
}
return map.values();
}
private void saveLoot(List<LootRecord> records)
{
Instant now = Instant.now();
Collection<ConfigLoot> combinedRecords = combine(records);
for (ConfigLoot record : combinedRecords)
{
ConfigLoot lootConfig = getLootConfig(record.type, record.name);
if (lootConfig == null)
{
lootConfig = record;
}
else
{
lootConfig.kills += record.kills;
for (int i = 0; i < record.drops.length; i += 2)
{
lootConfig.add(record.drops[i], record.drops[i + 1]);
}
}
lootConfig.last = now;
setLootConfig(lootConfig.type, lootConfig.name, lootConfig);
}
}
private void setEvent(LootRecordType lootRecordType, String eventType, Object metadata)
{
this.lootRecordType = lootRecordType;
@@ -1113,6 +1228,18 @@ public class LootTrackerPlugin extends Plugin
.collect(Collectors.toCollection(ArrayList::new));
}
private LootTrackerRecord convertToLootTrackerRecord(final ConfigLoot configLoot)
{
LootTrackerItem[] items = new LootTrackerItem[configLoot.drops.length / 2];
for (int i = 0; i < configLoot.drops.length; i += 2)
{
int id = configLoot.drops[i];
int qty = configLoot.drops[i + 1];
items[i >> 1] = buildLootTrackerItem(id, qty);
}
return new LootTrackerRecord(configLoot.name, "", configLoot.type, items, configLoot.kills);
}
/**
* Is player currently within the provided map regions
*/
@@ -1131,26 +1258,18 @@ public class LootTrackerPlugin extends Plugin
return false;
}
private long getTotalPrice(Collection<ItemStack> items)
{
long totalPrice = 0;
for (final ItemStack itemStack : items)
{
totalPrice += (long) itemManager.getItemPrice(itemStack.getId()) * itemStack.getQuantity();
}
return totalPrice;
}
private void lootReceivedChatMessage(final Collection<ItemStack> items, final String name)
{
long totalPrice = items.stream()
.mapToLong(is -> (long) itemManager.getItemPrice(is.getId()) * is.getQuantity())
.sum();
final String message = new ChatMessageBuilder()
.append(ChatColorType.HIGHLIGHT)
.append("You've killed ")
.append(name)
.append(" for ")
.append(QuantityFormatter.quantityToStackSize(getTotalPrice(items)))
.append(QuantityFormatter.quantityToStackSize(totalPrice))
.append(" loot.")
.build();
@@ -1160,4 +1279,62 @@ public class LootTrackerPlugin extends Plugin
.runeLiteFormattedMessage(message)
.build());
}
ConfigLoot getLootConfig(LootRecordType type, String name)
{
String profile = profileKey;
if (Strings.isNullOrEmpty(profile))
{
log.debug("Trying to get loot with no profile!");
return null;
}
String json = configManager.getConfiguration(LootTrackerConfig.GROUP, profile, "drops_" + type + "_" + name);
if (json == null)
{
return null;
}
return gson.fromJson(json, ConfigLoot.class);
}
void setLootConfig(LootRecordType type, String name, ConfigLoot loot)
{
String profile = profileKey;
if (Strings.isNullOrEmpty(profile))
{
log.debug("Trying to set loot with no profile!");
return;
}
String json = gson.toJson(loot);
configManager.setConfiguration(LootTrackerConfig.GROUP, profile, "drops_" + type + "_" + name, json);
}
void removeLootConfig(LootRecordType type, String name)
{
String profile = profileKey;
if (Strings.isNullOrEmpty(profile))
{
log.debug("Trying to remove loot with no profile!");
return;
}
configManager.unsetConfiguration(LootTrackerConfig.GROUP, profile, "drops_" + type + "_" + name);
}
void removeAllLoot()
{
String profile = profileKey;
if (Strings.isNullOrEmpty(profile))
{
log.debug("Trying to clear loot with no profile!");
return;
}
for (String key : configManager.getRSProfileConfigurationKeys(LootTrackerConfig.GROUP, profile, "drops_"))
{
configManager.unsetConfiguration(LootTrackerConfig.GROUP, profile, key);
}
}
}

View File

@@ -203,7 +203,7 @@ public class SlayerPlugin extends Plugin
private int cachedXp = -1;
private Instant infoTimer;
private boolean loginFlag;
private final List<String> targetNames = new ArrayList<>();
private final List<Pattern> targetNames = new ArrayList<>();
public final Function<NPC, HighlightedNpc> isTarget = (n) ->
{
@@ -664,7 +664,8 @@ public class SlayerPlugin extends Plugin
SlayerUnlock.GROTESQUE_GUARDIAN_DOUBLE_COUNT.isEnabled(client);
}
private boolean isTarget(NPC npc)
@VisibleForTesting
boolean isTarget(NPC npc)
{
if (targetNames.isEmpty())
{
@@ -681,16 +682,15 @@ public class SlayerPlugin extends Plugin
.replace('\u00A0', ' ')
.toLowerCase();
for (String target : targetNames)
for (Pattern target : targetNames)
{
if (name.contains(target))
{
if (ArrayUtils.contains(composition.getActions(), "Attack")
final Matcher targetMatcher = target.matcher(name);
if (targetMatcher.find()
&& (ArrayUtils.contains(composition.getActions(), "Attack")
// Pick action is for zygomite-fungi
|| ArrayUtils.contains(composition.getActions(), "Pick"))
{
return true;
}
|| ArrayUtils.contains(composition.getActions(), "Pick")))
{
return true;
}
}
return false;
@@ -703,13 +703,18 @@ public class SlayerPlugin extends Plugin
if (task != null)
{
Arrays.stream(task.getTargetNames())
.map(String::toLowerCase)
.map(SlayerPlugin::targetNamePattern)
.forEach(targetNames::add);
targetNames.add(taskName.toLowerCase().replaceAll("s$", ""));
targetNames.add(targetNamePattern(taskName.replaceAll("s$", "")));
}
}
private static Pattern targetNamePattern(final String targetName)
{
return Pattern.compile("(?:\\s|^)" + targetName + "(?:\\s|$)", Pattern.CASE_INSENSITIVE);
}
private void rebuildTargetList()
{
targets.clear();
@@ -723,7 +728,8 @@ public class SlayerPlugin extends Plugin
}
}
private void setTask(String name, int amt, int initAmt)
@VisibleForTesting
void setTask(String name, int amt, int initAmt)
{
setTask(name, amt, initAmt, null);
}

View File

@@ -993,6 +993,13 @@ public class ClientUI
int width = panel.getWrappedPanel().getPreferredSize().width;
int expandBy = pluginPanel != null ? pluginPanel.getWrappedPanel().getPreferredSize().width - width : width;
// Deactivate previously active panel
if (pluginPanel != null)
{
pluginPanel.onDeactivate();
}
pluginPanel = panel;
// Expand sidebar

View File

@@ -25,7 +25,6 @@
#version 330
uniform sampler2DArray textures;
uniform vec2 textureOffsets[128];
uniform float brightness;
uniform float smoothBanding;
uniform vec4 fogColor;
@@ -49,9 +48,7 @@ void main() {
if (textureId > 0) {
int textureIdx = textureId - 1;
vec2 animatedUv = fUv + textureOffsets[textureIdx];
vec4 textureColor = texture(textures, vec3(animatedUv, float(textureIdx)));
vec4 textureColor = texture(textures, vec3(fUv, float(textureIdx)));
vec4 textureColorBrightness = pow(textureColor, vec4(brightness, brightness, brightness, 1.0f));
// textured triangles hsl is a 7 bit lightness 2-126

View File

@@ -27,6 +27,10 @@
#define TILE_SIZE 128
// smallest unit of the texture which can be moved per tick. textures are all
// 128x128px - so this is equivalent to +1px
#define TEXTURE_ANIM_UNIT (1.0f / 128.0f)
#define FOG_SCENE_EDGE_MIN TILE_SIZE
#define FOG_SCENE_EDGE_MAX (103 * TILE_SIZE)
#define FOG_CORNER_ROUNDING 1.5
@@ -52,6 +56,8 @@ uniform int useFog;
uniform int fogDepth;
uniform int drawDistance;
uniform mat4 projectionMatrix;
uniform vec2 textureAnimations[128];
uniform int tick;
out vec4 Color;
noperspective centroid out float fHsl;
@@ -77,8 +83,17 @@ void main()
gl_Position = projectionMatrix * vec4(vertex, 1.f);
Color = vec4(rgb, 1.f - a);
fHsl = float(hsl);
textureId = int(uv.x);
fUv = uv.yz;
int textureIdx = int(uv.x); // the texture id + 1
vec2 textureUv = uv.yz;
vec2 textureAnim = vec2(0);
if (textureIdx > 0) {
textureAnim = textureAnimations[textureIdx - 1];
}
textureId = textureIdx;
fUv = textureUv + tick * textureAnim * TEXTURE_ANIM_UNIT;
int fogWest = max(FOG_SCENE_EDGE_MIN, cameraX - drawDistance);
int fogEast = min(FOG_SCENE_EDGE_MAX, cameraX + drawDistance - TILE_SIZE);

Binary file not shown.

After

Width:  |  Height:  |  Size: 420 B

View File

@@ -0,0 +1,98 @@
/*
* Copyright (c) 2022, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.client.game;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.testing.fieldbinder.Bind;
import com.google.inject.testing.fieldbinder.BoundFieldModule;
import java.util.concurrent.ScheduledExecutorService;
import javax.inject.Named;
import net.runelite.api.Client;
import net.runelite.api.ItemID;
import net.runelite.client.callback.ClientThread;
import net.runelite.client.config.RuneLiteConfig;
import net.runelite.http.api.item.ItemPrice;
import static org.junit.Assert.assertEquals;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class ItemManagerTest
{
@Inject
private ItemManager itemManager;
@Mock
@Bind
private Client client;
@Mock
@Bind
private ScheduledExecutorService scheduledExecutorService;
@Mock
@Bind
private ClientThread clientThread;
@Mock
@Bind
private ItemClient itemClient;
@Mock
@Bind
private RuneLiteConfig runeLiteConfig;
@Bind
@Named("activePriceThreshold")
private double activePriceThreshold = 5;
@Bind
@Named("lowPriceThreshold")
private int lowPriceThreshold = 1000;
@Before
public void before()
{
Guice.createInjector(BoundFieldModule.of(this)).injectMembers(this);
}
@Test
public void testGetWikiPrice()
{
ItemPrice itemPrice = new ItemPrice();
itemPrice.setId(ItemID.YEW_SEED);
itemPrice.setName("Yew seed");
itemPrice.setPrice(47_975);
itemPrice.setWikiPrice(50_754);
assertEquals(itemPrice.getWikiPrice(), itemManager.getWikiPrice(itemPrice));
itemPrice.setWikiPrice(300_000); // outside of 5x range
assertEquals(itemPrice.getPrice(), itemManager.getWikiPrice(itemPrice));
}
}

View File

@@ -55,6 +55,7 @@ import net.runelite.api.widgets.WidgetID;
import net.runelite.client.account.SessionManager;
import net.runelite.client.chat.ChatMessageManager;
import net.runelite.client.chat.QueuedMessage;
import net.runelite.client.config.ConfigManager;
import net.runelite.client.game.ItemManager;
import net.runelite.client.game.ItemStack;
import net.runelite.client.game.SpriteManager;
@@ -139,6 +140,10 @@ public class LootTrackerPluginTest
@Bind
private LootTrackerClient lootTrackerClient;
@Mock
@Bind
private ConfigManager configManager;
@Before
public void setUp()
{

View File

@@ -60,6 +60,8 @@ import net.runelite.client.game.npcoverlay.NpcOverlayService;
import net.runelite.client.ui.overlay.OverlayManager;
import net.runelite.client.ui.overlay.infobox.InfoBoxManager;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -926,4 +928,30 @@ public class SlayerPluginTest
assertEquals("Suqahs", slayerPlugin.getTaskName());
assertEquals(229, slayerPlugin.getAmount()); // 2 kills
}
@Test
public void npcMatching()
{
assertTrue(matches("Abyssal demon", Task.ABYSSAL_DEMONS));
assertTrue(matches("Baby blue dragon", Task.BLUE_DRAGONS));
assertTrue(matches("Duck", Task.BIRDS));
assertTrue(matches("Donny the Lad", Task.BANDITS));
assertFalse(matches("Rat", Task.PIRATES));
assertFalse(matches("Wolf", Task.WEREWOLVES));
assertFalse(matches("Scorpia's offspring", Task.SCORPIA));
assertFalse(matches("Jonny the beard", Task.BEARS));
}
private boolean matches(final String npcName, final Task task)
{
final NPC npc = mock(NPC.class);
final NPCComposition comp = mock(NPCComposition.class);
when(npc.getTransformedComposition()).thenReturn(comp);
when(comp.getName()).thenReturn(npcName);
when(comp.getActions()).thenReturn(new String[] { "Attack" });
slayerPlugin.setTask(task.getName(), 0, 0);
return slayerPlugin.isTarget(npc);
}
}

View File

@@ -1160,6 +1160,10 @@ public interface RSClient extends RSGameEngine, Client
@Override
void setSelectedItemSlot(int index);
@Import("selectedItemSlot")
@Override
int getSelectedItemIndex();
@Import("selectedItemWidget")
@Override
int getSelectedItemWidget();
@@ -1308,6 +1312,10 @@ public interface RSClient extends RSGameEngine, Client
@Import("isItemSelected")
int isItemSelected();
@Override
@Import("isItemSelected")
int getSelectedItem();
@Override
@Import("selectedItemName")
String getSelectedItemName();