/* * 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.cache; import java.awt.Color; import java.awt.Graphics2D; import java.awt.image.BufferedImage; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.FileWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.imageio.ImageIO; import lombok.Getter; import lombok.Setter; import lombok.experimental.Accessors; import lombok.extern.slf4j.Slf4j; import net.runelite.cache.definitions.AreaDefinition; import net.runelite.cache.definitions.FontDefinition; import net.runelite.cache.definitions.ObjectDefinition; import net.runelite.cache.definitions.OverlayDefinition; import net.runelite.cache.definitions.SpriteDefinition; import net.runelite.cache.definitions.UnderlayDefinition; import net.runelite.cache.definitions.WorldMapElementDefinition; import net.runelite.cache.definitions.loaders.OverlayLoader; import net.runelite.cache.definitions.loaders.SpriteLoader; import net.runelite.cache.definitions.loaders.UnderlayLoader; import net.runelite.cache.fs.Archive; import net.runelite.cache.fs.ArchiveFiles; import net.runelite.cache.fs.FSFile; import net.runelite.cache.fs.Index; import net.runelite.cache.fs.Storage; import net.runelite.cache.fs.Store; import net.runelite.cache.item.RSTextureProvider; import net.runelite.cache.models.JagexColor; import net.runelite.cache.region.Location; import net.runelite.cache.region.Position; import net.runelite.cache.region.Region; import net.runelite.cache.region.RegionLoader; import net.runelite.cache.util.BigBufferedImage; import net.runelite.cache.util.KeyProvider; import net.runelite.cache.util.XteaKeyManager; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLineParser; import org.apache.commons.cli.DefaultParser; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; import com.google.gson.Gson; @Slf4j @Accessors(chain = true) public class MapImageDumper { private static String outputDirectory; private static final int MAP_SCALE = 4; // this squared is the number of pixels per map square private static final int BLEND = 5; // number of surrounding tiles for ground blending private static int[] colorPalette = JagexColor.createPalette(JagexColor.BRIGHTNESS_MIN); private static int[][] TILE_SHAPE_2D = new int[][]{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, {1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1}, {1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0}, {0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1}, {0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, {1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1}, {1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0}, {1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1}, {1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1}}; private static int[][] TILE_ROTATION_2D = new int[][]{{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, {12, 8, 4, 0, 13, 9, 5, 1, 14, 10, 6, 2, 15, 11, 7, 3}, {15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0}, {3, 7, 11, 15, 2, 6, 10, 14, 1, 5, 9, 13, 0, 4, 8, 12}}; private final int wallColor = (238 + (int) (random() * 20.0D) - 10 << 16) + (238 + (int) (random() * 20.0D) - 10 << 8) + (238 + (int) (random() * 20.0D) - 10); private final int doorColor = 238 + (int) (random() * 20.0D) - 10 << 16; private final Store store; private final Map underlays = new HashMap<>(); private final Map overlays = new HashMap<>(); private SpriteDefinition[] mapDecorations; private final RegionLoader regionLoader; private final AreaManager areas; private final SpriteManager sprites; private final FontManager fonts; private final WorldMapManager worldMapManager; private RSTextureProvider rsTextureProvider; private final ObjectManager objectManager; @Getter @Setter private boolean labelRegions; @Getter @Setter private boolean outlineRegions; @Getter @Setter private boolean renderMap = true; @Getter @Setter private boolean renderObjects = true; @Getter @Setter private boolean renderIcons = true; @Getter @Setter private boolean renderLabels = true; @Getter @Setter private boolean transparency = true; @Getter @Setter private boolean lowMemory = false; public MapImageDumper(Store store, KeyProvider keyProvider) { this(store, new RegionLoader(store, keyProvider)); } public MapImageDumper(Store store, RegionLoader regionLoader) { this.store = store; this.regionLoader = regionLoader; this.areas = new AreaManager(store); this.sprites = new SpriteManager(store); this.fonts = new FontManager(store); this.worldMapManager = new WorldMapManager(store); this.objectManager = new ObjectManager(store); } public static void main(String[] args) throws IOException { Options options = new Options(); options.addOption(Option.builder().longOpt("cachedir").hasArg().required().build()); options.addOption(Option.builder().longOpt("xteapath").hasArg().required().build()); options.addOption(Option.builder().longOpt("outputdir").hasArg().required().build()); CommandLineParser parser = new DefaultParser(); CommandLine cmd; try { cmd = parser.parse(options, args); } catch (ParseException ex) { System.err.println("Error parsing command line options: " + ex.getMessage()); System.exit(-1); return; } final String cacheDirectory = cmd.getOptionValue("cachedir"); final String xteaJSONPath = cmd.getOptionValue("xteapath"); outputDirectory = cmd.getOptionValue("outputdir"); XteaKeyManager xteaKeyManager = new XteaKeyManager(); try (FileInputStream fin = new FileInputStream(xteaJSONPath)) { xteaKeyManager.loadKeys(fin); } File base = new File(cacheDirectory); File outDir = new File(outputDirectory); outDir.mkdirs(); try (Store store = new Store(base)) { store.load(); MapImageDumper dumper = new MapImageDumper(store, xteaKeyManager); dumper.load(); for (int i = 0; i < Region.Z; ++i) { BufferedImage image = dumper.drawMap(i); File imageFile = new File(outDir, "img-" + i + ".png"); ImageIO.write(image, "png", imageFile); log.info("Wrote image {}", imageFile); } } } protected double random() { // the client would use a random value here, but we prefer determinism return 0.5; } public MapImageDumper setBrightness(double brightness) { colorPalette = JagexColor.createPalette(brightness); return this; } public MapImageDumper load() throws IOException { loadUnderlays(store); loadOverlays(store); objectManager.load(); TextureManager textureManager = new TextureManager(store); textureManager.load(); rsTextureProvider = new RSTextureProvider(textureManager, sprites); loadRegions(); areas.load(); sprites.load(); loadSprites(); fonts.load(); worldMapManager.load(); return this; } public BufferedImage drawMap(int z) { int minX = regionLoader.getLowestX().getBaseX(); int minY = regionLoader.getLowestY().getBaseY(); int maxX = regionLoader.getHighestX().getBaseX() + Region.X; int maxY = regionLoader.getHighestY().getBaseY() + Region.Y; int dimX = maxX - minX; int dimY = maxY - minY; int pixelsX = dimX * MAP_SCALE; int pixelsY = dimY * MAP_SCALE; log.info("Map image dimensions: {}px x {}px, {}px per map square ({} MB). Max memory: {}mb", pixelsX, pixelsY, MAP_SCALE, (pixelsX * pixelsY * 3 / 1024 / 1024), Runtime.getRuntime().maxMemory() / 1024L / 1024L); BufferedImage image; if (lowMemory) { image = BigBufferedImage.create(pixelsX, pixelsY, transparency ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB); } else { image = new BufferedImage(pixelsX, pixelsY, transparency ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB); } drawMap(image, z); drawObjects(image, z); dumpMapIcons(z); // drawMapIcons(image, z); // drawMapLabels(image, z); return image; } private void drawNeighborObjects(BufferedImage image, int rx, int ry, int dx, int dy, int z) { Region neighbor = regionLoader.findRegionForRegionCoordinates(rx + dx, ry + dy); if (neighbor == null) { return; } drawObjects(image, Region.X * dx, Region.Y * -dy, neighbor, z); } public BufferedImage drawRegion(Region region, int z) { int pixelsX = Region.X * MAP_SCALE; int pixelsY = Region.Y * MAP_SCALE; BufferedImage image = new BufferedImage(pixelsX, pixelsY, transparency ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB); drawMap(image, 0, 0, z, region); drawNeighborObjects(image, region.getRegionX(), region.getRegionY(), -1, -1, z); drawNeighborObjects(image, region.getRegionX(), region.getRegionY(), -1, 0, z); drawNeighborObjects(image, region.getRegionX(), region.getRegionY(), -1, 1, z); drawNeighborObjects(image, region.getRegionX(), region.getRegionY(), 0, -1, z); drawObjects(image, 0, 0, region, z); drawNeighborObjects(image, region.getRegionX(), region.getRegionY(), 0, 1, z); drawNeighborObjects(image, region.getRegionX(), region.getRegionY(), 1, -1, z); drawNeighborObjects(image, region.getRegionX(), region.getRegionY(), 1, 0, z); drawNeighborObjects(image, region.getRegionX(), region.getRegionY(), 1, 1, z); drawMapIcons(image, 0, 0, region, z); return image; } private void drawMap(BufferedImage image, int drawBaseX, int drawBaseY, int z, Region region) { if (!renderMap) { return; } int[][][] map = new int[4][][]; for (int x = 0; x < Region.X; ++x) { for (int y = 0; y < Region.Y; ++y) { boolean isBridge = (region.getTileSetting(1, x, Region.Y - y - 1) & 2) != 0; int tileZ = z + (isBridge ? 1 : 0); if (tileZ >= Region.Z) { continue; } int tileSetting = region.getTileSetting(z, x, Region.Y - y - 1); if ((tileSetting & 24) == 0) { if (z == 0 && isBridge) { drawTile(image, map, region, drawBaseX, drawBaseY, 0, x, y); } drawTile(image, map, region, drawBaseX, drawBaseY, tileZ, x, y); } if (tileZ < 3) { int upTileSetting = region.getTileSetting(z + 1, x, Region.Y - y - 1); if ((upTileSetting & 8) != 0) { drawTile(image, map, region, drawBaseX, drawBaseY, tileZ + 1, x, y); } } } } } private void drawMap(BufferedImage image, int z) { for (Region region : regionLoader.getRegions()) { int baseX = region.getBaseX(); int baseY = region.getBaseY(); // to pixel X int drawBaseX = baseX - regionLoader.getLowestX().getBaseX(); // to pixel Y. top most y is 0, but the top most // region has the greatest y, so invert int drawBaseY = regionLoader.getHighestY().getBaseY() - baseY; drawMap(image, drawBaseX, drawBaseY, z, region); } } private void drawTile(BufferedImage to, int[][][] planes, Region region, int drawBaseX, int drawBaseY, int z, int x, int y) { int[][] pixels = planes[z]; if (pixels == null) { pixels = planes[z] = new int[Region.X * MAP_SCALE][Region.Y * MAP_SCALE]; drawMap(pixels, region, z); } for (int i = 0; i < MAP_SCALE; ++i) { for (int j = 0; j < MAP_SCALE; ++j) { int argb = pixels[x * MAP_SCALE + i][y * MAP_SCALE + j]; if (argb != 0) { to.setRGB(drawBaseX * MAP_SCALE + x * MAP_SCALE + i, drawBaseY * MAP_SCALE + y * MAP_SCALE + j, argb); } } } } private void drawMap(int[][] pixels, Region region, int z) { int baseX = region.getBaseX(); int baseY = region.getBaseY(); int len = Region.X + BLEND * 2; int[] hues = new int[len]; int[] sats = new int[len]; int[] light = new int[len]; int[] mul = new int[len]; int[] num = new int[len]; boolean hasLeftRegion = regionLoader.findRegionForWorldCoordinates(baseX - 1, baseY) != null; boolean hasRightRegion = regionLoader.findRegionForWorldCoordinates(baseX + Region.X, baseY) != null; boolean hasUpRegion = regionLoader.findRegionForWorldCoordinates(baseX, baseY + Region.Y) != null; boolean hasDownRegion = regionLoader.findRegionForWorldCoordinates(baseX, baseY - 1) != null; for (int xi = (hasLeftRegion ? -BLEND * 2 : -BLEND); xi < Region.X + (hasRightRegion ? BLEND * 2 : BLEND); ++xi) { for (int yi = (hasDownRegion ? -BLEND : 0); yi < Region.Y + (hasUpRegion ? BLEND : 0); ++yi) { int xr = xi + BLEND; if (xr >= (hasLeftRegion ? -BLEND : 0) && xr < Region.X + (hasRightRegion ? BLEND : 0)) { Region r = regionLoader.findRegionForWorldCoordinates(baseX + xr, baseY + yi); if (r != null) { int underlayId = r.getUnderlayId(z, convert(xr), convert(yi)); if (underlayId > 0) { UnderlayDefinition underlay = findUnderlay(underlayId - 1); hues[yi + BLEND] += underlay.getHue(); sats[yi + BLEND] += underlay.getSaturation(); light[yi + BLEND] += underlay.getLightness(); mul[yi + BLEND] += underlay.getHueMultiplier(); num[yi + BLEND]++; } } } int xl = xi - BLEND; if (xl >= (hasLeftRegion ? -BLEND : 0) && xl < Region.X + (hasRightRegion ? BLEND : 0)) { Region r = regionLoader.findRegionForWorldCoordinates(baseX + xl, baseY + yi); if (r != null) { int underlayId = r.getUnderlayId(z, convert(xl), convert(yi)); if (underlayId > 0) { UnderlayDefinition underlay = findUnderlay(underlayId - 1); hues[yi + BLEND] -= underlay.getHue(); sats[yi + BLEND] -= underlay.getSaturation(); light[yi + BLEND] -= underlay.getLightness(); mul[yi + BLEND] -= underlay.getHueMultiplier(); num[yi + BLEND]--; } } } } if (xi >= 0 && xi < Region.X) { int runningHues = 0; int runningSat = 0; int runningLight = 0; int runningMultiplier = 0; int runningNumber = 0; for (int yi = (hasDownRegion ? -BLEND * 2 : -BLEND); yi < Region.Y + (hasUpRegion ? BLEND * 2 : BLEND); ++yi) { int yu = yi + BLEND; if (yu >= (hasDownRegion ? -BLEND : 0) && yu < Region.Y + (hasUpRegion ? BLEND : 0)) { runningHues += hues[yu + BLEND]; runningSat += sats[yu + BLEND]; runningLight += light[yu + BLEND]; runningMultiplier += mul[yu + BLEND]; runningNumber += num[yu + BLEND]; } int yd = yi - BLEND; if (yd >= (hasDownRegion ? -BLEND : 0) && yd < Region.Y + (hasUpRegion ? BLEND : 0)) { runningHues -= hues[yd + BLEND]; runningSat -= sats[yd + BLEND]; runningLight -= light[yd + BLEND]; runningMultiplier -= mul[yd + BLEND]; runningNumber -= num[yd + BLEND]; } if (yi >= 0 && yi < Region.Y) { Region r = regionLoader.findRegionForWorldCoordinates(baseX + xi, baseY + yi); if (r != null) { int underlayId = r.getUnderlayId(z, convert(xi), convert(yi)); int overlayId = r.getOverlayId(z, convert(xi), convert(yi)); if (underlayId > 0 || overlayId > 0) { int underlayHsl = -1; if (underlayId > 0) { int avgHue = runningHues * 256 / runningMultiplier; int avgSat = runningSat / runningNumber; int avgLight = runningLight / runningNumber; // randomness is added to avgHue here if (avgLight < 0) { avgLight = 0; } else if (avgLight > 255) { avgLight = 255; } underlayHsl = packHsl(avgHue, avgSat, avgLight); } int underlayRgb = 0; if (underlayHsl != -1) { int var0 = method1792(underlayHsl, 96); underlayRgb = colorPalette[var0] | 0xFF000000; } int shape, rotation; int overlayRgb = 0; if (overlayId == 0) { shape = rotation = 0; } else { shape = r.getOverlayPath(z, convert(xi), convert(yi)) + 1; rotation = r.getOverlayRotation(z, convert(xi), convert(yi)); OverlayDefinition overlayDefinition = findOverlay(overlayId - 1); int overlayTexture = overlayDefinition.getTexture(); int hsl; if (overlayTexture >= 0) { hsl = rsTextureProvider.getAverageTextureRGB(overlayTexture); } else if (overlayDefinition.getRgbColor() == 0xFF_00FF) { hsl = -2; } else { // randomness added here int overlayHsl = packHsl(overlayDefinition.getHue(), overlayDefinition.getSaturation(), overlayDefinition.getLightness()); hsl = overlayHsl; } if (hsl != -2) { int var0 = adjustHSLListness0(hsl, 96); overlayRgb = colorPalette[var0] | 0xFF000000; } if (overlayDefinition.getSecondaryRgbColor() != -1) { int hue = overlayDefinition.getOtherHue(); int sat = overlayDefinition.getOtherSaturation(); int olight = overlayDefinition.getOtherLightness(); hsl = packHsl(hue, sat, olight); int var0 = adjustHSLListness0(hsl, 96); overlayRgb = colorPalette[var0] | 0xFF000000; } } if (shape == 0) { int drawX = xi; int drawY = Region.Y - 1 - yi; if (underlayRgb != 0) { drawMapSquare(pixels, drawX, drawY, underlayRgb); } } else if (shape == 1) { int drawX = xi; int drawY = Region.Y - 1 - yi; drawMapSquare(pixels, drawX, drawY, overlayRgb); } else { int drawX = xi * MAP_SCALE; int drawY = (Region.Y - 1 - yi) * MAP_SCALE; int[] tileShapes = TILE_SHAPE_2D[shape]; int[] tileRotations = TILE_ROTATION_2D[rotation]; if (underlayRgb != 0) { int rotIdx = 0; for (int i = 0; i < Region.Z; ++i) { int p1 = tileShapes[tileRotations[rotIdx++]] == 0 ? underlayRgb : overlayRgb; int p2 = tileShapes[tileRotations[rotIdx++]] == 0 ? underlayRgb : overlayRgb; int p3 = tileShapes[tileRotations[rotIdx++]] == 0 ? underlayRgb : overlayRgb; int p4 = tileShapes[tileRotations[rotIdx++]] == 0 ? underlayRgb : overlayRgb; pixels[drawX + 0][drawY + i] = p1; pixels[drawX + 1][drawY + i] = p2; pixels[drawX + 2][drawY + i] = p3; pixels[drawX + 3][drawY + i] = p4; } } else { int rotIdx = 0; for (int i = 0; i < Region.Z; ++i) { int p1 = tileShapes[tileRotations[rotIdx++]]; int p2 = tileShapes[tileRotations[rotIdx++]]; int p3 = tileShapes[tileRotations[rotIdx++]]; int p4 = tileShapes[tileRotations[rotIdx++]]; if (p1 != 0) { pixels[drawX + 0][drawY + i] = overlayRgb; } if (p2 != 0) { pixels[drawX + 1][drawY + i] = overlayRgb; } if (p3 != 0) { pixels[drawX + 2][drawY + i] = overlayRgb; } if (p4 != 0) { pixels[drawX + 3][drawY + i] = overlayRgb; } } } } } } } } } } } private static int convert(int d) { if (d >= 0) { return d % 64; } else { return 64 - -(d % 64) - 1; } } private void drawObjects(BufferedImage image, int drawBaseX, int drawBaseY, Region region, int z) { if (!renderObjects) { return; } List planeLocs = new ArrayList<>(); List pushDownLocs = new ArrayList<>(); List> layers = Arrays.asList(planeLocs, pushDownLocs); for (int localX = 0; localX < Region.X; localX++) { int regionX = localX + region.getBaseX(); for (int localY = 0; localY < Region.Y; localY++) { int regionY = localY + region.getBaseY(); planeLocs.clear(); pushDownLocs.clear(); boolean isBridge = (region.getTileSetting(1, localX, localY) & 2) != 0; int tileZ = z + (isBridge ? 1 : 0); for (Location loc : region.getLocations()) { Position pos = loc.getPosition(); if (pos.getX() != regionX || pos.getY() != regionY) { continue; } if (pos.getZ() == tileZ && (region.getTileSetting(z, localX, localY) & 24) == 0) { planeLocs.add(loc); } else if (z < 3 && pos.getZ() == tileZ + 1 && (region.getTileSetting(z + 1, localX, localY) & 8) != 0) { pushDownLocs.add(loc); } } for (List locs : layers) { for (Location location : locs) { int type = location.getType(); if (type >= 0 && type <= 3) { int rotation = location.getOrientation(); ObjectDefinition object = findObject(location.getId()); int drawX = (drawBaseX + localX) * MAP_SCALE; int drawY = (drawBaseY + (Region.Y - object.getSizeY() - localY)) * MAP_SCALE; int rgb = wallColor; if (object.getWallOrDoor() != 0) { rgb = doorColor; } rgb |= 0xFF000000; if (object.getMapSceneID() != -1) { blitMapDecoration(image, drawX, drawY, object); } else if (drawX >= 0 && drawY >= 0 && drawX < image.getWidth() && drawY < image.getHeight()) { if (type == 0 || type == 2) { if (rotation == 0) { image.setRGB(drawX + 0, drawY + 0, rgb); image.setRGB(drawX + 0, drawY + 1, rgb); image.setRGB(drawX + 0, drawY + 2, rgb); image.setRGB(drawX + 0, drawY + 3, rgb); } else if (rotation == 1) { image.setRGB(drawX + 0, drawY + 0, rgb); image.setRGB(drawX + 1, drawY + 0, rgb); image.setRGB(drawX + 2, drawY + 0, rgb); image.setRGB(drawX + 3, drawY + 0, rgb); } else if (rotation == 2) { image.setRGB(drawX + 3, drawY + 0, rgb); image.setRGB(drawX + 3, drawY + 1, rgb); image.setRGB(drawX + 3, drawY + 2, rgb); image.setRGB(drawX + 3, drawY + 3, rgb); } else if (rotation == 3) { image.setRGB(drawX + 0, drawY + 3, rgb); image.setRGB(drawX + 1, drawY + 3, rgb); image.setRGB(drawX + 2, drawY + 3, rgb); image.setRGB(drawX + 3, drawY + 3, rgb); } } if (type == 3) { if (rotation == 0) { image.setRGB(drawX + 0, drawY + 0, rgb); } else if (rotation == 1) { image.setRGB(drawX + 3, drawY + 0, rgb); } else if (rotation == 2) { image.setRGB(drawX + 3, drawY + 3, rgb); } else if (rotation == 3) { image.setRGB(drawX + 0, drawY + 3, rgb); } } if (type == 2) { if (rotation == 3) { image.setRGB(drawX + 0, drawY + 0, rgb); image.setRGB(drawX + 0, drawY + 1, rgb); image.setRGB(drawX + 0, drawY + 2, rgb); image.setRGB(drawX + 0, drawY + 3, rgb); } else if (rotation == 0) { image.setRGB(drawX + 0, drawY + 0, rgb); image.setRGB(drawX + 1, drawY + 0, rgb); image.setRGB(drawX + 2, drawY + 0, rgb); image.setRGB(drawX + 3, drawY + 0, rgb); } else if (rotation == 1) { image.setRGB(drawX + 3, drawY + 0, rgb); image.setRGB(drawX + 3, drawY + 1, rgb); image.setRGB(drawX + 3, drawY + 2, rgb); image.setRGB(drawX + 3, drawY + 3, rgb); } else if (rotation == 2) { image.setRGB(drawX + 0, drawY + 3, rgb); image.setRGB(drawX + 1, drawY + 3, rgb); image.setRGB(drawX + 2, drawY + 3, rgb); image.setRGB(drawX + 3, drawY + 3, rgb); } } } } } for (Location location : locs) { int type = location.getType(); if (type == 9) { int rotation = location.getOrientation(); ObjectDefinition object = findObject(location.getId()); int drawX = (drawBaseX + localX) * MAP_SCALE; int drawY = (drawBaseY + (Region.Y - object.getSizeY() - localY)) * MAP_SCALE; if (object.getMapSceneID() != -1) { blitMapDecoration(image, drawX, drawY, object); continue; } if (drawX >= 0 && drawY >= 0 && drawX < image.getWidth() && drawY < image.getHeight()) { int rgb = 0xFFEE_EEEE; if (object.getWallOrDoor() != 0) { rgb = 0xFFEE_0000; } if (rotation != 0 && rotation != 2) { image.setRGB(drawX + 0, drawY + 0, rgb); image.setRGB(drawX + 1, drawY + 1, rgb); image.setRGB(drawX + 2, drawY + 2, rgb); image.setRGB(drawX + 3, drawY + 3, rgb); } else { image.setRGB(drawX + 0, drawY + 3, rgb); image.setRGB(drawX + 1, drawY + 2, rgb); image.setRGB(drawX + 2, drawY + 1, rgb); image.setRGB(drawX + 3, drawY + 0, rgb); } } } } for (Location location : locs) { int type = location.getType(); if (type == 22 || (type >= 9 && type <= 11)) { ObjectDefinition object = findObject(location.getId()); int drawX = (drawBaseX + localX) * MAP_SCALE; int drawY = (drawBaseY + (Region.Y - object.getSizeY() - localY)) * MAP_SCALE; if (object.getMapSceneID() != -1) { blitMapDecoration(image, drawX, drawY, object); } } } } } } } private void drawObjects(BufferedImage image, int z) { for (Region region : regionLoader.getRegions()) { int baseX = region.getBaseX(); int baseY = region.getBaseY(); // to pixel X int drawBaseX = baseX - regionLoader.getLowestX().getBaseX(); // to pixel Y. top most y is 0, but the top most // region has the greatest y, so invert int drawBaseY = regionLoader.getHighestY().getBaseY() - baseY; drawObjects(image, drawBaseX, drawBaseY, region, z); } } private void drawMapIcons(BufferedImage image, int drawBaseX, int drawBaseY, Region region, int z) { int baseX = region.getBaseX(); int baseY = region.getBaseY(); Graphics2D graphics = image.createGraphics(); drawMapIcons(image, region, z, drawBaseX, drawBaseY); if (labelRegions) { graphics.setColor(Color.WHITE); String str = baseX + "," + baseY + " (" + region.getRegionX() + "," + region.getRegionY() + ")"; graphics.drawString(str, drawBaseX * MAP_SCALE, drawBaseY * MAP_SCALE + graphics.getFontMetrics().getHeight()); } if (outlineRegions) { graphics.setColor(Color.WHITE); graphics.drawRect(drawBaseX * MAP_SCALE, drawBaseY * MAP_SCALE, Region.X * MAP_SCALE, Region.Y * MAP_SCALE); } graphics.dispose(); } private void drawMapIcons(BufferedImage image, int z) { // map icons for (Region region : regionLoader.getRegions()) { int baseX = region.getBaseX(); int baseY = region.getBaseY(); // to pixel X int drawBaseX = baseX - regionLoader.getLowestX().getBaseX(); // to pixel Y. top most y is 0, but the top most // region has the greatest y, so invert int drawBaseY = regionLoader.getHighestY().getBaseY() - baseY; drawMapIcons(image, drawBaseX, drawBaseY, region, z); } } private void drawMapLabels(BufferedImage image, int z) { if (!renderLabels) { return; } FontName[] fontSizes = new FontName[] { FontName.VERDANA_11, FontName.VERDANA_13, FontName.VERDANA_15 }; List elements = worldMapManager.getElements(); for (WorldMapElementDefinition element : elements) { AreaDefinition area = areas.getArea(element.getAreaDefinitionId()); Position worldPosition = element.getWorldPosition(); if (area == null || area.getName() == null || worldPosition.getZ() != z) { continue; } FontName fontSize = fontSizes[area.getTextScale()]; FontDefinition font = fonts.findFontByName(fontSize.getName()); String areaLabel = area.getName(); String[] lines = areaLabel.split("
"); int ascent = 0; for (String line : lines) { int advance = 0; int stringWidth = font.stringWidth(line); for (int i = 0; i < line.length(); ++i) { char c = line.charAt(i); SpriteDefinition sprite = sprites.findSpriteByArchiveName(fontSize.getName(), c); if (sprite.getWidth() != 0 && sprite.getHeight() != 0) { int drawX = worldPosition.getX() - regionLoader.getLowestX().getBaseX(); int drawY = regionLoader.getHighestY().getBaseY() - worldPosition.getY() + Region.Y - 2; blitGlyph(image, (drawX * MAP_SCALE) + advance - (stringWidth / 2), (drawY * MAP_SCALE) + ascent - (font.getAscent() / 2), area.getTextColor(), sprite ); } advance += font.getAdvances()[c]; } ascent += font.getAscent() / 2; } } } private void dumpMapIcons(int z) { Map> icons = new HashMap<>(); if (z == 0) return; for (Region region : regionLoader.getRegions()) { for (Location location : region.getLocations()) { ObjectDefinition od = findObject(location.getId()); if (od.getMapAreaId() != -1) { AreaDefinition area = areas.getArea(od.getMapAreaId()); List x = icons.computeIfAbsent(area.spriteId, k -> new ArrayList<>()); x.add(location.getPosition().getX()); x.add(location.getPosition().getY()); } } } File outDir = new File(outputDirectory + "/icons"); outDir.mkdirs(); for (Integer spriteId : icons.keySet()) { SpriteDefinition sprite = sprites.findSprite(spriteId, 0); BufferedImage iconImage = new BufferedImage(sprite.getWidth(), sprite.getHeight(), BufferedImage.TYPE_INT_ARGB); for (int x = 0; x < sprite.getWidth(); ++x) { for (int y = 0; y < sprite.getHeight(); ++y) { int rgb = sprite.getPixels()[x + (y * sprite.getWidth())]; if (rgb != 0) { iconImage.setRGB(x, y, rgb | 0xFF000000); } } } try { File iconFile = new File(outDir, spriteId + ".png"); ImageIO.write(iconImage, "png", iconFile); } catch (Exception ex) { log.error("Failed to write sprite file", ex); } } try { Gson gson = new Gson(); File jsonFile = new File(outDir, "map-icons.json"); FileWriter writer = new FileWriter(jsonFile); gson.toJson(icons, writer); writer.flush(); writer.close(); } catch (Exception ex) { log.error("Failed to write map-icons.json", ex); } } private ObjectDefinition findObject(int id) { return objectManager.getObject(id); } private int packHsl(int var0, int var1, int var2) { if (var2 > 179) { var1 /= 2; } if (var2 > 192) { var1 /= 2; } if (var2 > 217) { var1 /= 2; } if (var2 > 243) { var1 /= 2; } int var3 = (var1 / 32 << 7) + (var0 / 4 << 10) + var2 / 2; return var3; } static int method1792(int var0, int var1) { if (var0 == -1) { return 12345678; } else { var1 = (var0 & 127) * var1 / 128; if (var1 < 2) { var1 = 2; } else if (var1 > 126) { var1 = 126; } return (var0 & 65408) + var1; } } static final int adjustHSLListness0(int var0, int var1) { if (var0 == -2) { return 12345678; } else if (var0 == -1) { if (var1 < 2) { var1 = 2; } else if (var1 > 126) { var1 = 126; } return var1; } else { var1 = (var0 & 127) * var1 / 128; if (var1 < 2) { var1 = 2; } else if (var1 > 126) { var1 = 126; } return (var0 & 65408) + var1; } } private void drawMapSquare(int[][] pixels, int x, int y, int rgb) { x *= MAP_SCALE; y *= MAP_SCALE; for (int i = 0; i < MAP_SCALE; ++i) { for (int j = 0; j < MAP_SCALE; ++j) { pixels[x + i][y + j] = rgb; } } } private void drawMapIcons(BufferedImage img, Region region, int z, int drawBaseX, int drawBaseY) { if (!renderIcons) { return; } for (Location location : region.getLocations()) { int localX = location.getPosition().getX() - region.getBaseX(); int localY = location.getPosition().getY() - region.getBaseY(); if (z != location.getPosition().getZ()) { continue; } ObjectDefinition od = findObject(location.getId()); assert od != null; int drawX = drawBaseX + localX; int drawY = drawBaseY + (Region.Y - 1 - localY); if (od.getMapAreaId() != -1) { AreaDefinition area = areas.getArea(od.getMapAreaId()); assert area != null; SpriteDefinition sprite = sprites.findSprite(area.spriteId, 0); assert sprite != null; blitIcon(img, (drawX * MAP_SCALE) - (sprite.getMaxWidth() / 2), (drawY * MAP_SCALE) - (sprite.getMaxHeight() / 2), sprite); } } // Draw the intermap link icons which are not stored with the map locations List elements = worldMapManager.getElements(); for (WorldMapElementDefinition element : elements) { AreaDefinition area = areas.getArea(element.getAreaDefinitionId()); Position worldPosition = element.getWorldPosition(); int regionX = worldPosition.getX() / Region.X; int regionY = worldPosition.getY() / Region.Y; if (area == null || area.getName() != null || worldPosition.getZ() != z || regionX != region.getRegionX() || regionY != region.getRegionY()) { continue; } int localX = worldPosition.getX() - region.getBaseX(); int localY = worldPosition.getY() - region.getBaseY(); int drawX = drawBaseX + localX; int drawY = drawBaseY + (Region.Y - 1 - localY); SpriteDefinition sprite = sprites.findSprite(area.spriteId, 0); blitIcon(img, (drawX * MAP_SCALE) - (sprite.getMaxWidth() / 2), (drawY * MAP_SCALE) - (sprite.getMaxHeight() / 2), sprite); } } private void loadRegions() throws IOException { regionLoader.loadRegions(); regionLoader.calculateBounds(); log.debug("North most region: {}", regionLoader.getLowestY().getBaseY()); log.debug("South most region: {}", regionLoader.getHighestY().getBaseY()); log.debug("West most region: {}", regionLoader.getLowestX().getBaseX()); log.debug("East most region: {}", regionLoader.getHighestX().getBaseX()); } private void loadUnderlays(Store store) throws IOException { Storage storage = store.getStorage(); Index index = store.getIndex(IndexType.CONFIGS); Archive archive = index.getArchive(ConfigType.UNDERLAY.getId()); byte[] archiveData = storage.loadArchive(archive); ArchiveFiles files = archive.getFiles(archiveData); for (FSFile file : files.getFiles()) { UnderlayLoader loader = new UnderlayLoader(); UnderlayDefinition underlay = loader.load(file.getFileId(), file.getContents()); underlays.put(underlay.getId(), underlay); } } private UnderlayDefinition findUnderlay(int id) { return underlays.get(id); } private void loadOverlays(Store store) throws IOException { Storage storage = store.getStorage(); Index index = store.getIndex(IndexType.CONFIGS); Archive archive = index.getArchive(ConfigType.OVERLAY.getId()); byte[] archiveData = storage.loadArchive(archive); ArchiveFiles files = archive.getFiles(archiveData); for (FSFile file : files.getFiles()) { OverlayLoader loader = new OverlayLoader(); OverlayDefinition overlay = loader.load(file.getFileId(), file.getContents()); overlays.put(overlay.getId(), overlay); } } private OverlayDefinition findOverlay(int id) { return overlays.get(id); } private void loadSprites() throws IOException { Storage storage = store.getStorage(); Index index = store.getIndex(IndexType.SPRITES); Archive a = index.findArchiveByName("mapscene"); byte[] contents = a.decompress(storage.loadArchive(a)); SpriteLoader loader = new SpriteLoader(); mapDecorations = loader.load(a.getArchiveId(), contents); } private void blitMapDecoration(BufferedImage dst, int x, int y, ObjectDefinition object) { SpriteDefinition sprite = mapDecorations[object.getMapSceneID()]; int ox = (object.getSizeX() * MAP_SCALE - sprite.getWidth()) / 2; int oy = (object.getSizeY() * MAP_SCALE - sprite.getHeight()) / 2; blitIcon(dst, x + ox, y + oy, sprite); } private void blitIcon(BufferedImage dst, int x, int y, SpriteDefinition sprite) { x += sprite.getOffsetX(); y += sprite.getOffsetY(); int ymin = Math.max(0, -y); int ymax = Math.min(sprite.getHeight(), dst.getHeight() - y); int xmin = Math.max(0, -x); int xmax = Math.min(sprite.getWidth(), dst.getWidth() - x); for (int yo = ymin; yo < ymax; yo++) { for (int xo = xmin; xo < xmax; xo++) { int rgb = sprite.getPixels()[xo + (yo * sprite.getWidth())]; if (rgb != 0) { dst.setRGB(x + xo, y + yo, rgb | 0xFF000000); } } } } private void blitGlyph(BufferedImage dst, int x, int y, int color, SpriteDefinition glyph) { int[] pixels = glyph.getPixels(); int[] shadowPixels = new int[pixels.length]; for (int i = 0; i < pixels.length; ++i) { if (pixels[i] != 0) { pixels[i] = color; shadowPixels[i] = 0xFF000000; } } SpriteDefinition shadow = new SpriteDefinition(); shadow.setPixels(shadowPixels); shadow.setOffsetX(glyph.getOffsetX()); shadow.setOffsetY(glyph.getOffsetY()); shadow.setWidth(glyph.getWidth()); shadow.setHeight(glyph.getHeight()); blitIcon(dst, x + 1, y + 1, shadow); blitIcon(dst, x, y, glyph); } }