diff --git a/runelite-api/src/main/java/net/runelite/api/Perspective.java b/runelite-api/src/main/java/net/runelite/api/Perspective.java index 2e507782a3..a6cbe297a7 100644 --- a/runelite-api/src/main/java/net/runelite/api/Perspective.java +++ b/runelite-api/src/main/java/net/runelite/api/Perspective.java @@ -27,8 +27,16 @@ package net.runelite.api; import java.awt.FontMetrics; import java.awt.Graphics2D; import java.awt.Polygon; +import java.awt.Rectangle; +import java.awt.geom.Area; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import net.runelite.api.model.Jarvis; +import net.runelite.api.model.Triangle; +import net.runelite.api.model.Vertex; public class Perspective { @@ -78,10 +86,55 @@ public class Perspective * 3D-space */ public static Point worldToCanvas(Client client, int x, int y, int plane, int zOffset) + { + return worldToCanvas(client, x, y, plane, x, y, zOffset); + } + + /** + * Translates two-dimensional ground coordinates within the 3D world to + * their corresponding coordinates on the game screen. Calculating heights + * based on the coordinates of the tile provided, rather than the world coordinates + * + * Using the position of each vertex, rather than the location of the object, to determine the + * height of each vertex causes the mesh to be vertically warped, based on the terrain below + * + * @param client + * @param x ground coordinate on the x axis + * @param y ground coordinate on the y axis + * @param plane ground plane on the z axis + * @param tileX the X coordinate of the tile the object is on + * @param tileY the Y coordinate of the tile the object is on + * @return a {@link Point} on screen corresponding to the position in + * 3D-space + */ + public static Point worldToCanvas(Client client, int x, int y, int plane, int tileX, int tileY) + { + return worldToCanvas(client, x, y, plane, tileX, tileY, 0); + } + + /** + * Translates two-dimensional ground coordinates within the 3D world to + * their corresponding coordinates on the game screen. Calculating heights + * based on the coordinates of the tile provided, rather than the world coordinates + * + * Using the position of each vertex, rather than the location of the object, to determine the + * height of each vertex causes the mesh to be vertically warped, based on the terrain below + * + * @param client + * @param x ground coordinate on the x axis + * @param y ground coordinate on the y axis + * @param plane ground plane on the z axis + * @param tileX the X coordinate of the tile the object is on + * @param tileY the Y coordinate of the tile the object is on + * @param zOffset distance from ground on the z axis + * @return a {@link Point} on screen corresponding to the position in + * 3D-space + */ + public static Point worldToCanvas(Client client, int x, int y, int plane, int tileX, int tileY, int zOffset) { if (x >= 128 && y >= 128 && x <= 13056 && y <= 13056) { - int z = getTileHeight(client, x, y, client.getPlane()) - plane; + int z = getTileHeight(client, tileX, tileY, client.getPlane()) - plane; x -= client.getCameraX(); y -= client.getCameraY(); z -= client.getCameraZ(); @@ -379,4 +432,223 @@ public class Perspective return new Point(xOffset, yOffset); } + /** + * You don't want this. Use {@link TileObject#getClickbox()} instead + * + * Get the on-screen clickable area of {@code model} as though it's for the object on the tile at + * ({@code tileX}, {@code tileY}) and rotated to angle {@code orientation} + * + * @param client + * @param model the model to calculate a clickbox for + * @param orientation the orientation of the model (0-2048, where 0 is north) + * @param tileX the X coordinate of the tile that the object using the model is on + * @param tileY the Y coordinate of the tile that the object using the model is on + * @return the clickable area of {@code model}, rotated to angle {@code orientation}, at the height of tile ({@code tileX}, {@code tileY}) + */ + public static Area getClickbox(Client client, Model model, int orientation, int tileX, int tileY) + { + if (model == null) + { + return null; + } + + List triangles = model.getTriangles().stream() + .map(triangle -> triangle.rotate(orientation)) + .collect(Collectors.toList()); + + List vertices = model.getVertices().stream() + .map(v -> v.rotate(orientation)) + .collect(Collectors.toList()); + + Area clickBox = get2DGeometry(client, triangles, orientation, tileX, tileY); + Area visibleAABB = getAABB(client, vertices, orientation, tileX, tileY); + + if (visibleAABB == null || clickBox == null) + { + return null; + } + + clickBox.intersect(visibleAABB); + return clickBox; + } + + private static Area get2DGeometry(Client client, List triangles, int orientation, int tileX, int tileY) + { + int radius = 5; + Area geometry = new Area(); + + for (Triangle triangle : triangles) + { + Vertex _a = triangle.getA(); + Point a = worldToCanvas(client, + tileX - _a.getX(), + tileY - _a.getZ(), + -_a.getY(), tileX, tileY); + if (a == null) + { + continue; + } + + Vertex _b = triangle.getB(); + Point b = worldToCanvas(client, + tileX - _b.getX(), + tileY - _b.getZ(), + -_b.getY(), tileX, tileY); + if (b == null) + { + continue; + } + + Vertex _c = triangle.getC(); + Point c = worldToCanvas(client, + tileX - _c.getX(), + tileY - _c.getZ(), + -_c.getY(), tileX, tileY); + if (c == null) + { + continue; + } + + int minX = Math.min(Math.min(a.getX(), b.getX()), c.getX()); + int minY = Math.min(Math.min(a.getY(), b.getY()), c.getY()); + + // For some reason, this calculation is always 4 pixels short of the actual in-client one + int maxX = Math.max(Math.max(a.getX(), b.getX()), c.getX()) + 4; + int maxY = Math.max(Math.max(a.getY(), b.getY()), c.getY()) + 4; + + // ...and the rectangles in the fixed client are shifted 4 pixels right and down + if (!client.isResized()) + { + minX += 4; + minY += 4; + maxX += 4; + maxY += 4; + } + + Rectangle clickableRect = new Rectangle( + minX - radius, minY - radius, + maxX - minX + radius, maxY - minY + radius + ); + geometry.add(new Area(clickableRect)); + } + + return geometry; + } + + private static Area getAABB(Client client, List vertices, int orientation, int tileX, int tileY) + { + int maxX = 0; + int minX = 0; + int maxY = 0; + int minY = 0; + int maxZ = 0; + int minZ = 0; + + for (Vertex vertex : vertices) + { + int x = vertex.getX(); + int y = vertex.getY(); + int z = vertex.getZ(); + + if (x > maxX) + { + maxX = x; + } + if (x < minX) + { + minX = x; + } + + if (y > maxY) + { + maxY = y; + } + if (y < minY) + { + minY = y; + } + + if (z > maxZ) + { + maxZ = z; + } + if (z < minZ) + { + minZ = z; + } + } + + int centerX = (minX + maxX) / 2; + int centerY = (minY + maxY) / 2; + int centerZ = (minZ + maxZ) / 2; + + int extremeX = (maxX - minX + 1) / 2; + int extremeY = (maxY - minY + 1) / 2; + int extremeZ = (maxZ - minZ + 1) / 2; + + if (extremeX < 32) + { + extremeX = 32; + } + + if (extremeZ < 32) + { + extremeZ = 32; + } + + int x1 = tileX - (centerX - extremeX); + int y1 = centerY - extremeY; + int z1 = tileY - (centerZ - extremeZ); + + int x2 = tileX - (centerX + extremeX); + int y2 = centerY + extremeY; + int z2 = tileY - (centerZ + extremeZ); + + Point p1 = worldToCanvas(client, x1, z1, -y1, tileX, tileY); + Point p2 = worldToCanvas(client, x1, z2, -y1, tileX, tileY); + Point p3 = worldToCanvas(client, x2, z2, -y1, tileX, tileY); + + Point p4 = worldToCanvas(client, x2, z1, -y1, tileX, tileY); + Point p5 = worldToCanvas(client, x1, z1, -y2, tileX, tileY); + Point p6 = worldToCanvas(client, x1, z2, -y2, tileX, tileY); + Point p7 = worldToCanvas(client, x2, z2, -y2, tileX, tileY); + Point p8 = worldToCanvas(client, x2, z1, -y2, tileX, tileY); + + List points = new ArrayList<>(8); + points.add(p1); + points.add(p2); + points.add(p3); + points.add(p4); + points.add(p5); + points.add(p6); + points.add(p7); + points.add(p8); + + try + { + points = Jarvis.convexHull(points); + } + catch (NullPointerException e) + { + // No non-null screen points for this AABB e.g. for an way off-screen model + return null; + } + + if (points == null) + { + return null; + } + + Polygon hull = new Polygon(); + for (Point p : points) + { + if (p != null) + { + hull.addPoint(p.getX(), p.getY()); + } + } + + return new Area(hull); + } + } diff --git a/runelite-api/src/main/java/net/runelite/api/Tile.java b/runelite-api/src/main/java/net/runelite/api/Tile.java index ecb997db40..720afcadbe 100644 --- a/runelite-api/src/main/java/net/runelite/api/Tile.java +++ b/runelite-api/src/main/java/net/runelite/api/Tile.java @@ -50,4 +50,6 @@ public interface Tile Point getRegionLocation(); Point getLocalLocation(); + + int getPlane(); } diff --git a/runelite-api/src/main/java/net/runelite/api/TileObject.java b/runelite-api/src/main/java/net/runelite/api/TileObject.java index 063dcef891..514134251f 100644 --- a/runelite-api/src/main/java/net/runelite/api/TileObject.java +++ b/runelite-api/src/main/java/net/runelite/api/TileObject.java @@ -26,6 +26,7 @@ package net.runelite.api; import java.awt.Graphics2D; import java.awt.Polygon; +import java.awt.geom.Area; public interface TileObject { @@ -54,4 +55,11 @@ public interface TileObject Point getMinimapLocation(); Polygon getConvexHull(Model model, int orientation); + + /** + * Get the on-screen clickable area of {@code object} + * + * @return the clickable area of {@code object} + */ + Area getClickbox(); } diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/RSDecorativeObjectMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/RSDecorativeObjectMixin.java index 42160674e2..f3fd5c155b 100644 --- a/runelite-mixins/src/main/java/net/runelite/mixins/RSDecorativeObjectMixin.java +++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSDecorativeObjectMixin.java @@ -25,18 +25,24 @@ package net.runelite.mixins; import java.awt.Polygon; +import java.awt.geom.Area; import net.runelite.api.Model; +import net.runelite.api.Perspective; import net.runelite.api.Renderable; import net.runelite.api.mixins.Inject; import net.runelite.api.mixins.Mixin; +import net.runelite.api.mixins.Shadow; +import net.runelite.rs.api.RSClient; import net.runelite.rs.api.RSDecorativeObject; @Mixin(RSDecorativeObject.class) public abstract class RSDecorativeObjectMixin implements RSDecorativeObject { + @Shadow("clientInstance") + private static RSClient client; + @Inject - @Override - public Polygon getConvexHull() + private Model getModel() { Renderable renderable = getRenderable(); if (renderable == null) @@ -55,6 +61,22 @@ public abstract class RSDecorativeObjectMixin implements RSDecorativeObject model = renderable.getModel(); } + return model; + } + + @Inject + @Override + public Area getClickbox() + { + return Perspective.getClickbox(client, getModel(), getOrientation(), getX(), getY()); + } + + @Inject + @Override + public Polygon getConvexHull() + { + Model model = getModel(); + if (model == null) { return null; diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/RSGameObjectMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/RSGameObjectMixin.java index 1f509ca478..0ff8ffaed1 100644 --- a/runelite-mixins/src/main/java/net/runelite/mixins/RSGameObjectMixin.java +++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSGameObjectMixin.java @@ -25,16 +25,23 @@ package net.runelite.mixins; import java.awt.Polygon; +import java.awt.geom.Area; import net.runelite.api.Model; +import net.runelite.api.Perspective; import net.runelite.api.Point; import net.runelite.api.Renderable; import net.runelite.api.mixins.Inject; import net.runelite.api.mixins.Mixin; +import net.runelite.api.mixins.Shadow; +import net.runelite.rs.api.RSClient; import net.runelite.rs.api.RSGameObject; @Mixin(RSGameObject.class) public abstract class RSGameObjectMixin implements RSGameObject { + @Shadow("clientInstance") + private static RSClient client; + @Inject @Override public Point getRegionMinLocation() @@ -50,8 +57,7 @@ public abstract class RSGameObjectMixin implements RSGameObject } @Inject - @Override - public Polygon getConvexHull() + private Model getModel() { Renderable renderable = getRenderable(); if (renderable == null) @@ -59,16 +65,28 @@ public abstract class RSGameObjectMixin implements RSGameObject return null; } - Model model; - if (renderable instanceof Model) { - model = (Model) renderable; + return (Model) renderable; } else { - model = renderable.getModel(); + return renderable.getModel(); } + } + + @Inject + @Override + public Area getClickbox() + { + return Perspective.getClickbox(client, getModel(), getOrientation(), getX(), getY()); + } + + @Inject + @Override + public Polygon getConvexHull() + { + Model model = getModel(); if (model == null) { diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/RSGroundObjectMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/RSGroundObjectMixin.java new file mode 100644 index 0000000000..8d84a334d1 --- /dev/null +++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSGroundObjectMixin.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2018, SomeoneWithAnInternetConnection + * 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.mixins; + +import java.awt.geom.Area; +import net.runelite.api.Model; +import net.runelite.api.Perspective; +import net.runelite.api.Renderable; +import net.runelite.api.mixins.Inject; +import net.runelite.api.mixins.Mixin; +import net.runelite.api.mixins.Shadow; +import net.runelite.rs.api.RSClient; +import net.runelite.rs.api.RSGroundObject; + +@Mixin(RSGroundObject.class) +public abstract class RSGroundObjectMixin implements RSGroundObject +{ + @Shadow("clientInstance") + private static RSClient client; + + @Inject + private Model getModel() + { + Renderable renderable = getRenderable(); + if (renderable == null) + { + return null; + } + + if (renderable instanceof Model) + { + return (Model) renderable; + } + else + { + return renderable.getModel(); + } + } + + @Inject + @Override + public Area getClickbox() + { + return Perspective.getClickbox(client, getModel(), 0, getX(), getY()); + } +} diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/RSItemLayerMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/RSItemLayerMixin.java new file mode 100644 index 0000000000..7ee2411045 --- /dev/null +++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSItemLayerMixin.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2018, SomeoneWithAnInternetConnection + * 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.mixins; + +import java.awt.geom.Area; +import net.runelite.api.mixins.Inject; +import net.runelite.api.mixins.Mixin; +import net.runelite.rs.api.RSItemLayer; + +@Mixin(RSItemLayer.class) +public abstract class RSItemLayerMixin implements RSItemLayer +{ + @Inject + @Override + public Area getClickbox() + { + throw new UnsupportedOperationException(); + } + +} diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/RSWallObjectMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/RSWallObjectMixin.java new file mode 100644 index 0000000000..47443cfcf7 --- /dev/null +++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSWallObjectMixin.java @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2018, SomeoneWithAnInternetConnection + * 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.mixins; + +import java.awt.geom.Area; +import net.runelite.api.Model; +import net.runelite.api.Perspective; +import net.runelite.api.Renderable; +import net.runelite.api.mixins.Inject; +import net.runelite.api.mixins.Mixin; +import net.runelite.api.mixins.Shadow; +import net.runelite.rs.api.RSClient; +import net.runelite.rs.api.RSWallObject; + +@Mixin(RSWallObject.class) +public abstract class RSWallObjectMixin implements RSWallObject +{ + @Shadow("clientInstance") + private static RSClient client; + + @Inject + private Model getModelA() + { + Renderable renderable = getRenderable1(); + if (renderable == null) + { + return null; + } + + if (renderable instanceof Model) + { + return (Model) renderable; + } + else + { + return renderable.getModel(); + } + } + + @Inject + private Model getModelB() + { + Renderable renderable = getRenderable2(); + if (renderable == null) + { + return null; + } + + if (renderable instanceof Model) + { + return (Model) renderable; + } + else + { + return renderable.getModel(); + } + } + + @Inject + @Override + public Area getClickbox() + { + Area clickbox = new Area(); + + Area clickboxA = Perspective.getClickbox(client, getModelA(), getOrientationA(), getX(), getY()); + Area clickboxB = Perspective.getClickbox(client, getModelB(), getOrientationB(), getX(), getY()); + + if (clickboxA == null && clickboxB == null) + { + return null; + } + + if (clickboxA != null) + { + clickbox.add(clickboxA); + } + + if (clickboxB != null) + { + clickbox.add(clickboxB); + } + + return clickbox; + } +} diff --git a/runescape-api/src/main/java/net/runelite/rs/api/RSGroundObject.java b/runescape-api/src/main/java/net/runelite/rs/api/RSGroundObject.java index d1c0c8ef26..e1d96c500c 100644 --- a/runescape-api/src/main/java/net/runelite/rs/api/RSGroundObject.java +++ b/runescape-api/src/main/java/net/runelite/rs/api/RSGroundObject.java @@ -25,6 +25,7 @@ package net.runelite.rs.api; import net.runelite.api.GroundObject; +import net.runelite.api.Renderable; import net.runelite.mapping.Import; public interface RSGroundObject extends GroundObject @@ -38,4 +39,7 @@ public interface RSGroundObject extends GroundObject @Import("y") int getY(); + + @Import("renderable") + Renderable getRenderable(); } diff --git a/runescape-api/src/main/java/net/runelite/rs/api/RSTile.java b/runescape-api/src/main/java/net/runelite/rs/api/RSTile.java index 2491175148..b7c9feb58e 100644 --- a/runescape-api/src/main/java/net/runelite/rs/api/RSTile.java +++ b/runescape-api/src/main/java/net/runelite/rs/api/RSTile.java @@ -71,5 +71,6 @@ public interface RSTile extends Tile int getY(); @Import("plane") + @Override int getPlane(); } diff --git a/runescape-api/src/main/java/net/runelite/rs/api/RSWallObject.java b/runescape-api/src/main/java/net/runelite/rs/api/RSWallObject.java index f68f6715c7..9cf6937e59 100644 --- a/runescape-api/src/main/java/net/runelite/rs/api/RSWallObject.java +++ b/runescape-api/src/main/java/net/runelite/rs/api/RSWallObject.java @@ -24,6 +24,7 @@ */ package net.runelite.rs.api; +import net.runelite.api.Renderable; import net.runelite.api.WallObject; import net.runelite.mapping.Import; @@ -49,6 +50,12 @@ public interface RSWallObject extends WallObject @Override int getOrientationB(); + @Import("renderable1") + Renderable getRenderable1(); + + @Import("renderable2") + Renderable getRenderable2(); + @Import("config") @Override int getConfig();