├── .travis.yml ├── lib └── common-1.4.0.1.jar ├── jitpack.yml ├── .gitignore ├── README.md ├── src └── main │ └── java │ └── com │ └── wurmonline │ └── wurmapi │ ├── api │ ├── map │ │ └── dump │ │ │ ├── Colorist.java │ │ │ └── DefaultColorist.java │ ├── WurmAPI.java │ └── MapData.java │ └── internal │ └── CaveColors.java └── pom.xml /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - oraclejdk8 4 | before_install: mvn validate 5 | -------------------------------------------------------------------------------- /lib/common-1.4.0.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeclubab/WurmModServerApi/HEAD/lib/common-1.4.0.1.jar -------------------------------------------------------------------------------- /jitpack.yml: -------------------------------------------------------------------------------- 1 | jdk: 2 | - oraclejdk8 3 | install: 4 | - echo "Running a custom install command" 5 | - mvn validate 6 | - mvn clean install -DskipTests 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Java template 3 | # Compiled class file 4 | *.class 5 | 6 | # Log file 7 | *.log 8 | 9 | # BlueJ files 10 | *.ctxt 11 | 12 | # Mobile Tools for Java (J2ME) 13 | .mtj.tmp/ 14 | 15 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 16 | hs_err_pid* 17 | 18 | # IDEA # 19 | .idea/ 20 | *.iml 21 | 22 | target/ 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WurmAPI 2 | 3 | [![Release](https://jitpack.io/v/codeclubab/WurmModServerApi.svg)](https://jitpack.io/#codeclubab/WurmModServerApi) 4 | [![Build Status](https://travis-ci.org/codeclubab/WurmModServerApi.svg?branch=master)](https://travis-ci.org/codeclubab/WurmModServerApi) 5 | 6 | Java API for Wurm Unlimited, allowing programmers to create and edit their own WU maps of any kind and size. 7 | 8 | WurmAPI preparation is very simple - just clone repository, open it in your favourite IDE, add "lib" folder as libraries path and done! 9 | -------------------------------------------------------------------------------- /src/main/java/com/wurmonline/wurmapi/api/map/dump/Colorist.java: -------------------------------------------------------------------------------- 1 | package com.wurmonline.wurmapi.api.map.dump; 2 | 3 | import com.wurmonline.mesh.GrassData; 4 | import com.wurmonline.mesh.Tiles.Tile; 5 | import com.wurmonline.mesh.TreeData; 6 | 7 | import java.awt.*; 8 | 9 | /** 10 | * Provides colour information for map dumps. 11 | */ 12 | public interface Colorist { 13 | Color getFlowerColorFor(GrassData.FlowerType flowerType); 14 | 15 | Color getFlowerColorFor(int meshEncodedTile); 16 | 17 | Color getTreeColorFor(TreeData.TreeType treeType); 18 | 19 | Color getSurfaceColorFor(Tile tile); 20 | 21 | Color getCaveColorFor(Tile tile); 22 | 23 | /** 24 | * Color used for an unknown tile type on the surface. 25 | * @return Color for unknown tile type. 26 | */ 27 | Color getSurfaceUnknownColor(); 28 | 29 | /** 30 | * @return Color for unknown underground tile type. 31 | */ 32 | Color getCaveUnknownColor(); 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/wurmonline/wurmapi/internal/CaveColors.java: -------------------------------------------------------------------------------- 1 | package com.wurmonline.wurmapi.internal; 2 | 3 | import com.wurmonline.mesh.Tiles.Tile; 4 | import java.awt.Color; 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | @Deprecated 9 | public class CaveColors { 10 | 11 | private static final Map mappings = new HashMap<>(); 12 | 13 | static { 14 | addMapping(Tile.TILE_CAVE_WALL, Color.DARK_GRAY); 15 | addMapping(Tile.TILE_CAVE_WALL_REINFORCED, Color.DARK_GRAY); 16 | addMapping(Tile.TILE_CAVE, Color.PINK); 17 | addMapping(Tile.TILE_CAVE_FLOOR_REINFORCED, Color.PINK); 18 | addMapping(Tile.TILE_CAVE_EXIT, Color.PINK); 19 | addMapping(Tile.TILE_CAVE_WALL_ORE_IRON, Color.RED.darker()); 20 | addMapping(Tile.TILE_CAVE_WALL_LAVA, Color.RED); 21 | addMapping(Tile.TILE_CAVE_WALL_ORE_COPPER, Color.GREEN); 22 | addMapping(Tile.TILE_CAVE_WALL_ORE_TIN, Color.GRAY); 23 | addMapping(Tile.TILE_CAVE_WALL_ORE_GOLD, Color.YELLOW.darker()); 24 | addMapping(Tile.TILE_CAVE_WALL_ORE_ADAMANTINE, Color.CYAN); 25 | addMapping(Tile.TILE_CAVE_WALL_ORE_GLIMMERSTEEL, Color.YELLOW.brighter()); 26 | addMapping(Tile.TILE_CAVE_WALL_ORE_SILVER, Color.LIGHT_GRAY); 27 | addMapping(Tile.TILE_CAVE_WALL_ORE_LEAD, Color.PINK.darker().darker()); 28 | addMapping(Tile.TILE_CAVE_WALL_ORE_ZINC, new Color(235, 235, 235)); 29 | addMapping(Tile.TILE_CAVE_WALL_SLATE, Color.BLACK); 30 | addMapping(Tile.TILE_CAVE_WALL_MARBLE, Color.WHITE); 31 | } 32 | 33 | private static void addMapping(Tile tile, Color color) { 34 | mappings.put(tile, color); 35 | } 36 | 37 | public static Color getColorFor(Tile tile) { 38 | return mappings.getOrDefault(tile, Color.PINK); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.wurmonline 8 | WurmModServerApi 9 | 1.4.0.1 10 | 11 | 1.8 12 | 1.8 13 | 14 | 15 | 16 | 17 | org.apache.maven.plugins 18 | maven-install-plugin 19 | 2.5.2 20 | 21 | 22 | install:com.wurmonline.common 23 | validate 24 | 25 | install-file 26 | 27 | 28 | lib/common-1.4.0.1.jar 29 | com.wurmonline 30 | common 31 | 1.4.0.1 32 | jar 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | com.wurmonline 42 | common 43 | 1.4.0.1 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /src/main/java/com/wurmonline/wurmapi/api/WurmAPI.java: -------------------------------------------------------------------------------- 1 | package com.wurmonline.wurmapi.api; 2 | 3 | import com.wurmonline.wurmapi.api.map.dump.Colorist; 4 | 5 | import java.io.File; 6 | import java.io.IOException; 7 | 8 | public class WurmAPI { 9 | 10 | /** 11 | * Creates new WurmAPI instance. This method must be used on existing and valid world directory. 12 | * 13 | * @param worldDirectory path to existing world directory. 14 | * @return WurmAPI instance 15 | */ 16 | public static WurmAPI open(String worldDirectory) throws IOException { 17 | return new WurmAPI(worldDirectory); 18 | } 19 | 20 | /** 21 | * Creates new WurmAPI instance. 22 | * 23 | * @param worldDirectory path to new or existing world directory. 24 | * @param powerOfTwo power of two of new map (must be between 10 and 15) 25 | * @return WurmAPI instance 26 | */ 27 | public static WurmAPI create(String worldDirectory, int powerOfTwo) throws IOException { 28 | return new WurmAPI(worldDirectory, powerOfTwo); 29 | } 30 | 31 | private final String rootDir; 32 | private final MapData mapData; 33 | 34 | private WurmAPI(String worldDirectory) throws IOException { 35 | this.rootDir = worldDirectory + File.separator; 36 | File file = new File(rootDir); 37 | file.mkdirs(); 38 | 39 | this.mapData = new MapData(rootDir); 40 | } 41 | 42 | private WurmAPI(String worldDirectory, int powerOfTwo) throws IOException { 43 | if (powerOfTwo < 10 || powerOfTwo > 15) { 44 | throw new IllegalArgumentException("Invalid map size: map with size 2^" + powerOfTwo + " cannot be created"); 45 | } 46 | 47 | this.rootDir = worldDirectory + File.separator; 48 | File file = new File(rootDir); 49 | file.mkdirs(); 50 | 51 | this.mapData = new MapData(rootDir, powerOfTwo); 52 | } 53 | 54 | public MapData getMapData() { 55 | return mapData; 56 | } 57 | 58 | /** 59 | * Releases all native resources used by WurmAPI. It shouldn't be used after calling this method. 60 | */ 61 | public void close() { 62 | mapData.close(); 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/com/wurmonline/wurmapi/api/map/dump/DefaultColorist.java: -------------------------------------------------------------------------------- 1 | package com.wurmonline.wurmapi.api.map.dump; 2 | 3 | import com.wurmonline.mesh.GrassData; 4 | import com.wurmonline.mesh.Tiles; 5 | import com.wurmonline.mesh.TreeData; 6 | 7 | import java.awt.*; 8 | import java.util.Collections; 9 | import java.util.HashMap; 10 | import java.util.Map; 11 | 12 | /** 13 | * Provides default colours for a map dump. 14 | */ 15 | public class DefaultColorist implements Colorist { 16 | private static final Color PURPLE = new Color(108, 46, 153); 17 | private static final Color YELLOW_GREEN = new Color(200, 230, 0); 18 | 19 | private static final Map FLOWER_COLOR; 20 | private static final Color FLOWER_COLOR_UNKNOWN = Tiles.Tile.TILE_GRASS.getColor(); 21 | static { 22 | Map flowerColour = new HashMap<>(); 23 | flowerColour.put(GrassData.FlowerType.FLOWER_1, Color.YELLOW); 24 | flowerColour.put(GrassData.FlowerType.FLOWER_2, Color.ORANGE); 25 | flowerColour.put(GrassData.FlowerType.FLOWER_3, PURPLE); 26 | flowerColour.put(GrassData.FlowerType.FLOWER_4, Color.WHITE); 27 | flowerColour.put(GrassData.FlowerType.FLOWER_5, Color.BLUE); 28 | flowerColour.put(GrassData.FlowerType.FLOWER_6, YELLOW_GREEN); 29 | flowerColour.put(GrassData.FlowerType.FLOWER_7, Color.PINK); 30 | flowerColour.put(GrassData.FlowerType.NONE, FLOWER_COLOR_UNKNOWN); 31 | FLOWER_COLOR = Collections.unmodifiableMap(flowerColour); 32 | } 33 | 34 | private static final Map TREE_COLOR; 35 | private static final Color TREE_COLOR_UNKNOWN = Tiles.Tile.TILE_GRASS.getColor(); 36 | static { 37 | // Some colours will be similar to the material but some will not, to aid in differentiating tree types. 38 | Map treeColour = new HashMap<>(); 39 | treeColour.put(TreeData.TreeType.BIRCH, Color.WHITE); 40 | treeColour.put(TreeData.TreeType.PINE, Color.YELLOW.brighter()); 41 | treeColour.put(TreeData.TreeType.OAK, Color.BLACK); 42 | treeColour.put(TreeData.TreeType.CEDAR, Color.CYAN); 43 | treeColour.put(TreeData.TreeType.WILLOW, Color.DARK_GRAY); 44 | treeColour.put(TreeData.TreeType.MAPLE, PURPLE); 45 | treeColour.put(TreeData.TreeType.APPLE, YELLOW_GREEN); 46 | treeColour.put(TreeData.TreeType.LEMON, Color.YELLOW); 47 | treeColour.put(TreeData.TreeType.OLIVE, new Color(80, 80, 0)); 48 | treeColour.put(TreeData.TreeType.CHERRY, Color.RED); 49 | treeColour.put(TreeData.TreeType.CHESTNUT, Color.LIGHT_GRAY); 50 | treeColour.put(TreeData.TreeType.WALNUT, Color.ORANGE); 51 | treeColour.put(TreeData.TreeType.FIR, Color.GREEN.darker()); 52 | treeColour.put(TreeData.TreeType.LINDEN, Color.PINK); 53 | treeColour.put(TreeData.TreeType.ORANGE, Color.ORANGE.brighter()); 54 | TREE_COLOR = Collections.unmodifiableMap(treeColour); 55 | } 56 | 57 | private static final Map CAVE_COLORS; 58 | private static final Color CAVE_COLOR_UNKNOWN = Color.PINK; 59 | static { 60 | Map caveColors = new HashMap<>(); 61 | caveColors.put(Tiles.Tile.TILE_CAVE_WALL, Color.DARK_GRAY); 62 | caveColors.put(Tiles.Tile.TILE_CAVE_WALL_REINFORCED, Color.DARK_GRAY); 63 | caveColors.put(Tiles.Tile.TILE_CAVE, Color.PINK); 64 | caveColors.put(Tiles.Tile.TILE_CAVE_FLOOR_REINFORCED, Color.PINK); 65 | caveColors.put(Tiles.Tile.TILE_CAVE_EXIT, Color.PINK); 66 | caveColors.put(Tiles.Tile.TILE_CAVE_WALL_ORE_IRON, Color.RED.darker()); 67 | caveColors.put(Tiles.Tile.TILE_CAVE_WALL_LAVA, Color.RED); 68 | caveColors.put(Tiles.Tile.TILE_CAVE_WALL_ORE_COPPER, Color.GREEN); 69 | caveColors.put(Tiles.Tile.TILE_CAVE_WALL_ORE_TIN, Color.GRAY); 70 | caveColors.put(Tiles.Tile.TILE_CAVE_WALL_ORE_GOLD, Color.YELLOW.darker()); 71 | caveColors.put(Tiles.Tile.TILE_CAVE_WALL_ORE_ADAMANTINE, Color.CYAN); 72 | caveColors.put(Tiles.Tile.TILE_CAVE_WALL_ORE_GLIMMERSTEEL, Color.YELLOW.brighter()); 73 | caveColors.put(Tiles.Tile.TILE_CAVE_WALL_ORE_SILVER, Color.LIGHT_GRAY); 74 | caveColors.put(Tiles.Tile.TILE_CAVE_WALL_ORE_LEAD, Color.PINK.darker().darker()); 75 | caveColors.put(Tiles.Tile.TILE_CAVE_WALL_ORE_ZINC, new Color(235, 235, 235)); 76 | caveColors.put(Tiles.Tile.TILE_CAVE_WALL_SLATE, Color.BLACK); 77 | caveColors.put(Tiles.Tile.TILE_CAVE_WALL_MARBLE, Color.WHITE); 78 | caveColors.put(Tiles.Tile.TILE_CAVE_WALL_ROCKSALT, Color.CYAN.brighter()); 79 | caveColors.put(Tiles.Tile.TILE_CAVE_WALL_SANDSTONE, Color.ORANGE.darker()); 80 | CAVE_COLORS = Collections.unmodifiableMap(caveColors); 81 | } 82 | 83 | private static final Color SURFACE_COLOR_UNKNOWN = Tiles.Tile.TILE_DIRT.getColor(); 84 | 85 | @Override 86 | public Color getFlowerColorFor(GrassData.FlowerType flowerType) { 87 | return FLOWER_COLOR.getOrDefault(flowerType, FLOWER_COLOR_UNKNOWN); 88 | } 89 | 90 | @Override 91 | public Color getFlowerColorFor(int meshEncodedTile) { 92 | byte grassData = Tiles.decodeData(meshEncodedTile); 93 | return getFlowerColorFor(GrassData.FlowerType.decodeTileData(grassData)); 94 | } 95 | 96 | @Override 97 | public Color getTreeColorFor(TreeData.TreeType treeType) { 98 | return TREE_COLOR.getOrDefault(treeType, TREE_COLOR_UNKNOWN); 99 | } 100 | 101 | @Override 102 | public Color getSurfaceColorFor(Tiles.Tile tile) { 103 | return tile.getColor(); 104 | } 105 | 106 | @Override 107 | public Color getCaveColorFor(Tiles.Tile tile) { 108 | return CAVE_COLORS.getOrDefault(tile, CAVE_COLOR_UNKNOWN); 109 | } 110 | 111 | /** 112 | * @return Color for unknown surface tile type. 113 | */ 114 | @Override 115 | public Color getSurfaceUnknownColor() { 116 | return SURFACE_COLOR_UNKNOWN; 117 | } 118 | 119 | @Override 120 | public Color getCaveUnknownColor() { 121 | return CAVE_COLOR_UNKNOWN; 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/main/java/com/wurmonline/wurmapi/api/MapData.java: -------------------------------------------------------------------------------- 1 | package com.wurmonline.wurmapi.api; 2 | 3 | import com.wurmonline.mesh.BushData.BushType; 4 | import com.wurmonline.mesh.FoliageAge; 5 | import com.wurmonline.mesh.GrassData; 6 | import com.wurmonline.mesh.MeshIO; 7 | import com.wurmonline.mesh.Tiles; 8 | import com.wurmonline.mesh.Tiles.Tile; 9 | import com.wurmonline.mesh.TreeData.TreeType; 10 | import com.wurmonline.wurmapi.api.map.dump.Colorist; 11 | import com.wurmonline.wurmapi.api.map.dump.DefaultColorist; 12 | 13 | import java.awt.Color; 14 | import java.awt.image.BufferedImage; 15 | import java.io.File; 16 | import java.io.IOException; 17 | import java.util.Random; 18 | import java.util.logging.Level; 19 | import java.util.logging.Logger; 20 | 21 | public final class MapData { 22 | 23 | private static final float MAP_HEIGHT = 1000; 24 | 25 | public static final int DEFAULT_ROCK_RESOURCE_COUNT = 51; 26 | 27 | public static final int DEFAULT_ORE_RESOURCE_COUNT = 1000; 28 | 29 | private final MeshIO surfaceMesh; 30 | private final MeshIO rockMesh; 31 | private final MeshIO flagsMesh; 32 | private final MeshIO caveMesh; 33 | private final MeshIO resourcesMesh; 34 | private final MeshIO[] allMeshes; 35 | 36 | MapData(String root) throws IOException { 37 | this.surfaceMesh = MeshIO.open(root + "top_layer.map"); 38 | this.rockMesh = MeshIO.open(root + "rock_layer.map"); 39 | this.flagsMesh = MeshIO.open(root + "flags.map"); 40 | this.caveMesh = MeshIO.open(root + "map_cave.map"); 41 | this.resourcesMesh = MeshIO.open(root + "resources.map"); 42 | allMeshes = new MeshIO[] {surfaceMesh, rockMesh, flagsMesh, caveMesh, resourcesMesh}; 43 | } 44 | 45 | MapData(String root, int powerOfTwo) throws IOException { 46 | this.surfaceMesh = createMap(root + "top_layer.map", powerOfTwo); 47 | this.rockMesh = createMap(root + "rock_layer.map", powerOfTwo); 48 | this.flagsMesh = createMap(root + "flags.map", powerOfTwo); 49 | this.caveMesh = createMap(root + "map_cave.map", powerOfTwo); 50 | this.resourcesMesh = createMap(root + "resources.map", powerOfTwo); 51 | allMeshes = new MeshIO[] {surfaceMesh, rockMesh, flagsMesh, caveMesh, resourcesMesh}; 52 | 53 | int halfWidth = getWidth() / 2; 54 | int halfHeight = getHeight() / 2; 55 | 56 | for (int i = 0; i < getWidth(); i++) { 57 | for (int i2 = 0; i2 < getHeight(); i2++) { 58 | setCaveTile(i, i2, Tile.TILE_CAVE_WALL); 59 | 60 | int distToEdge = Math.min(halfWidth - Math.abs(i - halfWidth), halfHeight - Math.abs(i2 - halfHeight)); 61 | if (distToEdge > 5) { 62 | setRockHeight(i, i2, (short) (95)); 63 | setSurfaceTile(i, i2, Tile.TILE_DIRT, (short) (100)); 64 | } 65 | else { 66 | setRockHeight(i, i2, (short) (-200)); 67 | setSurfaceTile(i, i2, Tile.TILE_DIRT, (short) (-100)); 68 | } 69 | } 70 | } 71 | } 72 | 73 | private MeshIO createMap(String dir, int powerOfTwo) throws IOException { 74 | File file = new File(dir); 75 | if (file.exists()) { 76 | file.delete(); 77 | file.createNewFile(); 78 | } 79 | else { 80 | file.createNewFile(); 81 | } 82 | 83 | int realSize = 1 << powerOfTwo; 84 | int[] data = new int[realSize * realSize]; 85 | 86 | return MeshIO.createMap(dir, powerOfTwo, data); 87 | } 88 | 89 | /** 90 | * Currently map width and height are always equal, but two methods exists in case if this will change in a future. 91 | * 92 | * @return map width 93 | */ 94 | public int getWidth() { 95 | return surfaceMesh.getSize(); 96 | } 97 | 98 | /** 99 | * Currently map width and height are always equal, but two methods exists in case if this will change in a future. 100 | * 101 | * @return map height 102 | */ 103 | public int getHeight() { 104 | return surfaceMesh.getSize(); 105 | } 106 | 107 | /** 108 | * 109 | * @param x x location in game world. 110 | * @param y y location in game world. 111 | * @return surface tile type in location. 112 | */ 113 | public Tile getSurfaceTile(int x, int y) { 114 | return Tiles.getTile(Tiles.decodeType(surfaceMesh.getTile(x, y))); 115 | } 116 | 117 | /** 118 | * 119 | * @param x x location in game world. 120 | * @param y y location in game world. 121 | * @return surface tile height in location. 122 | */ 123 | public short getSurfaceHeight(int x, int y) { 124 | return Tiles.decodeHeight(surfaceMesh.getTile(x, y)); 125 | } 126 | 127 | private byte getSurfaceData(int x, int y) { 128 | // internal method, I don't think it should be exposed 129 | return Tiles.decodeData(surfaceMesh.getTile(x, y)); 130 | } 131 | 132 | /** 133 | * @param x x location in game world. 134 | * @param y y location in game world. 135 | * @return height of dirt layer in location, or 0 if negative (rock layer being higher than surface layer). 136 | */ 137 | public short getDirtLayerHeight(int x, int y) { 138 | short dirtHeight = (short) (getSurfaceHeight(x, y) - getRockHeight(x, y)); 139 | return dirtHeight >= 0 ? dirtHeight : 0; 140 | } 141 | 142 | /** 143 | * This method changes height of surface layer without changing any other data. 144 | * 145 | * @param x x location in game world. 146 | * @param y y location in game world. 147 | * @param height height of tile, be careful with very high or very low values as both seem to cause server to crash. 148 | */ 149 | public void setSurfaceHeight(int x, int y, short height) { 150 | Tile type = getSurfaceTile(x, y); 151 | byte data = getSurfaceData(x, y); 152 | 153 | setSurfaceTile(x, y, type, height, data); 154 | } 155 | 156 | /** 157 | * Sets tile data on a surface.
158 | * Please see {@link #setSurfaceTile(int, int, com.wurmonline.mesh.Tiles.Tile, short) this method} for more details. 159 | * 160 | * @param x x location in game world. 161 | * @param y y location in game world. 162 | * @param tileType type of tile. Using cave, trees and bushes constants is not allowed. 163 | */ 164 | public void setSurfaceTile(int x, int y, Tile tileType) { 165 | setSurfaceTile(x, y, tileType, getSurfaceHeight(x, y)); 166 | } 167 | 168 | /** 169 | * Sets tile data on a surface.
170 | * While it is possible to place practically everything using this method, it is recommended to set things like trees and bushes using specialized methods instead of this one.

171 | * 172 | * If tile is on lower height than rock layer height at the moment of saving map to file, it will be automatically set to rock layer height.
173 | * If the whole tile is exposed (rock level equal to surface level in all four corners), its type will be changed to rock on map save.
174 | * This method will NOT prevent doing illogical things like placing trees or grass under water.

175 | * 176 | * Under the hood, tile data is 32-bit value, where:
177 | * Bits 1-8 : tile type data
178 | * Bits 9-16 : additional data (used for example by trees, bushes and grass, varies from tile type to tile type, this method is not using these bits)
179 | * Bits 17-32 : height data (as signed short value) 180 | * 181 | * @param x x location in game world. 182 | * @param y y location in game world. 183 | * @param height height of tile, be careful with very high or very low values as both seem to cause server to crash. 184 | * @param tileType type of tile. Using cave, trees and bushes constants is not allowed. 185 | * 186 | * @see #setTree(int, int, com.wurmonline.mesh.TreeData.TreeType, com.wurmonline.mesh.FoliageAge, com.wurmonline.mesh.GrassData.GrowthTreeStage) 187 | * @see #setBush(int, int, com.wurmonline.mesh.BushData.BushType, com.wurmonline.mesh.FoliageAge, com.wurmonline.mesh.GrassData.GrowthTreeStage) 188 | */ 189 | public void setSurfaceTile(int x, int y, Tile tileType, short height) { 190 | if (tileType == null) { 191 | throw new IllegalArgumentException("Tile type is null"); 192 | } 193 | else if (tileType.isCave()) { 194 | throw new IllegalArgumentException("Tile type is not surface type: "+tileType.toString()); 195 | } 196 | else if (tileType.isTree() || tileType.isBush()) { 197 | throw new IllegalArgumentException("Tile type is tree or bush: please use specialized methods to put them on map instead."); 198 | } 199 | 200 | setSurfaceTile(x, y, tileType, height, (byte) 0); 201 | } 202 | 203 | private void setSurfaceTile(int x, int y, Tile tileType, short height, byte data) { 204 | if (tileType == null) { 205 | tileType = Tile.TILE_DIRT; 206 | } 207 | 208 | surfaceMesh.setTile(x, y, Tiles.encode(height, (byte) tileType.getId(), data)); 209 | } 210 | 211 | /** 212 | * Places tree in specified position in game world.

213 | * 214 | * Placing tree should happen after calling {@link #setSurfaceTile(int, int, Tile, short) setSurfaceTile}, 215 | * as it is using data from a tile (height and grass type) to generate the final tree data.
216 | * This method will NOT prevent doing illogical things like placing trees under water.

217 | * 218 | * Under the hood, 1 byte of tree special data is composed of these values:
219 | * Bits 1-2 : growth stage of grass
220 | * Bit 3 : tree position (center or any)
221 | * Bit 4 : contains fruit
222 | * Bits 5-8 : age of tree (trees with sprouts count as stage of growth as well) 223 | * 224 | * @param x x location in game world. 225 | * @param y y location in game world. 226 | * @param treeType type of tree 227 | * @param age age of tree 228 | * @param grassStage stage of grass growth on tree tile. It replaces grass height of normal grass tile if it was previously set. 229 | */ 230 | public void setTree(int x, int y, TreeType treeType, FoliageAge age, GrassData.GrowthTreeStage grassStage) { 231 | if (treeType == null) { 232 | throw new IllegalArgumentException("Tree type is null"); 233 | } 234 | else if (age == null) { 235 | throw new IllegalArgumentException("Foliage age is null"); 236 | } 237 | else if (grassStage == null) { 238 | throw new IllegalArgumentException("Grass stage is null"); 239 | } 240 | 241 | byte currentType = Tiles.decodeType(surfaceMesh.getTile(x, y)); 242 | byte resultType; 243 | if (currentType == Tiles.TILE_TYPE_MYCELIUM) { 244 | resultType = treeType.asMyceliumTree(); 245 | } 246 | else if (currentType == Tiles.TILE_TYPE_ENCHANTED_GRASS) { 247 | resultType = treeType.asEnchantedTree(); 248 | } 249 | else { 250 | resultType = treeType.asNormalTree(); 251 | } 252 | 253 | setFoliage(x, y, resultType, age, grassStage); 254 | } 255 | 256 | /** 257 | * Places bush in specified position in game world.

258 | * 259 | * Placing bush should happen after calling {@link #setSurfaceTile(int, int, Tile, short) setSurfaceTile}, as it is 260 | * using data from a tile (height and grass type) to generate the final bush data.
261 | * This method will NOT prevent doing illogical things like placing grass under water.

262 | * 263 | * Under the hood, 1 byte of bush special data is composed in the same way as tree data: {@link #setTree(int, int, com.wurmonline.mesh.TreeData.TreeType, com.wurmonline.mesh.FoliageAge, com.wurmonline.mesh.GrassData.GrowthTreeStage) setTree}. 264 | * 265 | * @param x x location in game world. 266 | * @param y y location in game world. 267 | * @param bushType type of bush 268 | * @param age age of bush 269 | * @param grassStage stage of grass growth on bush tile. It replaces grass height of normal grass tile if it was previously set. 270 | */ 271 | public void setBush(int x, int y, BushType bushType, FoliageAge age, GrassData.GrowthTreeStage grassStage) { 272 | if (bushType == null) { 273 | throw new IllegalArgumentException("Bush type is null"); 274 | } 275 | else if (age == null) { 276 | throw new IllegalArgumentException("Foliage age is null"); 277 | } 278 | else if (grassStage == null) { 279 | throw new IllegalArgumentException("Grass stage is null"); 280 | } 281 | 282 | byte currentType = Tiles.decodeType(surfaceMesh.getTile(x, y)); 283 | byte resultType; 284 | if (currentType == Tiles.TILE_TYPE_MYCELIUM) { 285 | resultType = bushType.asMyceliumBush(); 286 | } 287 | else if (currentType == Tiles.TILE_TYPE_ENCHANTED_GRASS) { 288 | resultType = bushType.asEnchantedBush(); 289 | } 290 | else { 291 | resultType = bushType.asNormalBush(); 292 | } 293 | 294 | setFoliage(x, y, resultType, age, grassStage); 295 | } 296 | 297 | private void setFoliage(int x, int y, byte foliageType, FoliageAge age, GrassData.GrowthTreeStage grassStage) { 298 | int data = surfaceMesh.getTile(x, y); 299 | byte resultData = Tiles.encodeTreeData(age, false, false, grassStage); 300 | short currentHeight = Tiles.decodeHeight(data); 301 | 302 | surfaceMesh.setTile(x, y, Tiles.encode(currentHeight, foliageType, resultData)); 303 | } 304 | 305 | /** 306 | * Sets grass data on given grass, mycelium, kelp or reed tile (for any other types of tiles it does nothing).

307 | * 308 | * Flower type can be set ONLY for grass tiles - in other cases method will return without doing anything. 309 | * 310 | * @param x x location in game world. 311 | * @param y y location in game world. 312 | * @param grassStage growth stage of grass/kelp/reed 313 | * @param flower type of flower (must be NONE for tiles other than grass) 314 | */ 315 | public void setGrass(int x, int y, GrassData.GrowthStage grassStage, GrassData.FlowerType flower) { 316 | if (grassStage == null) { 317 | throw new IllegalArgumentException("Grass stage is null"); 318 | } 319 | else if (flower == null) { 320 | flower = GrassData.FlowerType.NONE; 321 | } 322 | 323 | int currentType = Tiles.decodeType(surfaceMesh.getTile(x, y)); 324 | GrassData.GrassType grassType; 325 | if (currentType == Tiles.TILE_TYPE_GRASS) { 326 | grassType = GrassData.GrassType.GRASS; 327 | } 328 | else if (currentType == Tiles.TILE_TYPE_MYCELIUM) { 329 | grassType = GrassData.GrassType.GRASS; 330 | } 331 | else if (currentType == Tiles.TILE_TYPE_KELP) { 332 | grassType = GrassData.GrassType.KELP; 333 | } 334 | else if (currentType == Tiles.TILE_TYPE_REED) { 335 | grassType = GrassData.GrassType.REED; 336 | } 337 | else { 338 | return; 339 | } 340 | 341 | if (currentType != Tiles.TILE_TYPE_GRASS && flower != GrassData.FlowerType.NONE) { 342 | return; 343 | } 344 | 345 | short currentHeight = Tiles.decodeHeight(surfaceMesh.getTile(x, y)); 346 | surfaceMesh.setTile(x, y, Tiles.encode(currentHeight,(byte) currentType, GrassData.encodeGrassTileData(grassStage, grassType, flower))); 347 | } 348 | 349 | public short getRockHeight(int x, int y) { 350 | return Tiles.decodeHeight(rockMesh.getTile(x, y)); 351 | } 352 | 353 | /** 354 | * Sets height of rock layer at location.

355 | * 356 | * Under the hood, rock layer is almost identical to surface layer - this method sets rock as tile type, ignores tile special data and sets desired height. 357 | * 358 | * @param x x location in game world. 359 | * @param y y location in game world. 360 | * @param height height of tile, be careful with very high or very low values as both seem to cause server to crash. 361 | */ 362 | public void setRockHeight(int x, int y, short height) { 363 | rockMesh.setTile(x, y, Tiles.encode(height, (byte) Tiles.TILE_TYPE_ROCK, (byte) 0)); 364 | } 365 | 366 | /** 367 | * 368 | * @param x x location in game world. 369 | * @param y y location in game world. 370 | * @return cave tile type in location. 371 | */ 372 | public Tile getCaveTile(int x, int y) { 373 | return Tiles.getTile(Tiles.decodeType(caveMesh.getTile(x, y))); 374 | } 375 | 376 | /** 377 | * Sets tile data inside cave.
378 | * Only solid cave walls are allowed - exception will be thrown on attempt to set non-cave tile type or cave type which is not a wall.
379 | * This method sets default values for resource counts: 51 for rock tiles and 1000 for veins. 380 | * 381 | * @param x x location in game world. 382 | * @param y y location in game world. 383 | * @param tileType type of tile. Only cave walls constants are allowed. 384 | */ 385 | public void setCaveTile(int x, int y, Tile tileType) { 386 | if (tileType == Tile.TILE_CAVE_WALL) { 387 | setCaveTile(x, y, tileType, (short) DEFAULT_ROCK_RESOURCE_COUNT); 388 | } 389 | else { 390 | setCaveTile(x, y, tileType, (short) DEFAULT_ORE_RESOURCE_COUNT); 391 | } 392 | } 393 | 394 | /** 395 | * Sets tile data inside cave.
396 | * Only solid cave walls are allowed - exception will be thrown on attempt to set non-cave tile type or cave type which is not a wall. 397 | * 398 | * @param x x location in game world. 399 | * @param y y location in game world. 400 | * @param tileType type of tile. Only cave walls constants are allowed. 401 | * @param resourceCount number of mining actions needed to deplete vein. Must be higher than 0. 402 | */ 403 | public void setCaveTile(int x, int y, Tile tileType, short resourceCount) { 404 | if (tileType == null || !tileType.isCave()) { 405 | throw new IllegalArgumentException("Tile type is null"); 406 | } 407 | else if (!tileType.isCave()) { 408 | throw new IllegalArgumentException("Tile type is not cave type: "+tileType.toString()); 409 | } 410 | else if (!tileType.isSolidCave()) { 411 | throw new IllegalArgumentException("Tile type is invalid cave type: "+tileType.toString()); 412 | } 413 | 414 | setCaveResourceCount(x, y, resourceCount); 415 | setCaveTile(x, y, tileType, (short) -100, (byte) 0); 416 | } 417 | 418 | public void setCaveTile(int x, int y, Tile tileType, short height, byte data) { 419 | caveMesh.setTile(x, y, Tiles.encode(height, tileType.getId(), data)); 420 | } 421 | 422 | /** 423 | * @param x x location in game world. 424 | * @param y y location in game world. 425 | * @return number of mining actions needed to deplete vein. 426 | */ 427 | public short getCaveResourceCount(int x, int y) { 428 | final int value = resourcesMesh.getTile(x, y); 429 | final int toReturn = (value >> 16) & 0xFFFF; 430 | return (short) toReturn; 431 | } 432 | 433 | /** 434 | * Sets number of mining actions needed to deplete vein in current location (rock tiles count as veins as well). 435 | * 436 | * @param x x location in game world. 437 | * @param y y location in game world. 438 | * @param resourceCount number of mining actions needed to deplete vein. Must be higher than 0. 439 | */ 440 | public void setCaveResourceCount(int x, int y, short resourceCount) { 441 | if (resourceCount <= 0) { 442 | throw new IllegalArgumentException("Invalid amount of resources in cave tile: "+resourceCount+", must be higher than 0"); 443 | } 444 | 445 | final int value = resourcesMesh.getTile(x, y); 446 | resourcesMesh.setTile(x, y, ((resourceCount & 0xFFFF) << 16) + (value & 0xFFFF)); 447 | } 448 | 449 | /** 450 | * Creates classical Wurm Online map dump, with semi-3d terrain.
451 | * You don't need to save map first to create updated map dump - it is using data from memory. 452 | * 453 | * @return map image 454 | */ 455 | public BufferedImage createMapDump() { 456 | return createMapDump(surfaceMesh.getSizeLevel()); 457 | } 458 | 459 | /** 460 | * Creates classical Wurm Online map dump, with semi-3d terrain.
461 | * You don't need to save map first to create updated map dump - it is using data from memory.
462 | * Using this method, you can create downscaled version of image (for example, if you want to take a quick look at map without creating full sized preview). 463 | * 464 | * @param desiredPowerOfTwo desired output image power of two, or map power of two if it is lower than this value. Must be bigger than 7. 465 | * @return map image 466 | */ 467 | public BufferedImage createMapDump(int desiredPowerOfTwo) { 468 | if (desiredPowerOfTwo < 7) { 469 | throw new IllegalArgumentException("Desired power of two is smaller than 7."); 470 | } 471 | final int scale = Math.min(desiredPowerOfTwo, surfaceMesh.getSizeLevel()); 472 | final int scaleDiff = surfaceMesh.getSizeLevel() - scale; 473 | 474 | int downscalePower = 1 << scaleDiff; 475 | final int lWidth = getWidth(); 476 | final int downWidth = getWidth() / downscalePower; 477 | 478 | final BufferedImage bi2 = new BufferedImage(downWidth, downWidth, BufferedImage.TYPE_INT_RGB); 479 | final float[] data = new float[downWidth * downWidth * 3]; 480 | 481 | for (int x = 0; x < lWidth; x += downscalePower) { 482 | int alt = downWidth - 1; 483 | for (int y = lWidth - 1; y >= 0; y -= downscalePower) { 484 | float node = (float) (getSurfaceHeight(x, y) / (Short.MAX_VALUE / 3.3f)); 485 | float node2 = x == lWidth - 1 || y == lWidth - 1 ? node : (float) (getSurfaceHeight(x + downscalePower, y + downscalePower) / (Short.MAX_VALUE / 3.3f)); 486 | 487 | final byte tex = Tiles.decodeType(surfaceMesh.getTile(x, y)); 488 | 489 | final float hh = node; 490 | 491 | float h = ((node2 - node) * 1500) / 256.0f * downWidth / 128 + hh / 2 + 1.0f; 492 | h *= 0.4f; 493 | 494 | float r = h; 495 | float g = h; 496 | float b = h; 497 | 498 | final Tile tile = Tiles.getTile(tex); 499 | final Color color; 500 | if (tile != null) { 501 | color = tile.getColor(); 502 | } 503 | else { 504 | color = Tile.TILE_DIRT.getColor(); 505 | } 506 | r *= (color.getRed() / 255.0f) * 2; 507 | g *= (color.getGreen() / 255.0f) * 2; 508 | b *= (color.getBlue() / 255.0f) * 2; 509 | 510 | if (r < 0) 511 | r = 0; 512 | if (r > 1) 513 | r = 1; 514 | if (g < 0) 515 | g = 0; 516 | if (g > 1) 517 | g = 1; 518 | if (b < 0) 519 | b = 0; 520 | if (b > 1) 521 | b = 1; 522 | 523 | if (node < 0) { 524 | r = r * 0.2f + 0.4f * 0.4f; 525 | g = g * 0.2f + 0.5f * 0.4f; 526 | b = b * 0.2f + 1.0f * 0.4f; 527 | } 528 | 529 | final int altTarget = y / downscalePower - (int) (Tiles.decodeHeight(surfaceMesh.getTile(x, y)) * MAP_HEIGHT / 4 / (Short.MAX_VALUE / 3.3f)) / downscalePower; 530 | while (alt > altTarget && alt >= 0) { 531 | final int coord = (x / downscalePower + alt * downWidth) * 3; 532 | data[coord + 0] = r * 255; 533 | data[coord + 1] = g * 255; 534 | data[coord + 2] = b * 255; 535 | alt--; 536 | } 537 | } 538 | } 539 | 540 | bi2.getRaster().setPixels(0, 0, downWidth, downWidth, data); 541 | return bi2; 542 | } 543 | 544 | /** 545 | * Creates flat map dump, showing all terrain types in different colors.
546 | * You don't need to save map first to create updated map dump - it is using data from memory. 547 | * 548 | * @param showWater set true if you want to make water visible, false otherwise. 549 | * @return map image 550 | */ 551 | public BufferedImage createTerrainDump(boolean showWater) { 552 | return createFlatDump(true, showWater); 553 | } 554 | 555 | public BufferedImage createFlowerDump(boolean showWater) { 556 | return createFlatDump(true, showWater, true, false, new DefaultColorist()); 557 | } 558 | 559 | public BufferedImage createTreeDump(boolean showWater) { 560 | return createFlatDump(true, showWater, false, true, new DefaultColorist()); 561 | } 562 | 563 | /** 564 | * Creates flat map dump, showing all cave terrain types in different colors.
565 | * You don't need to save map first to create updated map dump - it is using data from memory. 566 | * 567 | * @param showWater set true if you want to make water visible, false otherwise. 568 | * @param tiles ore types to show on cave dump (all will be shown if not specified or null) 569 | * @return map image 570 | */ 571 | public BufferedImage createCaveDump(boolean showWater, Tile... tiles) { 572 | return createFlatDump(false, showWater, tiles); 573 | } 574 | 575 | private BufferedImage createFlatDump(boolean isSurface, boolean showWater, Tile... allowedTiles) { 576 | return createFlatDump(isSurface, showWater, false, false, new DefaultColorist(), allowedTiles); 577 | } 578 | 579 | private BufferedImage createFlatDump(boolean isSurface, boolean showWater, boolean showFlowerTypes, 580 | boolean showTreeTypes, Colorist colorist, Tile... allowedTiles) { 581 | final MeshIO terrainMesh; 582 | if (isSurface) { 583 | terrainMesh = surfaceMesh; 584 | } 585 | else { 586 | terrainMesh = caveMesh; 587 | } 588 | 589 | final MeshIO heightMesh; 590 | if (isSurface) { 591 | heightMesh = surfaceMesh; 592 | } 593 | else { 594 | heightMesh = rockMesh; 595 | } 596 | 597 | int lWidth = 16384; 598 | if (lWidth > getWidth()) 599 | lWidth = getWidth(); 600 | int yo = getWidth() - lWidth; 601 | if (yo < 0) 602 | yo = 0; 603 | int xo = getWidth() - lWidth; 604 | if (xo < 0) 605 | xo = 0; 606 | 607 | final Random random = new Random(); 608 | if (xo > 0) 609 | xo = random.nextInt(xo); 610 | if (yo > 0) 611 | yo = random.nextInt(yo); 612 | 613 | final BufferedImage bi2 = new BufferedImage(lWidth, lWidth, BufferedImage.TYPE_INT_RGB); 614 | final float[] data = new float[lWidth * lWidth * 3]; 615 | 616 | for (int x = 0; x < lWidth; x++) { 617 | for (int y = lWidth - 1; y >= 0; y--) { 618 | final short height = Tiles.decodeHeight(heightMesh.getTile(x + xo, y + yo)); 619 | final int encodedTile = terrainMesh.getTile(x + xo, y + yo); 620 | final byte tex = Tiles.decodeType(encodedTile); 621 | final Tile tile = Tiles.getTile(tex); 622 | boolean visible = true; 623 | 624 | if (allowedTiles != null && allowedTiles.length > 0) { 625 | visible = false; 626 | for (int i = 0; i< allowedTiles.length; i++) { 627 | if (allowedTiles[i] == tile) { 628 | visible = true; 629 | break; 630 | } 631 | } 632 | } 633 | 634 | final Color color; 635 | if (tile != null) { 636 | if (isSurface) { 637 | if (tile.isGrass() && showFlowerTypes) { 638 | color = colorist.getFlowerColorFor(encodedTile); 639 | } else if (tile.isTree() && showTreeTypes) { 640 | color = colorist.getTreeColorFor(tile.getTreeType(Tiles.decodeData(encodedTile))); 641 | } else { 642 | color = colorist.getSurfaceColorFor(tile); 643 | } 644 | } 645 | else if (visible) { 646 | color = colorist.getCaveColorFor(tile); 647 | } 648 | else { 649 | color = colorist.getCaveColorFor(Tile.TILE_CAVE_WALL); 650 | } 651 | } 652 | else { 653 | if (isSurface) { 654 | color = colorist.getSurfaceUnknownColor(); 655 | } 656 | else { 657 | color = colorist.getCaveUnknownColor(); 658 | } 659 | } 660 | int r = color.getRed(); 661 | int g = color.getGreen(); 662 | int b = color.getBlue(); 663 | if (height < 0 && showWater) { 664 | r = (int) (r * 0.2f + 0.4f * 0.4f * 256f); 665 | g = (int) (g * 0.2f + 0.5f * 0.4f * 256f); 666 | b = (int) (b * 0.2f + 1.0f * 0.4f * 256f); 667 | } 668 | 669 | data[(x + y * lWidth) * 3 + 0] = r; 670 | data[(x + y * lWidth) * 3 + 1] = g; 671 | data[(x + y * lWidth) * 3 + 2] = b; 672 | } 673 | } 674 | 675 | bi2.getRaster().setPixels(0, 0, lWidth, lWidth, data); 676 | return bi2; 677 | } 678 | 679 | /** 680 | * Creates flat map dump, showing all terrain types in different colors and with contour lines.
681 | * You don't need to save map first to create updated map dump - it is using data from memory. 682 | * 683 | * @param showWater set true if you want to make water visible, false otherwise. 684 | * @param interval interval for next controur line 685 | * @return map image 686 | */ 687 | public BufferedImage createTopographicDump(boolean showWater, short interval) { 688 | int lWidth = 16384; 689 | if (lWidth > getWidth()) 690 | lWidth = getWidth(); 691 | int yo = getWidth() - lWidth; 692 | if (yo < 0) 693 | yo = 0; 694 | int xo = getWidth() - lWidth; 695 | if (xo < 0) 696 | xo = 0; 697 | 698 | final Random random = new Random(); 699 | if (xo > 0) 700 | xo = random.nextInt(xo); 701 | if (yo > 0) 702 | yo = random.nextInt(yo); 703 | 704 | final BufferedImage bi2 = new BufferedImage(lWidth, lWidth, BufferedImage.TYPE_INT_RGB); 705 | final float[] data = new float[lWidth * lWidth * 3]; 706 | 707 | for (int x = 0; x < lWidth; x++) { 708 | for (int y = lWidth - 1; y >= 0; y--) { 709 | final short height = Tiles.decodeHeight(surfaceMesh.getTile(x + xo, y + yo)); 710 | final short nearHeightNX = x == 0 ? height : Tiles.decodeHeight(surfaceMesh.getTile(x + xo - 1, y + yo)); 711 | final short nearHeightNY = y == 0 ? height : Tiles.decodeHeight(surfaceMesh.getTile(x + xo, y + yo - 1)); 712 | final short nearHeightX = x == lWidth - 1 ? height : Tiles.decodeHeight(surfaceMesh.getTile(x + xo + 1, y + yo)); 713 | final short nearHeightY = y == lWidth - 1 ? height : Tiles.decodeHeight(surfaceMesh.getTile(x + xo, y + yo + 1)); 714 | boolean isControur = checkContourLine(height, nearHeightNX, interval) || checkContourLine(height, nearHeightNY, interval) || checkContourLine(height, nearHeightX, interval) || checkContourLine(height, nearHeightY, interval); 715 | 716 | final byte tex = Tiles.decodeType(surfaceMesh.getTile(x + xo, y + yo)); 717 | 718 | final Tile tile = Tiles.getTile(tex); 719 | final Color color; 720 | if (tile != null) { 721 | color = tile.getColor(); 722 | } 723 | else { 724 | color = Tile.TILE_DIRT.getColor(); 725 | } 726 | int r = color.getRed(); 727 | int g = color.getGreen(); 728 | int b = color.getBlue(); 729 | if (isControur) { 730 | r = 0; 731 | g = 0; 732 | b = 0; 733 | } 734 | else if (height < 0 && showWater) { 735 | r = (int) (r * 0.2f + 0.4f * 0.4f * 256f); 736 | g = (int) (g * 0.2f + 0.5f * 0.4f * 256f); 737 | b = (int) (b * 0.2f + 1.0f * 0.4f * 256f); 738 | } 739 | 740 | data[(x + y * lWidth) * 3 + 0] = r; 741 | data[(x + y * lWidth) * 3 + 1] = g; 742 | data[(x + y * lWidth) * 3 + 2] = b; 743 | } 744 | } 745 | 746 | bi2.getRaster().setPixels(0, 0, lWidth, lWidth, data); 747 | return bi2; 748 | } 749 | 750 | private boolean checkContourLine(short h0, short h1, short interval) { 751 | if (h0 == h1) { 752 | return false; 753 | } 754 | for (int i = h0; i<=h1; i++) { 755 | if (i % interval == 0) { 756 | return true; 757 | } 758 | } 759 | return false; 760 | } 761 | 762 | /** 763 | * Saves all changes to file. Before saving, this method will remove some map errors like wrong terrain type on completely exposed tiles and surface layer being lower than rock layer. 764 | */ 765 | public void saveChanges() { 766 | for (int i = 0; i < getWidth(); i++) { 767 | for (int i2 = 0; i2 < getHeight(); i2++) { 768 | short surfaceHeight = getSurfaceHeight(i, i2); 769 | short rockHeight = getRockHeight(i, i2); 770 | 771 | if (rockHeight > surfaceHeight) { 772 | setSurfaceHeight(i, i2, rockHeight); 773 | } 774 | } 775 | } 776 | 777 | for (int i = 0; i < getWidth() - 1; i++) { 778 | for (int i2 = 0; i2 < getHeight() - 1; i2++) { 779 | short h00 = getDirtLayerHeight(i, i2); 780 | short h10 = getDirtLayerHeight(i + 1, i2); 781 | short h01 = getDirtLayerHeight(i, i2 + 1); 782 | short h11 = getDirtLayerHeight(i + 1, i2 + 1); 783 | int total = h00 + h10 + h01 + h11; 784 | if (total == 0) { 785 | short height = getSurfaceHeight(i, i2); 786 | surfaceMesh.setTile(i, i2, Tiles.encode(height, (byte) Tiles.TILE_TYPE_ROCK, (byte) 0)); 787 | } 788 | } 789 | } 790 | 791 | try { 792 | for (MeshIO file : allMeshes) { 793 | file.saveAll(); 794 | } 795 | } catch (IOException ex) { 796 | Logger.getLogger(MapData.class.getName()).log(Level.SEVERE, null, ex); 797 | } 798 | } 799 | 800 | void close() { 801 | try { 802 | for (MeshIO file : allMeshes) { 803 | file.close(); 804 | } 805 | } catch (IOException ex) { 806 | Logger.getLogger(MapData.class.getName()).log(Level.SEVERE, null, ex); 807 | } 808 | } 809 | 810 | } 811 | --------------------------------------------------------------------------------