diff --git a/cache/src/main/java/net/runelite/cache/definitions/TextureDefinition.java b/cache/src/main/java/net/runelite/cache/definitions/TextureDefinition.java index d34bc5678a..820d73b6bd 100644 --- a/cache/src/main/java/net/runelite/cache/definitions/TextureDefinition.java +++ b/cache/src/main/java/net/runelite/cache/definitions/TextureDefinition.java @@ -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; diff --git a/cache/src/main/java/net/runelite/cache/definitions/loaders/TextureLoader.java b/cache/src/main/java/net/runelite/cache/definitions/loaders/TextureLoader.java index 3e9ff09e89..626f620d0d 100644 --- a/cache/src/main/java/net/runelite/cache/definitions/loaders/TextureLoader.java +++ b/cache/src/main/java/net/runelite/cache/definitions/loaders/TextureLoader.java @@ -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; } diff --git a/cache/src/main/java/net/runelite/cache/script/assembler/ScriptWriter.java b/cache/src/main/java/net/runelite/cache/script/assembler/ScriptWriter.java index 60ebfb3369..739769c21c 100644 --- a/cache/src/main/java/net/runelite/cache/script/assembler/ScriptWriter.java +++ b/cache/src/main/java/net/runelite/cache/script/assembler/ScriptWriter.java @@ -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 map = maps[index++] = new HashMap<>(); + Map map = maps[index++] = new LinkedHashMap<>(); for (LookupCase scase : lswitch.getCases()) { diff --git a/runelite-api/src/main/java/net/runelite/api/Client.java b/runelite-api/src/main/java/net/runelite/api/Client.java index 3415a670cf..64a42738c4 100644 --- a/runelite-api/src/main/java/net/runelite/api/Client.java +++ b/runelite-api/src/main/java/net/runelite/api/Client.java @@ -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. * diff --git a/runelite-api/src/main/java/net/runelite/api/ItemComposition.java b/runelite-api/src/main/java/net/runelite/api/ItemComposition.java index 235e3dbef8..ff4c4906df 100644 --- a/runelite-api/src/main/java/net/runelite/api/ItemComposition.java +++ b/runelite-api/src/main/java/net/runelite/api/ItemComposition.java @@ -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); } diff --git a/runelite-client/src/main/java/net/runelite/client/RuneLite.java b/runelite-client/src/main/java/net/runelite/client/RuneLite.java index 46a0e5f2db..e4c7ce7869 100644 --- a/runelite-client/src/main/java/net/runelite/client/RuneLite.java +++ b/runelite-client/src/main/java/net/runelite/client/RuneLite.java @@ -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(); diff --git a/runelite-client/src/main/java/net/runelite/client/config/ConfigManager.java b/runelite-client/src/main/java/net/runelite/client/config/ConfigManager.java index f42ef80efa..519ab8329d 100644 --- a/runelite-client/src/main/java/net/runelite/client/config/ConfigManager.java +++ b/runelite-client/src/main/java/net/runelite/client/config/ConfigManager.java @@ -534,6 +534,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); } diff --git a/runelite-client/src/main/java/net/runelite/client/game/ItemManager.java b/runelite-client/src/main/java/net/runelite/client/game/ItemManager.java index 07f31068be..3a6f9b1711 100644 --- a/runelite-client/src/main/java/net/runelite/client/game/ItemManager.java +++ b/runelite-client/src/main/java/net/runelite/client/game/ItemManager.java @@ -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; } /** diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/PluginClassLoader.java b/runelite-client/src/main/java/net/runelite/client/plugins/PluginClassLoader.java new file mode 100644 index 0000000000..3be3a4ed91 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/PluginClassLoader.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2016-2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.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); + } + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/PluginManager.java b/runelite-client/src/main/java/net/runelite/client/plugins/PluginManager.java index e1be2f6144..398ca97eee 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/PluginManager.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/PluginManager.java @@ -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> 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 loadPlugins(List> plugins, BiConsumer onPluginLoaded) throws PluginInstantiationException { MutableGraph> graph = GraphBuilder diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/CrypticClue.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/CrypticClue.java index 19bbf55731..785ffce336 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/CrypticClue.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/CrypticClue.java @@ -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."), diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GpuPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GpuPlugin.java index 96c992f2da..23e2f4fe46 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GpuPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GpuPlugin.java @@ -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,7 @@ 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); + gl.glUniform1i(uniTick, client.getGameCycle()); // Calculate projection matrix Matrix4 projectionMatrix = new Matrix4(); @@ -1295,24 +1304,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 +1512,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 diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/gpu/TextureManager.java b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/TextureManager.java index 351eba2823..994fc64d26 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/gpu/TextureManager.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/TextureManager.java @@ -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; } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/idlenotifier/IdleNotifierPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/idlenotifier/IdleNotifierPlugin.java index 987ecedabb..5cae67799b 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/idlenotifier/IdleNotifierPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/idlenotifier/IdleNotifierPlugin.java @@ -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 { diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerBox.java b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerBox.java index 16da3c9460..97dd4504df 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerBox.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerBox.java @@ -316,6 +316,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 +351,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"); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPanel.java index 654d7db9b1..bde380c210 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPanel.java @@ -95,17 +95,18 @@ class LootTrackerPanel extends PluginPanel // When there is no loot, display this private final PluginErrorPanel errorPanel = new PluginErrorPanel(); + // When there is loot, display this. This contains the actions, overall, and log panel. + private final JPanel layoutPanel = new JPanel(); + // Handle loot boxes private final JPanel logsContainer = new JPanel(); // Handle overall session data - private final JPanel overallPanel = new JPanel(); 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 JLabel detailsTitle = new JLabel(); private final JButton backBtn = new JButton(); private final JToggleButton viewHiddenBtn = new JToggleButton(); @@ -171,15 +172,36 @@ class LootTrackerPanel extends PluginPanel setLayout(new BorderLayout()); // Create layout panel for wrapping - final JPanel layoutPanel = new JPanel(); layoutPanel.setLayout(new BoxLayout(layoutPanel, BoxLayout.Y_AXIS)); + layoutPanel.setVisible(false); add(layoutPanel, BorderLayout.NORTH); + final JPanel actionsPanel = buildActionsPanel(); + final JPanel 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)); actionsContainer.setBorder(new EmptyBorder(5, 5, 5, 10)); - actionsContainer.setVisible(false); final JPanel viewControls = new JPanel(new GridLayout(1, 3, 10, 0)); viewControls.setBackground(ColorScheme.DARKER_GRAY_COLOR); @@ -252,14 +274,19 @@ 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) )); overallPanel.setBackground(ColorScheme.DARKER_GRAY_COLOR); overallPanel.setLayout(new BorderLayout()); - overallPanel.setVisible(false); // Add icon and contents final JPanel overallInfo = new JPanel(); @@ -310,15 +337,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() @@ -511,8 +530,7 @@ class LootTrackerPanel extends PluginPanel // Show main view remove(errorPanel); - actionsContainer.setVisible(true); - overallPanel.setVisible(true); + layoutPanel.setVisible(true); // Create box final LootTrackerBox box = new LootTrackerBox(itemManager, record.getTitle(), record.getType(), record.getSubTitle(), @@ -555,7 +573,7 @@ class LootTrackerPanel extends PluginPanel { final LootTrackerClient client = plugin.getLootTrackerClient(); final boolean syncLoot = client.getUuid() != null && config.syncPanel(); - final int result = JOptionPane.showOptionDialog(overallPanel, + final int result = JOptionPane.showOptionDialog(box, syncLoot ? SYNC_RESET_ALL_WARNING_TEXT : NO_SYNC_RESET_ALL_WARNING_TEXT, "Are you sure?", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE, null, new String[]{"Yes", "No"}, "No"); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPlugin.java index b8c9f1a7b2..9c43c44545 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPlugin.java @@ -1131,26 +1131,18 @@ public class LootTrackerPlugin extends Plugin return false; } - private long getTotalPrice(Collection items) - { - long totalPrice = 0; - - for (final ItemStack itemStack : items) - { - totalPrice += (long) itemManager.getItemPrice(itemStack.getId()) * itemStack.getQuantity(); - } - - return totalPrice; - } - private void lootReceivedChatMessage(final Collection 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(); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/slayer/SlayerPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/slayer/SlayerPlugin.java index b535ec643a..6c7b95ebd9 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/slayer/SlayerPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/slayer/SlayerPlugin.java @@ -203,7 +203,7 @@ public class SlayerPlugin extends Plugin private int cachedXp = -1; private Instant infoTimer; private boolean loginFlag; - private final List targetNames = new ArrayList<>(); + private final List targetNames = new ArrayList<>(); public final Function 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); } diff --git a/runelite-client/src/main/java/net/runelite/client/ui/ClientUI.java b/runelite-client/src/main/java/net/runelite/client/ui/ClientUI.java index 0f1964da7a..4df9504c89 100644 --- a/runelite-client/src/main/java/net/runelite/client/ui/ClientUI.java +++ b/runelite-client/src/main/java/net/runelite/client/ui/ClientUI.java @@ -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 diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/frag.glsl b/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/frag.glsl index d028c4ce40..8c4b53dec1 100644 --- a/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/frag.glsl +++ b/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/frag.glsl @@ -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 diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/vert.glsl b/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/vert.glsl index 0b161a1149..6e19adfdb9 100644 --- a/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/vert.glsl +++ b/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/vert.glsl @@ -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); diff --git a/runelite-client/src/test/java/net/runelite/client/game/ItemManagerTest.java b/runelite-client/src/test/java/net/runelite/client/game/ItemManagerTest.java new file mode 100644 index 0000000000..dfcc12242b --- /dev/null +++ b/runelite-client/src/test/java/net/runelite/client/game/ItemManagerTest.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2022, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.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)); + } +} \ No newline at end of file diff --git a/runelite-client/src/test/java/net/runelite/client/plugins/slayer/SlayerPluginTest.java b/runelite-client/src/test/java/net/runelite/client/plugins/slayer/SlayerPluginTest.java index 3fc570c233..6ce2d86783 100644 --- a/runelite-client/src/test/java/net/runelite/client/plugins/slayer/SlayerPluginTest.java +++ b/runelite-client/src/test/java/net/runelite/client/plugins/slayer/SlayerPluginTest.java @@ -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); + } }