From 9a93250c9ca2ee5af53341f90b24274aabda6bce Mon Sep 17 00:00:00 2001 From: Max Weber Date: Sat, 5 Jan 2019 20:02:18 -0700 Subject: [PATCH 1/2] Add BeforeRender event --- .../net/runelite/api/events/BeforeRender.java | 32 +++++++++++++++++++ .../net/runelite/client/callback/Hooks.java | 4 +++ 2 files changed, 36 insertions(+) create mode 100644 runelite-api/src/main/java/net/runelite/api/events/BeforeRender.java diff --git a/runelite-api/src/main/java/net/runelite/api/events/BeforeRender.java b/runelite-api/src/main/java/net/runelite/api/events/BeforeRender.java new file mode 100644 index 0000000000..741041e1e0 --- /dev/null +++ b/runelite-api/src/main/java/net/runelite/api/events/BeforeRender.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2018 Abex + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.api.events; + +/** + * Posted at the start of every frame + */ +public class BeforeRender +{ +} diff --git a/runelite-client/src/main/java/net/runelite/client/callback/Hooks.java b/runelite-client/src/main/java/net/runelite/client/callback/Hooks.java index 77f4de94bb..4d9ff2b381 100644 --- a/runelite-client/src/main/java/net/runelite/client/callback/Hooks.java +++ b/runelite-client/src/main/java/net/runelite/client/callback/Hooks.java @@ -46,6 +46,7 @@ import net.runelite.api.MainBufferProvider; import net.runelite.api.RenderOverview; import net.runelite.api.Renderable; import net.runelite.api.WorldMapManager; +import net.runelite.api.events.BeforeRender; import net.runelite.api.events.GameTick; import net.runelite.api.hooks.Callbacks; import net.runelite.api.hooks.DrawCallbacks; @@ -81,6 +82,7 @@ public class Hooks implements Callbacks private static final OverlayRenderer renderer = injector.getInstance(OverlayRenderer.class); private static final GameTick GAME_TICK = new GameTick(); + private static final BeforeRender BEFORE_RENDER = new BeforeRender(); @Inject private EventBus eventBus; @@ -149,6 +151,8 @@ public class Hooks implements Callbacks client.setTickCount(tick + 1); } + eventBus.post(BEFORE_RENDER); + clientThread.invoke(); long now = System.currentTimeMillis(); From 9098d1fa94cacfe697fef1e0c9da7369cd6e3627 Mon Sep 17 00:00:00 2001 From: Max Weber Date: Sat, 5 Jan 2019 20:03:05 -0700 Subject: [PATCH 2/2] Add Skybox plugin --- .../main/java/net/runelite/api/Client.java | 20 + .../client/plugins/gpu/GpuPlugin.java | 2 + .../client/plugins/skybox/Skybox.java | 510 ++++++++++ .../client/plugins/skybox/SkyboxPlugin.java | 128 +++ .../runelite/client/plugins/skybox/skybox.txt | 925 ++++++++++++++++++ .../client/plugins/skybox/SkyboxTest.java | 68 ++ .../net/runelite/mixins/RSClientMixin.java | 17 + .../net/runelite/mixins/RSSceneMixin.java | 20 +- .../java/net/runelite/rs/api/RSClient.java | 11 + 9 files changed, 1700 insertions(+), 1 deletion(-) create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/skybox/Skybox.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/skybox/SkyboxPlugin.java create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/skybox/skybox.txt create mode 100644 runelite-client/src/test/java/net/runelite/client/plugins/skybox/SkyboxTest.java 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 1bfbadb6ee..421d7041b0 100644 --- a/runelite-api/src/main/java/net/runelite/api/Client.java +++ b/runelite-api/src/main/java/net/runelite/api/Client.java @@ -1513,6 +1513,16 @@ public interface Client extends GameEngine */ void setOculusOrbNormalSpeed(int speed); + /** + * Gets local X coord where the camera is pointing when the Oculus orb is active + */ + int getOculusOrbFocalPointX(); + + /** + * Gets local Y coord where the camera is pointing when the Oculus orb is active + */ + int getOculusOrbFocalPointY(); + /** * Opens in-game world hopper interface */ @@ -1524,6 +1534,16 @@ public interface Client extends GameEngine */ void hopToWorld(World world); + /** + * Sets the RGB color of the skybox + */ + void setSkyboxColor(int skyboxColor); + + /** + * Gets the RGB color of the skybox + */ + int getSkyboxColor(); + boolean isGpu(); void setGpu(boolean gpu); 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 998cee57b8..68209ba82d 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 @@ -849,6 +849,8 @@ public class GpuPlugin extends Plugin implements DrawCallbacks lastAntiAliasingMode = antiAliasingMode; // Clear scene + int sky = client.getSkyboxColor(); + gl.glClearColor((sky >> 16 & 0xFF) / 255f, (sky >> 8 & 0xFF) / 255f, (sky & 0xFF) / 255f, 1f); gl.glClear(gl.GL_COLOR_BUFFER_BIT); // Upload buffers diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/skybox/Skybox.java b/runelite-client/src/main/java/net/runelite/client/plugins/skybox/Skybox.java new file mode 100644 index 0000000000..4e8df9c76e --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/skybox/Skybox.java @@ -0,0 +1,510 @@ +/* + * Copyright (c) 2019 Abex + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.skybox; + +import java.awt.image.BufferedImage; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.util.Arrays; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import net.runelite.api.Client; + +class Skybox +{ + @FunctionalInterface + public interface ChunkMapper + { + /** + * Gets the instance template chunk data for the specified point + * + * @see Client#getInstanceTemplateChunks + */ + int getTemplateChunk(int cx, int cy, int plane); + } + + private static final double SQRT2 = Math.sqrt(2); + + // How many stddev per direction we need to stay visibly continuous + // 511/512 accuracy + private static final double BLEND_DISTRIBUTION = 3.075; + + // This has a worst case complexity of O((BLEND_RADUS*2)^2) + // BLEND_RADIUS is in chunks (8 tiles) + private static final int BLEND_RADIUS = 5; + + // The maximum number of tiles that can be blended before becoming visibly discontinuous + private static final int MAX_BLEND = (int) ((BLEND_RADIUS * 8) / BLEND_DISTRIBUTION); + + private static final int PLANE_ALL = 0b1111; + + private static final Pattern PATTERN = Pattern.compile("^[ \\t]*(?" + + "//.*$|" + // //comment + "m[ \\t]*(?[0-9]+)[ \\t]+(?[0-9]+)|" + // m + "r[ \\t]*(?[0-9]+)[ \\t]+(?[0-9]+)|" + // r + "R[ \\t]*(?[0-9]+)[ \\t]+(?[0-9]+)[ \\t]+(?[0-9]+)[ \\t]+(?[0-9]+)|" + // R + "c[ \\t]*(?[0-9-]+)[ \\t]+(?[0-9-]+)|" + // c + "C[ \\t]*(?[0-9-]+)[ \\t]+(?[0-9-]+)[ \\t]+(?[0-9-]+)[ \\t]+(?[0-9-]+)|" + // C + "#[ \\t]*(?[0-9a-fA-F]{6}|[0-9a-fA-F]{3})|" + // # or # + "p[ \\t]*(?all|0?[ \\t]*1?[ \\t]*2?[ \\t]*3?)|" + // p all or p<1><2><3><4> + "b[ \\t]*(?[0-9]+)|" + // b + "bounds[ \\t]+(?[0-9]+)[ \\t]+(?[0-9]+)[ \\t]+(?[0-9]+)[ \\t]+(?[0-9]+)" + // bounds + ")[ \\t]*"); + + private final int[] chunks; + private final int[] planeOverrides; + + private final int x1; + private final int y1; + private final int x2; + private final int y2; + private final int stride; + + public Skybox(InputStream is, String filename) throws IOException + { + this(new InputStreamReader(is), filename); + } + + public Skybox(Reader reader, String filename) throws IOException + { + int[] chunks = null; + int[] planeOverrides = new int[64]; + int planeOverrideEnd = 0; + int x1 = 0, y1 = 0, x2 = 0, y2 = 0, stride = 0; + BufferedReader br = new BufferedReader(reader); + int lineNo = 1; + int color = 0; + int plane = PLANE_ALL; + int rx1 = 0, ry1 = 0, rx2 = 0, ry2 = 0; + try + { + Matcher m = PATTERN.matcher(""); + for (String line; (line = br.readLine()) != null; lineNo++) + { + m.reset(line); + int end = 0; + for (; end < line.length(); ) + { + m.region(end, line.length()); + if (!m.find()) + { + throw new IllegalArgumentException("Unexpected: \"" + line.substring(end) + "\" (" + filename + ":" + lineNo + ")"); + } + end = m.end(); + + String expr = m.group("expr"); + if (expr == null || expr.length() <= 0 || expr.startsWith("//")) + { + continue; + } + + if (chunks == null) + { + if (!expr.startsWith("bounds")) + { + throw new IllegalArgumentException("Expceted bounds (" + filename + ":" + lineNo + ")"); + } + x1 = Integer.parseInt(m.group("bx1")) * 8; + y1 = Integer.parseInt(m.group("by1")) * 8; + x2 = (Integer.parseInt(m.group("bx2")) + 1) * 8; + y2 = (Integer.parseInt(m.group("by2")) + 1) * 8; + stride = (x2 - x1); + chunks = new int[stride * (y2 - y1)]; + Arrays.fill(chunks, -1); + continue; + } + + char cha = expr.charAt(0); + switch (cha) + { + case '#': + String sColor = m.group("color"); + int scolor = Integer.parseInt(sColor, 16); + int cr, cg, cb; + if (sColor.length() == 3) + { + // Expand #RGB to #RRGGBB + cr = scolor >> 8 & 0xF; + cr |= cr << 4; + cg = scolor >> 4 & 0xF; + cg |= cg << 4; + cb = scolor & 0xF; + cb |= cb << 4; + } + else + { + cr = scolor >> 16 & 0xFF; + cg = scolor >> 8 & 0xFF; + cb = scolor & 0xFF; + } + + // Convert to YCoCg24 because it produces less blending artifacts due + // to mismatched skew rates + // See: https://stackoverflow.com/questions/10566668/lossless-rgb-to-ycbcr-transformation + byte cco = (byte) (cb - cr); + byte tmp = (byte) (cr + (cco >> 1)); + byte ccg = (byte) (tmp - cg); + byte cy = (byte) (cg + (ccg >> 1)); + + color = color & 0xFF000000 | (cy & 0xFF) << 16 | (cco & 0xFF) << 8 | (ccg & 0xFF); + break; + case 'b': + int iblend = Integer.parseInt(m.group("blend")); + if (iblend < 0) + { + throw new IllegalArgumentException("Blend must be >=0 (" + filename + ":" + lineNo + ")"); + } + if (iblend > MAX_BLEND) + { + throw new IllegalArgumentException("Blend must be <= " + MAX_BLEND + " (" + filename + ":" + lineNo + ")"); + } + color = color & 0x00FFFFFF | iblend << 24; + break; + case 'm': + rx2 = rx1 = Integer.parseInt(m.group("mrx")); + ry2 = ry1 = Integer.parseInt(m.group("mry")); + break; + case 'p': + String planes = m.group("plane"); + if ("all".equals(planes)) + { + plane = PLANE_ALL; + } + else + { + plane = 0; + for (int i = 0; i < planes.length(); i++) + { + plane |= 1 << (planes.charAt(i) - '0'); + } + } + break; + case 'r': + case 'R': + if (cha == 'r') + { + rx2 = rx1 = Integer.parseInt(m.group("rx")); + ry2 = ry1 = Integer.parseInt(m.group("ry")); + } + else + { + rx1 = Integer.parseInt(m.group("rx1")); + ry1 = Integer.parseInt(m.group("ry1")); + rx2 = Integer.parseInt(m.group("rx2")); + ry2 = Integer.parseInt(m.group("ry2")); + } + // fallthrough + case 'c': + case 'C': + int cx1 = rx1 * 8; + int cy1 = ry1 * 8; + int cx2 = rx2 * 8 + 7; + int cy2 = ry2 * 8 + 7; + if (cha == 'c') + { + cx2 = cx1 = cx1 + Integer.parseInt(m.group("cx")); + cy2 = cy1 = cy1 + Integer.parseInt(m.group("cy")); + } + else if (cha == 'C') + { + cx2 = cx1 + Integer.parseInt(m.group("cx2")); + cy2 = cy1 + Integer.parseInt(m.group("cy2")); + cx1 = cx1 + Integer.parseInt(m.group("cx1")); + cy1 = cy1 + Integer.parseInt(m.group("cy1")); + } + + if (cx1 < x1 || cy1 < y1 || cx2 >= x2 || cy2 >= y2) + { + throw new IllegalArgumentException("Coordinate out of bounds (" + filename + ":" + lineNo + ")"); + } + if (cx1 > cx2 || cy1 > cy2) + { + throw new IllegalArgumentException("First coord must be before second (" + filename + ":" + lineNo + ")"); + } + + for (int y = cy1; y <= cy2; y++) + { + int yoffset = stride * (y - y1); + for (int x = cx1; x <= cx2; x++) + { + int offset = (x - x1) + yoffset; + + if (plane == PLANE_ALL) + { + chunks[offset] = color; + } + else + { + // We are not setting all planes in this chunk, so allocate a plane override section + // and add a pointer to it in the normal chunk's space. We do this because most chunks + // do not have plane-specific data + int ocv = chunks[offset]; + int poptr; + if ((ocv & 0x8000_0000) != 0 && ocv != -1) + { + // Existing plane override + poptr = ocv & 0x7FFF_FFFF; + } + else + { + poptr = planeOverrideEnd; + planeOverrideEnd += 4; + if (planeOverrideEnd > planeOverrides.length) + { + planeOverrides = Arrays.copyOf(planeOverrides, planeOverrideEnd + 64); + } + chunks[offset] = poptr | 0x8000_0000; + for (int i = 0; i < 4; i++) + { + planeOverrides[poptr + i] = ocv; + } + } + + for (int i = 0; i < 4; i++) + { + if ((plane & (1 << i)) != 0) + { + planeOverrides[poptr + i] = color; + } + } + } + } + } + break; + } + } + } + } + catch (NumberFormatException ex) + { + throw new IllegalArgumentException("Expected number (" + filename + ":" + lineNo + ")", ex); + } + if (chunks == null) + { + throw new IllegalArgumentException(filename + ": no data"); + } + + this.chunks = chunks; + this.planeOverrides = planeOverrides; + this.stride = stride; + this.x1 = x1; + this.y1 = y1; + this.x2 = x2; + this.y2 = y2; + } + + private int chunkData(int cx, int cy, int plane, ChunkMapper chunkMapper) + { + if (chunkMapper != null) + { + int itp = chunkMapper.getTemplateChunk(cx, cy, plane); + if (itp == -1) + { + return -1; + } + + cy = itp >> 3 & 0x7FF; + cx = itp >> 14 & 0x3FF; + plane = itp >> 24 & 0x3; + } + + if (cx < x1) + { + cx = x1; + } + if (cx >= x2) + { + cx = x2 - 1; + } + if (cy < y1) + { + cy = y1; + } + if (cy >= y2) + { + cy = y2 - 1; + } + + int cv = chunks[(stride * (cy - y1)) + (cx - x1)]; + if (cv == -1) + { + return -1; + } + if ((cv & 0x8000_0000) != 0) + { + cv = planeOverrides[(cv & 0x7FFF_FFFF) | plane]; + } + return cv; + } + + /** + * Calculates the RGB color for a specific world coordinate. Arguments are floats for sub-tile accuracy. + * + * @param x X coordinate in tiles + * @param y Y coordinate in tiles + * @param chunkMapper maps chunks to their instance templates, or null if not in an instance + */ + public int getColorForPoint(double x, double y, int plane, double brightness, ChunkMapper chunkMapper) + { + x /= 8.d; + y /= 8.d; + + int cx = (int) x; + int cy = (int) y; + + int centerChunkData = chunkData(cx, cy, plane, chunkMapper); + if (centerChunkData == -1) + { + // No data in the center chunk? + return 0; + } + + double t = 0; + double ty = 0; + double tco = 0; + double tcg = 0; + + int xmin = (int) (x - BLEND_RADIUS); + int xmax = (int) Math.ceil(x + BLEND_RADIUS); + int ymin = (int) (y - BLEND_RADIUS); + int ymax = (int) Math.ceil(y + BLEND_RADIUS); + + for (int ucx = xmin; ucx < xmax; ucx++) + { + for (int ucy = ymin; ucy <= ymax; ucy++) + { + int val = chunkData(ucx, ucy, plane, chunkMapper); + if (val == -1) + { + continue; + } + + // Get the blend value, add 1/8 tile to make sure we don't div/0, convert to chunks + double sigma = ((val >>> 24) + .125) / 8.d; + + // Calculate how far we have to be away before we can discard this value without + // becoming visibly discontinuous + double minDist = 1 + (sigma * BLEND_DISTRIBUTION); + + // Try to fast-fail + double dxl = ucx - x; + double dxh = dxl + 1.d; + if (dxl < -minDist || dxl > minDist) + { + continue; + } + + double dyl = ucy - y; + double dyh = dyl + 1.d; + if (dyl < -minDist || dyh > minDist) + { + continue; + } + + // Calculate integrate a gaussian distribution in each dimension for + // this chunk relative to the requested point + double erfdivc = sigma * SQRT2; + double m = (erf(dxl / erfdivc) - erf(dxh / erfdivc)) * (erf(dyl / erfdivc) - erf(dyh / erfdivc)); + + // Load our YCoCg24 values into floats + double vy = (val >>> 16 & 0xFF) / 255.d; + double vco = (byte) (val >>> 8) / 128.d; + double vcg = (byte) val / 128.d; + + // And multiply by the weight + ty += vy * m; + tco += vco * m; + tcg += vcg * m; + t += m; + } + } + + // Convert back to int range values, and bounds check while we are at it + byte ay = (byte) Math.min(Math.max(Math.round(Math.pow(ty / t, brightness) * 255.d), 0), 255); + byte aco = (byte) Math.min(Math.max(Math.round(tco * 128.d / t), -128), 127); + byte acg = (byte) Math.min(Math.max(Math.round(tcg * 128.d / t), -128), 127); + + // convert back to rgb from YCoCg24 + int g = (ay - (acg >> 1)) & 0xFF; + int tmp = (g + acg) & 0xFF; + int r = (tmp - (aco >> 1)) & 0xFF; + int b = (r + aco) & 0xFF; + + return r << 16 | g << 8 | b; + } + + /** + * Approximation of erf 'Gauss error function' which is used to calculate + * the cumulative distribution of a gaussian distribution. + * This is used to simulate a large kernel gaussian blur without having + * to sample the same chunk multiple times. + */ + private double erf(double x) + { + double ax = Math.abs(x); + double t = 1.d / (1.d + (ax * .3275911d)); + double y = 1.d - ((((((1.061405429d * t) - 1.453152027d) * t) + 1.421413741d) * t - 0.284496736d) * t + 0.254829592d) * t * Math.exp(-ax * ax); + return Math.copySign(y, x); + } + + /** + * Draws the skybox map to an image + * + * @param resolution The number of pixels per tile + * @param line How many tiles to put a line + * @param plane the plane (0-4) to render + */ + BufferedImage render(double resolution, int line, int plane, ChunkMapper chunkMapper) + { + int w = (int) (((x2 - x1) * 8) * resolution); + int h = (int) (((y2 - y1) * 8) * resolution); + BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); + int lineEvery = line <= 0 ? Integer.MAX_VALUE : (int) (line * resolution); + + for (int y = 0; y < h; y++) + { + for (int x = 0; x < w; x++) + { + int color; + if (x % lineEvery == 0 || y % lineEvery == 0) + { + color = 0x00FFFFFF; + } + else + { + double fx = (x1 * 8) + (x / resolution); + double fy = (y1 * 8) + (y / resolution); + color = getColorForPoint(fx, fy, plane, .8, chunkMapper); + } + img.setRGB(x, h - 1 - y, color | 0xFF000000); + } + } + + return img; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/skybox/SkyboxPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/skybox/SkyboxPlugin.java new file mode 100644 index 0000000000..19241741d4 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/skybox/SkyboxPlugin.java @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2019 Abex + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.skybox; + +import com.google.inject.Inject; +import java.io.IOException; +import net.runelite.api.Client; +import net.runelite.api.GameState; +import net.runelite.api.Player; +import net.runelite.api.coords.LocalPoint; +import net.runelite.api.events.BeforeRender; +import net.runelite.api.events.GameStateChanged; +import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; + +@PluginDescriptor( + name = "Skybox", + description = "Draws a oldschool styled skybox", + enabledByDefault = false, + tags = {"sky"} +) +public class SkyboxPlugin extends Plugin +{ + @Inject + private Client client; + + private Skybox skybox; + + @Override + public void startUp() throws IOException + { + skybox = new Skybox(SkyboxPlugin.class.getResourceAsStream("skybox.txt"), "skybox.txt"); + } + + @Override + public void shutDown() + { + client.setSkyboxColor(0); + skybox = null; + } + + private int mapChunk(int cx, int cy, int plane) + { + cx -= client.getBaseX() / 8; + cy -= client.getBaseY() / 8; + + int[][] instanceTemplateChunks = client.getInstanceTemplateChunks()[plane]; + // Blending can access this out of bounds, so do a range check + if (cx < 0 || cx >= instanceTemplateChunks.length || cy < 0 || cy >= instanceTemplateChunks[cx].length) + { + return -1; + } + + return instanceTemplateChunks[cx][cy]; + } + + @Subscribe + public void onBeforeRender(BeforeRender r) + { + if (skybox == null || client.getGameState() != GameState.LOGGED_IN) + { + return; + } + + Player player = client.getLocalPlayer(); + if (player == null) + { + return; + } + + int px, py; + if (client.getOculusOrbState() == 1) + { + px = client.getOculusOrbFocalPointX(); + py = client.getOculusOrbFocalPointY(); + } + else + { + LocalPoint p = client.getLocalPlayer().getLocalLocation(); + px = p.getX(); + py = p.getY(); + } + + // Inverse of camera location / 2 + int spx = px - ((client.getCameraX() - px) >> 1); + int spy = py - ((client.getCameraY() - py) >> 1); + + client.setSkyboxColor(skybox.getColorForPoint( + client.getBaseX() + (spx / 128.f), + client.getBaseY() + (spy / 128.f), + client.getPlane(), + client.getTextureProvider().getBrightness(), + client.isInInstancedRegion() ? this::mapChunk : null + )); + } + + @Subscribe + public void onGameStateChanged(GameStateChanged gameStateChanged) + { + if (gameStateChanged.getGameState() == GameState.LOGIN_SCREEN) + { + client.setSkyboxColor(0); + } + } +} diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/skybox/skybox.txt b/runelite-client/src/main/resources/net/runelite/client/plugins/skybox/skybox.txt new file mode 100644 index 0000000000..4a2a443ddb --- /dev/null +++ b/runelite-client/src/main/resources/net/runelite/client/plugins/skybox/skybox.txt @@ -0,0 +1,925 @@ +// Copyright (c) 2019, Abex +// Copyright (c) 2019, Psikoi +// Copyright (c) 2019, Ron Young +// +// 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. + +// Skybox format +// +// Comments are c-style; Starts with // and ends with the next newline +// +// The first expression must be +// bounds rx1 ry1 rx2 ry2 +// which sets the bounds of the image +// +// Color is set with #RRGGBB or #RGB and persists until set again +// Blending is set with b. It persists until set again. The number is in tiles, up to 13 +// +// p <0> <1> <2> <3> +// p all +// Sets a mask for the planes to be altered by the next operation +// +// m +// Moves the cursor to the specified region. This does not change any chunks +// +// The following operations commit chunks to the map: +// +// r +// Sets all of the chunks in a region to the current color and blending +// +// R +// Sets all of the chunks in [x1,x2] [y1,y2] are set to the current color and blending +// +// c +// Sets the chunk relative to the last region/region1 to the current color and blending +// +// C +// Sets the chunks relative to the last region/region1 to the current color and blending + +bounds 18 39 60 162 + +b 8 + +#8AD2DF +R 18 39 60 64 + +#0f0f0f +r 34 75 + +#161616 +R 47 61 53 62 + +#181818 +R 37 79 39 80 + +#1a1a1a +R 47 60 53 60 + +#202020 +R 46 59 53 59 + +#242424 +r 29 81 + +#242e2c +r 56 160 + +#262626 +R 46 58 53 58 +r 24 79 + +#271010 +R 46 74 48 75 +r 47 76 + +#2a2f42 +r 38 75 + +#2b2b2b +R 46 57 53 57 +R 55 62 56 62 +r 45 151 + +#2e2e2e +R 36 147 37 147 + +#323232 +R 46 56 52 56 +R 51 74 52 75 +R 54 75 55 76 +R 58 58 59 58 +r 59 59 + +#332a15 +R 26 73 27 73 +R 27 80 27 81 +R 27 158 28 158 +R 29 84 29 85 +R 30 72 31 72 +R 36 153 36 154 +R 37 150 41 154 +R 39 147 42 149 +R 40 69 40 70 +R 40 81 40 82 +R 41 70 41 71 +R 41 79 42 79 +R 42 142 43 142 +R 42 150 42 152 +R 44 148 44 150 +R 44 152 49 153 +R 45 150 46 150 +R 46 161 47 161 +R 47 151 48 151 +R 48 150 49 150 +R 48 154 50 155 +R 49 77 49 79 +R 52 152 52 153 +r 20 155 +r 22 157 +r 24 154 +r 28 66 +r 30 80 +r 31 81 +r 34 77 +r 34 85 +r 35 73 +r 35 75 +r 35 156 +r 36 79 +r 36 146 +r 36 150 +r 37 73 +r 37 148 +r 38 147 +r 39 145 +r 41 75 +r 41 146 +r 42 154 +r 43 146 +r 43 149 +r 45 79 +r 45 148 +r 45 154 +r 46 149 +r 47 85 +r 47 87 +r 47 160 +r 48 148 +r 50 151 +r 51 147 +r 54 151 +r 54 153 +r 58 88 + +#342b29 +R 50 144 51 144 + +#353535 +R 42 81 42 83 + +#383838 +R 46 55 52 55 + +#393326 +R 51 77 51 79 + +#3c3c3c +r 30 73 +r 39 73 +r 49 81 + +#3e3e3e +r 48 52 + +#3f384a +R 25 156 26 157 + +#40645d +r 49 49 + +#406e8c +R 37 68 38 69 +r 26 154 +r 36 69 + +#413d2d +r 50 145 + +#424242 +r 50 150 + +#424940 +R 29 68 30 68 +R 38 158 39 158 +R 42 69 43 71 +R 45 68 45 69 +R 49 149 50 149 +R 55 151 56 151 +R 58 151 58 152 +r 39 72 +r 39 156 +r 51 150 +r 53 150 +r 53 154 +r 55 154 +r 56 150 + +#474747 +R 50 146 51 146 +r 25 83 +r 27 83 + +#49432a +R 25 152 26 153 +R 51 148 52 149 +r 42 76 +r 46 148 +r 53 149 +r 54 148 + +#496e53 +r 33 82 + +#4a706a +R 18 54 19 54 +R 20 54 21 56 +R 31 78 38 78 +R 34 47 34 48 +R 42 73 43 74 +R 54 50 56 50 +R 54 51 57 55 +R 56 58 56 59 +R 56 141 57 142 +R 57 57 57 58 +R 58 51 58 53 +r 43 72 +r 44 71 +r 53 55 +r 53 77 + +#4a718f +R 25 58 25 59 +r 26 58 + +#4c463a +R 58 146 58 147 +r 59 147 + +#4f4f4f +r 48 81 + +#51649c +r 37 59 +r 39 58 + +#543232 +r 32 76 +r 36 75 +r 36 81 +r 46 81 +r 47 83 + +#553d75 +R 26 60 28 60 + +#57493e +r 35 83 + +#575757 +R 49 157 50 159 +R 52 150 52 151 + +#576f6f +r 56 159 + +#606060 +r 43 75 + +#64554b +R 22 59 22 60 +R 23 58 23 59 +r 24 58 + +#665742 +r 40 75 + +#697387 +R 40 57 41 57 +r 36 57 +r 41 58 + +#6a5151 +r 39 75 + +#6d758e +R 44 55 45 57 +r 43 56 + +#707b98 +R 32 60 32 61 +r 33 61 +r 34 59 + +#7fc7d9 +R 27 53 28 53 +R 27 58 28 58 +R 29 54 29 55 +R 38 44 40 44 +R 42 42 43 43 +R 42 49 43 50 +R 43 45 46 48 +R 44 49 45 49 +R 49 45 49 47 +R 50 43 51 44 +R 50 45 53 48 +R 51 49 52 51 +R 57 46 59 47 +r 28 57 +r 45 42 +r 52 44 +r 53 49 +r 54 48 +r 57 60 + +#817843 +R 58 160 59 160 + +#85857a +R 45 70 46 71 +r 25 77 +r 27 77 +r 47 68 + +#89a4ab +r 44 51 +r 59 44 + +#a4d3df +R 25 75 26 75 +R 28 75 31 75 +r 28 74 +r 37 75 +r 38 71 +r 42 75 +r 47 73 +r 49 75 + +#a5996a +r 29 72 + +#a5d5e1 +R 25 86 26 89 +R 28 76 31 76 +R 30 86 31 87 +R 32 86 33 86 +R 33 76 33 77 +R 40 73 41 74 +R 53 90 54 92 +R 54 74 55 74 +R 54 89 55 89 +r 26 66 +r 28 70 +r 30 83 +r 32 84 +r 36 71 +r 38 84 +r 40 71 +r 44 75 +r 44 79 +r 45 73 +r 45 85 +r 47 70 +r 53 78 +r 55 90 +r 57 154 + +#b2b595 +r 40 67 + +#b6cbd8 +R 22 61 28 62 +R 36 60 37 60 +R 42 59 42 61 +R 42 159 43 159 +R 43 58 45 61 +r 24 60 +r 29 60 +r 32 75 +r 44 155 +r 47 158 +r 53 76 + +#dbd9db +r 45 75 + +// RegionID 4663 +m 18 55 +#4a706a +C 0 0 5 7 +#527874 +C 6 0 7 1 +#668d8d +C 6 2 7 7 + +// RegionID 4664 +m 18 56 +#4a706a +C 0 0 5 7 +C 6 2 7 7 +#608786 +C 6 0 7 1 + +// RegionID 4919 +m 19 55 +C 6 0 7 1 +#668d8d +C 0 0 5 1 +#8dbac1 +C 6 2 7 7 +#9dccd6 +C 0 2 5 7 + +// RegionID 4920 +m 19 56 +#4a706a +C 0 2 7 7 +#80aab0 +C 6 0 7 1 +#8dbac1 +C 0 0 5 1 + +// RegionID 5689 +m 22 57 +#4a706a +C 0 4 7 7 + +// RegionID 5690 +m 22 58 +C 0 0 7 1 +#64554b +C 0 2 7 7 + +// RegionID 5945 +m 23 57 +#4a706a +C 0 4 7 7 + +// RegionID 5948 +m 23 60 +#64554b +C 0 0 5 5 +C 0 6 3 7 +C 6 0 7 3 +#b6cbd8 +C 4 6 7 7 +C 6 4 7 5 + +// RegionID 6201 +m 24 57 +#4a706a +C 0 4 3 7 + +// RegionID 6203 +m 24 59 +#4a718f +C 4 2 7 7 +C 6 0 7 1 +#64554b +C 0 0 3 7 +C 4 0 5 1 + +// RegionID 6460 +m 25 60 +#4a718f +C 0 0 5 1 +#553d75 +C 0 2 7 5 +C 4 6 7 7 +C 6 0 7 1 +#adc0cf +C 2 6 3 7 +#b6cbd8 +C 0 6 1 7 + +// RegionID 6710 +m 26 54 +#7fc7d9 +C 0 0 7 3 + +// RegionID 6715 +m 26 59 +#4a718f +C 0 0 5 7 +C 6 0 7 5 +b 5 +#4d020a +C 5 5 8 8 +b 8 + +// RegionID 6966 +m 27 54 +#7fc7d9 +C 0 0 7 3 + +// RegionID 6971 +m 27 59 +#553d75 +C 2 6 7 7 +#5e2423 +C 0 6 1 7 +#7fc7d9 +C 0 0 7 3 + +// RegionID 7222 +m 28 54 +C 0 0 7 3 +C 6 4 7 7 + +// RegionID 7223 +m 28 55 +C 6 0 7 7 + +// RegionID 7224 +m 28 56 +C 4 0 7 7 + +// RegionID 7227 +m 28 59 +#553d75 +C 0 6 7 7 +#7fc7d9 +C 0 0 7 3 +C 4 4 7 5 + +// RegionID 8508 +m 33 60 +#707b98 +C 0 0 1 3 +C 0 4 7 7 +#838aa1 +C 2 0 7 3 + +// RegionID 9007 +m 35 47 +#3b4f4d +C 2 6 5 7 +#4a706a +C 0 0 7 5 +C 0 6 1 7 +C 6 6 7 7 + +// RegionID 9008 +m 35 48 +#384947 +C 2 0 5 1 +#4a706a +C 0 2 7 7 +C 0 0 1 1 +C 6 0 7 1 + +// RegionID 10029 +m 39 45 +#7fc7d9 +C 0 0 7 1 + +// RegionID 10040 +m 39 56 +#6d758e +C 0 4 3 7 + +// RegionID 10044 +m 39 60 +#b6cbd8 +C 0 6 7 7 + +// RegionID 10055 +m 39 71 +#a4d3df +C 0 0 1 7 +#a5d5e1 +C 2 0 7 7 + +// RegionID 10285 +m 40 45 +#7fc7d9 +C 2 0 7 3 +C 4 4 7 5 +C 0 0 1 1 +C 6 6 7 7 + +// RegionID 10555 +m 41 59 +#b6cbd8 +C 6 0 7 7 + +// RegionID 10556 +m 41 60 +C 6 0 7 7 + +// RegionID 10810 +m 42 58 +#869dcc +C 0 0 3 3 +#b6cbd8 +C 0 4 7 7 +C 4 0 7 3 + +// RegionID 10905 +m 42 153 +#332a15 +C 2 0 5 5 +C 6 0 7 1 +#a9bbc6 +C 6 2 7 3 +#b3c7d3 +C 0 2 1 5 +C 2 6 5 7 +C 6 4 7 5 +#b6cbd8 +C 0 0 1 1 +C 0 6 1 7 +C 6 6 7 7 + +// RegionID 11062 +m 43 54 +C 6 6 7 7 + +// RegionID 11065 +m 43 57 +#6d758e +C 0 0 7 3 +#b6cbd8 +C 0 4 7 7 + +// RegionID 11314 +m 44 50 +#89a4ab +C 0 4 7 7 + +// RegionID 11317 +m 44 53 +#b6cbd8 +C 4 6 7 7 + +// RegionID 11318 +m 44 54 +C 0 4 1 7 +C 2 2 7 7 +C 4 0 7 1 + +// RegionID 11418 +m 44 154 +#332a15 +C 0 0 7 3 +#acbfcb +C 0 4 7 5 +#b6cbd8 +C 0 6 7 7 + +// RegionID 11675 +m 45 155 +C 0 0 1 7 + +// RegionID 11825 +m 46 49 +#7fc7d9 +C 0 0 7 3 +C 6 4 7 5 + +// RegionID 11830 +m 46 54 +#b6cbd8 +C 6 0 7 5 + +// RegionID 11836 +m 46 60 +#1a1a1a +C 2 0 3 3 +C 4 0 7 7 +#b4c9d6 +C 2 4 3 5 +#b6cbd8 +C 0 0 1 7 +C 2 6 3 7 + +// RegionID 11837 +m 46 61 +#161616 +C 6 0 7 9 +#b6cbd8 +C 0 0 5 9 + +// RegionID 12081 +m 47 49 +#7fc7d9 +C 0 0 3 5 + +// RegionID 12086 +m 47 54 +#b6cbd8 +C 0 0 1 5 + +// RegionID 12113 +m 47 81 +#4f4f4f +C 0 0 3 3 +C 4 4 7 7 +#543232 +C 0 4 3 7 +C 4 0 7 3 + +// RegionID 12181 +m 47 149 +#332a15 +C 0 0 1 7 +#b6cbd8 +C 2 0 7 7 + +// RegionID 12339 +m 48 51 +#3e3e3e +C 0 6 7 7 + +// RegionID 12437 +m 48 149 +#332a15 +C 2 0 7 7 +#b6cbd8 +C 0 0 1 7 + +// RegionID 12444 +m 48 156 +#332a15 +C 0 0 7 3 + +// RegionID 12595 +m 49 51 +#3e3e3e +C 0 6 1 7 + +// RegionID 12596 +m 49 52 +C 0 0 1 5 + +// RegionID 12849 +m 50 49 +#40645d +C 0 0 5 7 +#426660 +C 6 0 7 7 + +// RegionID 13209 +m 51 153 +#3b3535 +C 0 0 3 3 +C 4 4 7 7 +#543232 +C 0 4 3 7 +C 4 0 7 3 + +// RegionID 13210 +m 51 154 +#332a15 +C 0 0 3 7 + +// RegionID 13618 +m 53 50 +#4a706a +C 4 0 7 7 +#7fc7d9 +C 0 0 3 7 + +// RegionID 13619 +m 53 51 +#4a706a +C 4 0 7 7 +#7fc7d9 +C 0 0 3 7 + +// RegionID 13620 +m 53 52 +#4a706a +C 2 0 7 7 + +// RegionID 13621 +m 53 53 +C 2 0 7 7 + +// RegionID 13622 +m 53 54 +C 4 0 7 7 + +// RegionID 13873 +m 54 49 +C 0 4 7 7 +#7fc7d9 +C 0 0 7 3 + +// RegionID 14129 +m 55 49 +#4a706a +C 0 4 7 7 + +// RegionID 14385 +m 56 49 +C 0 4 7 7 + +// RegionID 14641 +m 57 49 +#543232 +C 0 4 7 7 + +// RegionID 14642 +m 57 50 +#4a706a +C 0 6 7 7 +#543232 +C 0 0 7 5 + +// RegionID 14651 +m 57 59 +#4a706a +C 0 0 7 3 +#7fc7d9 +C 0 4 7 7 + +// RegionID 14907 +m 58 59 +#323232 +C 0 0 7 3 + +// RegionID 14908 +m 58 60 +#7fc7d9 +C 0 0 3 7 + + +// Demonic Gorillas +#1A2B2B +R 32 88 33 88 + +// POH +b 12 +m29 89 + +// 29 89 p0 Basic wood +// 29 89 p1 Basic stone +// 29 89 p3 Fremennik-style wood +p 0 1 3 +#8AD2DF +C 0 0 7 15 +p 0 +#251B09 +c 3 0 +c 0 3 +c 2 3 +c 4 3 +c 6 3 +c 7 4 +p 1 +#484840 +c 3 0 +c 0 3 +c 2 3 +c 4 3 +c 6 3 +c 7 4 + +// 29 89 p2 Whitewashed stone +p 2 +#7fc7d9 +C 0 0 7 15 +p 2 3 +#2C2C29 +c 3 0 +c 0 3 +c 2 3 +c 4 3 +c 6 3 +c 7 4 + +m30 89 + +// 30 89 p0 Tropical wood +p 0 +#7fc7d9 +C 0 0 7 15 + +// 30 89 p1 Fancy stone +// 30 89 p2 Deathly mansion +p 1 2 +#8AD2DF +C 0 0 7 15 + +p 2 +#8AD2DF +C 0 0 7 15 + +p all +#2C2C29 +c 3 0 +c 0 3 +c 2 3 +c 4 3 +c 6 3 +c 7 4 diff --git a/runelite-client/src/test/java/net/runelite/client/plugins/skybox/SkyboxTest.java b/runelite-client/src/test/java/net/runelite/client/plugins/skybox/SkyboxTest.java new file mode 100644 index 0000000000..616937e176 --- /dev/null +++ b/runelite-client/src/test/java/net/runelite/client/plugins/skybox/SkyboxTest.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2019 Abex + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client.plugins.skybox; + +import com.google.common.base.Strings; +import com.google.common.io.CharSource; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import javax.imageio.ImageIO; +import lombok.extern.slf4j.Slf4j; +import org.junit.Assert; +import org.junit.Test; + +@Slf4j +public class SkyboxTest +{ + @Test + public void testLoadSimple() throws IOException + { + Skybox skybox = new Skybox(CharSource.wrap("bounds 0 0 100 100 #00F // R 0 0 100 100\r\nr 99 99").openStream(), "simple"); + Assert.assertEquals(0, skybox.getColorForPoint(0, 0, 0, 1, null)); + Assert.assertEquals(0x0000FF, skybox.getColorForPoint((99 * 64) + 32, (99 * 64) + 32, 0, 1, null)); + } + + @Test + public void testLoadActual() throws IOException + { + long start = System.nanoTime(); + Skybox skybox = new Skybox(SkyboxPlugin.class.getResourceAsStream("skybox.txt"), "skybox.txt"); + log.info("Parse took {}ms", (System.nanoTime() - start) / 1_000_000); + + String skyboxFile = System.getProperty("skyboxExport"); + if (!Strings.isNullOrEmpty(skyboxFile)) + { + start = System.nanoTime(); + BufferedImage img = skybox.render(1f, 0, 0, null); + long time = System.nanoTime() - start; + log.info("Map render took {}ms", time / 1_000_000); + log.info("Single render takes ~{}ns/frame", time / (img.getWidth() * img.getHeight())); + ImageIO.write(img, "png", new File(skyboxFile)); + } + + Assert.assertNotEquals(skybox.getColorForPoint(3232, 3232, 0, .9, null), 0); // Lumbridge will never be black + } +} diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/RSClientMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/RSClientMixin.java index 8945139b9f..f1059583cb 100644 --- a/runelite-mixins/src/main/java/net/runelite/mixins/RSClientMixin.java +++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSClientMixin.java @@ -169,6 +169,9 @@ public abstract class RSClientMixin implements RSClient @Inject private static boolean oldIsResized; + @Inject + static int skyboxColor; + @Inject @Override public Callbacks getCallbacks() @@ -1381,4 +1384,18 @@ public abstract class RSClientMixin implements RSClient return array; } + + @Inject + @Override + public void setSkyboxColor(int newSkyboxColor) + { + skyboxColor = newSkyboxColor; + } + + @Inject + @Override + public int getSkyboxColor() + { + return skyboxColor; + } } \ No newline at end of file diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/RSSceneMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/RSSceneMixin.java index f71082ce37..58c9d7c908 100644 --- a/runelite-mixins/src/main/java/net/runelite/mixins/RSSceneMixin.java +++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSSceneMixin.java @@ -65,6 +65,9 @@ public abstract class RSSceneMixin implements RSScene @Inject private static int[] tmpX = new int[6]; + @Shadow("skyboxColor") + static int skyboxColor; + @Inject private static int[] tmpY = new int[6]; @@ -80,6 +83,22 @@ public abstract class RSSceneMixin implements RSScene drawCallbacks.drawScene(cameraX, cameraY, cameraZ, cameraPitch, cameraYaw, plane); } + final boolean isGpu = client.isGpu(); + + if (!isGpu) + { + if (skyboxColor != 0) + { + client.RasterizerFillRectangle( + client.getViewportXOffset(), + client.getViewportYOffset(), + client.getViewportWidth(), + client.getViewportHeight(), + skyboxColor + ); + } + } + final int maxX = getMaxX(); final int maxY = getMaxY(); final int maxZ = getMaxZ(); @@ -87,7 +106,6 @@ public abstract class RSSceneMixin implements RSScene final int minLevel = getMinLevel(); final RSTile[][][] tiles = getTiles(); - final boolean isGpu = client.isGpu(); final int distance = isGpu ? rl$drawDistance : DEFAULT_DISTANCE; if (cameraX < 0) diff --git a/runescape-api/src/main/java/net/runelite/rs/api/RSClient.java b/runescape-api/src/main/java/net/runelite/rs/api/RSClient.java index 9297fec684..79a4062927 100644 --- a/runescape-api/src/main/java/net/runelite/rs/api/RSClient.java +++ b/runescape-api/src/main/java/net/runelite/rs/api/RSClient.java @@ -794,6 +794,14 @@ public interface RSClient extends RSGameEngine, Client @Override void setOculusOrbNormalSpeed(int state); + @Import("lookingAtX") + @Override + int getOculusOrbFocalPointX(); + + @Import("lookingAtY") + @Override + int getOculusOrbFocalPointY(); + RSItem getLastItemDespawn(); void setLastItemDespawn(RSItem lastItemDespawn); @@ -896,6 +904,9 @@ public interface RSClient extends RSGameEngine, Client @Import("graphicsPixelsHeight") int getGraphicsPixelsHeight(); + @Import("fillRectangle") + void RasterizerFillRectangle(int x, int y, int w, int h, int rgb); + @Import("startX") int getStartX();