package org.dynmap.hdmap; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.LineNumberReader; import java.util.ArrayList; import java.util.BitSet; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.zip.ZipEntry; import java.util.zip.ZipException; import java.util.zip.ZipFile; import org.dynmap.ConfigurationNode; import org.dynmap.DynmapCore; import org.dynmap.Log; import org.dynmap.MapManager; import org.dynmap.debug.Debug; import org.dynmap.hdmap.TexturePack.BlockTransparency; import org.dynmap.hdmap.TexturePack.HDTextureMap; import org.dynmap.renderer.CustomRenderer; import org.dynmap.renderer.MapDataContext; import org.dynmap.renderer.RenderPatch; import org.dynmap.renderer.RenderPatchFactory.SideVisible; import org.dynmap.utils.BlockStep; import org.dynmap.utils.ForgeConfigFile; import org.dynmap.utils.MapIterator; import org.dynmap.utils.PatchDefinition; import org.dynmap.utils.PatchDefinitionFactory; /** * Custom block models - used for non-cube blocks to represent the physical volume associated with the block * Used by perspectives to determine if rays have intersected a block that doesn't occupy its whole block */ public class HDBlockModels { private static final int BLOCKTABLELEN = 4096; private static int linkalg[] = new int[BLOCKTABLELEN]; private static int linkmap[][] = new int[BLOCKTABLELEN][]; private static int max_patches; private static HashMap<Integer, HDBlockModel> models_by_id_data = new HashMap<Integer, HDBlockModel>(); private static PatchDefinitionFactory pdf = new PatchDefinitionFactory(); private static BitSet customModelsRequestingTileData = new BitSet(); // Index by 16*id + data private static BitSet changeIgnoredBlocks = new BitSet(); // Index by 16*id + data private static HashSet<String> loadedmods = new HashSet<String>(); // special render data algorithms private static final int FENCE_ALGORITHM = 1; private static final int CHEST_ALGORITHM = 2; private static final int REDSTONE_ALGORITHM = 3; private static final int GLASS_IRONFENCE_ALG = 4; private static final int WIRE_ALGORITHM = 5; private static final int DOOR_ALGORITHM = 6; private static final int REDSTONE_BLKTYPEID = 55; private static final int FENCEGATE_BLKTYPEID = 107; private enum ChestData { SINGLE_WEST, SINGLE_SOUTH, SINGLE_EAST, SINGLE_NORTH, LEFT_WEST, LEFT_SOUTH, LEFT_EAST, LEFT_NORTH, RIGHT_WEST, RIGHT_SOUTH, RIGHT_EAST, RIGHT_NORTH }; public static final int getMaxPatchCount() { return max_patches; } public static final PatchDefinitionFactory getPatchDefinitionFactory() { return pdf; } /* Reset model if defined by different block set */ public static boolean resetIfNotBlockSet(int blkid, int blkdata, String blockset) { HDBlockModel bm = models_by_id_data.get((blkid << 4) | blkdata); if((bm != null) && (bm.getBlockSet().equals(blockset) == false)) { Debug.debug("Reset block model for " + blkid + ":" + blkdata + " from " + bm.getBlockSet() + " due to new def from " + blockset); models_by_id_data.remove((blkid << 4) | blkdata); return true; } return false; } /* Get texture count needed for model */ public static int getNeededTextureCount(int blkid, int blkdata) { HDBlockModel bm = models_by_id_data.get((blkid << 4) | blkdata); if(bm != null) { return bm.getTextureCount(); } return 6; } public static final boolean isChangeIgnoredBlock(int blkid, int blkdata) { return changeIgnoredBlocks.get((blkid << 4) | blkdata); } /* Process any block aliases */ public static void handleBlockAlias() { for(int i = 0; i < BLOCKTABLELEN; i++) { int id = MapManager.mapman.getBlockIDAlias(i); if(id != i) { /* New mapping? */ remapModel(i, id); } } } private static void remapModel(int id, int newid) { if ((id > 0) && (id < BLOCKTABLELEN) && (newid >= 0) && (newid < BLOCKTABLELEN)) { linkalg[id] = linkalg[newid]; linkmap[id] = linkmap[newid]; for (int meta = 0; meta < 16; meta++) { int srcid = (newid * 16) + meta; int destid = (id * 16) + meta; HDBlockModel m = models_by_id_data.get(srcid); if (m != null) { models_by_id_data.put(destid, m); } else { models_by_id_data.remove(destid); } customModelsRequestingTileData.set(destid, customModelsRequestingTileData.get(srcid)); changeIgnoredBlocks.set(destid, changeIgnoredBlocks.get(srcid)); } } } public static class HDScaledBlockModels { private short[][][] modelvectors; private PatchDefinition[][][] patches; private CustomBlockModel[][] custom; public final short[] getScaledModel(int blocktype, int blockdata, int blockrenderdata) { short[][] m; try { if(modelvectors[blocktype] == null) { return null; } m = modelvectors[blocktype]; } catch (ArrayIndexOutOfBoundsException aioobx) { short[][][] newmodels = new short[blocktype+1][][]; System.arraycopy(modelvectors, 0, newmodels, 0, modelvectors.length); modelvectors = newmodels; return null; } return m[(blockrenderdata>=0)?blockrenderdata:blockdata]; } public PatchDefinition[] getPatchModel(int blocktype, int blockdata, int blockrenderdata) { try { if(patches[blocktype] == null) { return null; } return patches[blocktype][(blockrenderdata>=0)?blockrenderdata:blockdata]; } catch (ArrayIndexOutOfBoundsException aioobx) { PatchDefinition[][][] newpatches = new PatchDefinition[blocktype+1][][]; System.arraycopy(patches, 0, newpatches, 0, patches.length); patches = newpatches; return null; } } public CustomBlockModel getCustomBlockModel(int blocktype, int blockdata) { try { if(custom[blocktype] == null) { return null; } return custom[blocktype][blockdata]; } catch (ArrayIndexOutOfBoundsException aioobx) { CustomBlockModel[][] newcustom = new CustomBlockModel[blocktype+1][]; System.arraycopy(custom, 0, newcustom, 0, custom.length); custom = newcustom; return null; } } } private static HashMap<Integer, HDScaledBlockModels> scaled_models_by_scale = new HashMap<Integer, HDScaledBlockModels>(); public static abstract class HDBlockModel { private String blockset; /** * Block definition - positions correspond to Bukkit coordinates (+X is south, +Y is up, +Z is west) * @param blockid - block ID * @param databits - bitmap of block data bits matching this model (bit N is set if data=N would match) * @param blockset - ID of block definition set */ protected HDBlockModel(int blockid, int databits, String blockset) { this.blockset = blockset; if(blockid > 0) { for(int i = 0; i < 16; i++) { if((databits & (1<<i)) != 0) { HDBlockModel prev = models_by_id_data.put((blockid<<4)+i, this); if((prev != null) && (prev != this)) { prev.removed(blockid, i); } } } } } public String getBlockSet() { return blockset; } public abstract int getTextureCount(); public void removed(int blkid, int blkdat) { } } public static class CustomBlockModel extends HDBlockModel { public CustomRenderer render; public CustomBlockModel(int blockid, int databits, String classname, Map<String,String> classparm, String blockset) { super(blockid, databits, blockset); try { Class<?> cls = Class.forName(classname); /* Get class */ render = (CustomRenderer) cls.newInstance(); if(render.initializeRenderer(pdf, blockid, databits, classparm) == false) { Log.severe("Error loading custom renderer - " + classname); render = null; } else { if(render.getTileEntityFieldsNeeded() != null) { for(int i = 0; i < 16; i++) { if ((databits & (1 << i)) != 0) { customModelsRequestingTileData.set((blockid<<4) | i); } } } } } catch (Exception x) { Log.severe("Error loading custom renderer - " + classname, x); render = null; } } @Override public int getTextureCount() { return render.getMaximumTextureCount(pdf); } private static final RenderPatch[] empty_list = new RenderPatch[0]; public RenderPatch[] getMeshForBlock(MapDataContext ctx) { if(render != null) return render.getRenderPatchList(ctx); else return empty_list; } @Override public void removed(int blkid, int blkdat) { super.removed(blkid, blkdat); customModelsRequestingTileData.clear((blkid<<4) | blkdat); } } public static class HDBlockVolumetricModel extends HDBlockModel { /* Volumetric model specific attributes */ private long blockflags[]; private int nativeres; private HashMap<Integer, short[]> scaledblocks; /** * Block definition - positions correspond to Bukkit coordinates (+X is south, +Y is up, +Z is west) * (for volumetric models) * @param blockid - block ID * @param databits - bitmap of block data bits matching this model (bit N is set if data=N would match) * @param nativeres - native subblocks per edge of cube (up to 64) * @param blockflags - array of native^2 long integers representing volume of block (bit X of element (nativeres*Y+Z) is set if that subblock is filled) * if array is short, other elements area are assumed to be zero (fills from bottom of block up) * @param blockset - ID of set of blocks defining model */ public HDBlockVolumetricModel(int blockid, int databits, int nativeres, long[] blockflags, String blockset) { super(blockid, databits, blockset); this.nativeres = nativeres; this.blockflags = new long[nativeres * nativeres]; System.arraycopy(blockflags, 0, this.blockflags, 0, blockflags.length); } /** * Test if given native block is filled (for volumetric model) * * @param x - X coordinate * @param y - Y coordinate * @param z - Z coordinate * @return true if set, false if not */ public final boolean isSubblockSet(int x, int y, int z) { return ((blockflags[nativeres*y+z] & (1 << x)) != 0); } /** * Set subblock value (for volumetric model) * * @param x - X coordinate * @param y - Y coordinate * @param z - Z coordinate * @param isset - true = set, false = clear */ public final void setSubblock(int x, int y, int z, boolean isset) { if(isset) blockflags[nativeres*y+z] |= (1 << x); else blockflags[nativeres*y+z] &= ~(1 << x); } /** * Get scaled map of block: will return array of alpha levels, corresponding to how much of the * scaled subblocks are occupied by the original blocks (indexed by Y*res*res + Z*res + X) * @param res - requested scale (res subblocks per edge of block) * @return array of alpha values (0-255), corresponding to resXresXres subcubes of block */ public short[] getScaledMap(int res) { if(scaledblocks == null) { scaledblocks = new HashMap<Integer, short[]>(); } short[] map = scaledblocks.get(Integer.valueOf(res)); if(map == null) { map = new short[res*res*res]; if(res == nativeres) { for(int i = 0; i < blockflags.length; i++) { for(int j = 0; j < nativeres; j++) { if((blockflags[i] & (1 << j)) != 0) map[res*i+j] = 255; } } } /* If scaling from smaller sub-blocks to larger, each subblock contributes to 1-2 blocks * on each axis: need to calculate crossovers for each, and iterate through smaller * blocks to accumulate contributions */ else if(res > nativeres) { int weights[] = new int[res]; int offsets[] = new int[res]; /* LCM of resolutions is used as length of line (res * nativeres) * Each native block is (res) long, each scaled block is (nativeres) long * Each scaled block overlaps 1 or 2 native blocks: starting with native block 'offsets[]' with * 'weights[]' of its (res) width in the first, and the rest in the second */ for(int v = 0, idx = 0; v < res*nativeres; v += nativeres, idx++) { offsets[idx] = (v/res); /* Get index of the first native block we draw from */ if((v+nativeres-1)/res == offsets[idx]) { /* If scaled block ends in same native block */ weights[idx] = nativeres; } else { /* Else, see how much is in first one */ weights[idx] = (offsets[idx] + res) - v; weights[idx] = (offsets[idx]*res + res) - v; } } /* Now, use weights and indices to fill in scaled map */ for(int y = 0, off = 0; y < res; y++) { int ind_y = offsets[y]; int wgt_y = weights[y]; for(int z = 0; z < res; z++) { int ind_z = offsets[z]; int wgt_z = weights[z]; for(int x = 0; x < res; x++, off++) { int ind_x = offsets[x]; int wgt_x = weights[x]; int raw_w = 0; for(int xx = 0; xx < 2; xx++) { int wx = (xx==0)?wgt_x:(nativeres-wgt_x); if(wx == 0) continue; for(int yy = 0; yy < 2; yy++) { int wy = (yy==0)?wgt_y:(nativeres-wgt_y); if(wy == 0) continue; for(int zz = 0; zz < 2; zz++) { int wz = (zz==0)?wgt_z:(nativeres-wgt_z); if(wz == 0) continue; if(isSubblockSet(ind_x+xx, ind_y+yy, ind_z+zz)) { raw_w += wx*wy*wz; } } } } map[off] = (short)((255*raw_w) / (nativeres*nativeres*nativeres)); if(map[off] > 255) map[off] = 255; if(map[off] < 0) map[off] = 0; } } } } else { /* nativeres > res */ int weights[] = new int[nativeres]; int offsets[] = new int[nativeres]; /* LCM of resolutions is used as length of line (res * nativeres) * Each native block is (res) long, each scaled block is (nativeres) long * Each native block overlaps 1 or 2 scaled blocks: starting with scaled block 'offsets[]' with * 'weights[]' of its (res) width in the first, and the rest in the second */ for(int v = 0, idx = 0; v < res*nativeres; v += res, idx++) { offsets[idx] = (v/nativeres); /* Get index of the first scaled block we draw to */ if((v+res-1)/nativeres == offsets[idx]) { /* If native block ends in same scaled block */ weights[idx] = res; } else { /* Else, see how much is in first one */ weights[idx] = (offsets[idx]*nativeres + nativeres) - v; } } /* Now, use weights and indices to fill in scaled map */ long accum[] = new long[map.length]; for(int y = 0; y < nativeres; y++) { int ind_y = offsets[y]; int wgt_y = weights[y]; for(int z = 0; z < nativeres; z++) { int ind_z = offsets[z]; int wgt_z = weights[z]; for(int x = 0; x < nativeres; x++) { if(isSubblockSet(x, y, z)) { int ind_x = offsets[x]; int wgt_x = weights[x]; for(int xx = 0; xx < 2; xx++) { int wx = (xx==0)?wgt_x:(res-wgt_x); if(wx == 0) continue; for(int yy = 0; yy < 2; yy++) { int wy = (yy==0)?wgt_y:(res-wgt_y); if(wy == 0) continue; for(int zz = 0; zz < 2; zz++) { int wz = (zz==0)?wgt_z:(res-wgt_z); if(wz == 0) continue; accum[(ind_y+yy)*res*res + (ind_z+zz)*res + (ind_x+xx)] += wx*wy*wz; } } } } } } } for(int i = 0; i < map.length; i++) { map[i] = (short)(accum[i]*255/nativeres/nativeres/nativeres); if(map[i] > 255) map[i] = 255; if(map[i] < 0) map[i] = 0; } } scaledblocks.put(Integer.valueOf(res), map); } return map; } @Override public int getTextureCount() { return 6; } } public static class HDBlockPatchModel extends HDBlockModel { /* Patch model specific attributes */ private PatchDefinition[] patches; private final int max_texture; /** * Block definition - positions correspond to Bukkit coordinates (+X is south, +Y is up, +Z is west) * (for patch models) * @param blockid - block ID * @param databits - bitmap of block data bits matching this model (bit N is set if data=N would match) * @param patches - list of patches (surfaces composing model) * @param blockset - ID of set of blocks defining model */ public HDBlockPatchModel(int blockid, int databits, PatchDefinition[] patches, String blockset) { super(blockid, databits, blockset); this.patches = patches; int max = 0; for(int i = 0; i < patches.length; i++) { if((patches[i] != null) && (patches[i].textureindex > max)) max = patches[i].textureindex; } this.max_texture = max + 1; } /** * Get patches for block model (if patch model) * @return patches for model */ public final PatchDefinition[] getPatches() { return patches; } @Override public int getTextureCount() { return max_texture; } } /** * Get link algorithm * @param blkid - block ID * @return 0=no link alg */ public static final int getLinkAlgID(int blkid) { return linkalg[blkid]; } /** * Get link block IDs * @param blkid - block ID * @return array of block IDs to link with */ public static final int[] getLinkIDs(int blkid) { return linkmap[blkid]; } /** * Get list of tile entity fields needed for custom renderer at given ID and data value, if any * @param blkid - block ID * @param blkdat - block data * @return null if none needed, else list of fields needed */ public static final String[] getTileEntityFieldsNeeded(int blkid, int blkdat) { int idx = (blkid << 4) | blkdat; if(customModelsRequestingTileData.get(idx)) { HDBlockModel mod = models_by_id_data.get(idx); if(mod instanceof CustomBlockModel) { return ((CustomBlockModel)mod).render.getTileEntityFieldsNeeded(); } } return null; } /** * Get scaled set of models for all modelled blocks * @param scale - scale * @return scaled models */ public static HDScaledBlockModels getModelsForScale(int scale) { HDScaledBlockModels model = scaled_models_by_scale.get(Integer.valueOf(scale)); if(model == null) { model = new HDScaledBlockModels(); short[][][] blockmodels = new short[BLOCKTABLELEN][][]; PatchDefinition[][][] patches = new PatchDefinition[BLOCKTABLELEN][][]; CustomBlockModel[][] custom = new CustomBlockModel[BLOCKTABLELEN][]; for(Integer id_data : models_by_id_data.keySet()) { int blkid = id_data.intValue() >> 4; int blkmeta = id_data.intValue() & 0xF; HDBlockModel m = models_by_id_data.get(id_data); if(m instanceof HDBlockVolumetricModel) { short[][] row = blockmodels[blkid]; if(row == null) { row = new short[16][]; blockmodels[blkid] = row; } HDBlockVolumetricModel vm = (HDBlockVolumetricModel)m; short[] smod = vm.getScaledMap(scale); /* See if scaled model is full block : much faster to not use it if it is */ if(smod != null) { boolean keep = false; for(int i = 0; (!keep) && (i < smod.length); i++) { if(smod[i] == 0) keep = true; } if(keep) { row[blkmeta] = smod; } } } else if(m instanceof HDBlockPatchModel) { HDBlockPatchModel pm = (HDBlockPatchModel)m; PatchDefinition[] patch = pm.getPatches(); PatchDefinition[][] row = patches[blkid]; if(row == null) { row = new PatchDefinition[16][]; patches[blkid] = row; } if(patch != null) { row[blkmeta] = patch; } } else if(m instanceof CustomBlockModel) { CustomBlockModel cbm = (CustomBlockModel)m; CustomBlockModel[] row = custom[blkid]; if(row == null) { row = new CustomBlockModel[16]; custom[blkid] = row; } row[blkmeta] = cbm; } } model.modelvectors = blockmodels; model.patches = patches; model.custom = custom; scaled_models_by_scale.put(scale, model); } return model; } private static void addFiles(ArrayList<String> files, File dir, String path) { File[] listfiles = dir.listFiles(); if(listfiles == null) return; for(File f : listfiles) { String fn = f.getName(); if(fn.equals(".") || (fn.equals(".."))) continue; if(f.isFile()) { if(fn.endsWith("-models.txt")) { files.add(path + fn); } } else if(f.isDirectory()) { addFiles(files, f, path + f.getName() + "/"); } } } public static String getModIDFromFileName(String fn) { int off = fn.lastIndexOf('/'); if (off > 0) fn = fn.substring(off+1); off = fn.lastIndexOf('-'); if (off > 0) fn = fn.substring(0, off); return fn; } /** * Load models * @param core - core object * @param config - model configuration data */ public static void loadModels(DynmapCore core, ConfigurationNode config) { File datadir = core.getDataFolder(); max_patches = 6; /* Reset to default */ /* Reset models-by-ID-Data cache */ models_by_id_data.clear(); /* Reset scaled models by scale cache */ scaled_models_by_scale.clear(); /* Reset change-ignored flags */ changeIgnoredBlocks.clear(); /* Reset model list */ loadedmods.clear(); /* Load block models */ int i = 0; boolean done = false; InputStream in = null; ZipFile zf; while (!done) { in = TexturePack.class.getResourceAsStream("/models_" + i + ".txt"); if(in != null) { loadModelFile(in, "models_" + i + ".txt", config, core, "core"); try { in.close(); } catch (IOException iox) {} in = null; } else { done = true; } i++; } /* Check mods to see if model files defined there: do these first, as they trump other sources */ for (String modid : core.getServer().getModList()) { File f = core.getServer().getModContainerFile(modid); // Get mod file if (f.isFile()) { zf = null; in = null; try { zf = new ZipFile(f); String fn = "assets/" + modid.toLowerCase() + "/dynmap-models.txt"; ZipEntry ze = zf.getEntry(fn); if (ze != null) { in = zf.getInputStream(ze); loadModelFile(in, fn, config, core, modid); loadedmods.add(modid); // Add to set: prevent others definitions for same mod } } catch (ZipException e) { } catch (IOException e) { } finally { if (in != null) { try { in.close(); } catch (IOException e) { } in = null; } if (zf != null) { try { zf.close(); } catch (IOException e) { } zf = null; } } } } // Load external model files (these go before internal versions, to allow external overrides) ArrayList<String> files = new ArrayList<String>(); File customdir = new File(datadir, "renderdata"); addFiles(files, customdir, ""); for(String fn : files) { File custom = new File(customdir, fn); if(custom.canRead()) { try { in = new FileInputStream(custom); loadModelFile(in, custom.getPath(), config, core, getModIDFromFileName(fn)); } catch (IOException iox) { Log.severe("Error loading " + custom.getPath()); } finally { if(in != null) { try { in.close(); } catch (IOException iox) {} in = null; } } } } // Load internal texture files (these go last, to allow other versions to replace them) zf = null; try { zf = new ZipFile(core.getPluginJarFile()); Enumeration<? extends ZipEntry> e = zf.entries(); while (e.hasMoreElements()) { ZipEntry ze = e.nextElement(); String n = ze.getName(); if (!n.startsWith("renderdata/")) continue; if (!n.endsWith("-models.txt")) continue; in = zf.getInputStream(ze); if (in != null) { loadModelFile(in, n, config, core, getModIDFromFileName(n)); try { in.close(); } catch (IOException x) { in = null; } } } } catch (IOException iox) { Log.severe("Error processing nodel files"); } finally { if (in != null) { try { in.close(); } catch (IOException iox) {} in = null; } if (zf != null) { try { zf.close(); } catch (IOException iox) {} zf = null; } } } private static Integer getIntValue(Map<String,Integer> vars, String val) throws NumberFormatException { char c = val.charAt(0); if(Character.isLetter(c) || (c == '%') || (c == '&')) { int off = val.indexOf('+'); int offset = 0; if (off > 0) { offset = Integer.valueOf(val.substring(off+1)); val = val.substring(0, off); } Integer v = vars.get(val); if(v == null) { if ((c == '%') || (c == '&')) { // block/item unique IDs vars.put(val, 0); v = 0; } else { throw new NumberFormatException("invalid ID - " + val); } } if((offset != 0) && (v.intValue() > 0)) v = v.intValue() + offset; return v; } else { return Integer.valueOf(val); } } // Patch index ordering, corresponding to BlockStep ordinal order public static final int boxPatchList[] = { 1, 4, 0, 3, 2, 5 }; /** * Load models from file * @param core */ private static void loadModelFile(InputStream in, String fname, ConfigurationNode config, DynmapCore core, String blockset) { LineNumberReader rdr = null; int cnt = 0; boolean need_mod_cfg = false; boolean mod_cfg_loaded = false; String modname = null; String modversion = null; final String mcver = core.getDynmapPluginPlatformVersion(); try { String line; ArrayList<HDBlockVolumetricModel> modlist = new ArrayList<HDBlockVolumetricModel>(); ArrayList<HDBlockPatchModel> pmodlist = new ArrayList<HDBlockPatchModel>(); HashMap<String,Integer> varvals = new HashMap<String,Integer>(); HashMap<String, PatchDefinition> patchdefs = new HashMap<String, PatchDefinition>(); pdf.setPatchNameMape(patchdefs); int layerbits = 0; int rownum = 0; int scale = 0; rdr = new LineNumberReader(new InputStreamReader(in)); while((line = rdr.readLine()) != null) { boolean skip = false; if ((line.length() > 0) && (line.charAt(0) == '[')) { // If version constrained like int end = line.indexOf(']'); // Find end if (end < 0) { Log.severe("Format error - line " + rdr.getLineNumber() + " of " + fname + ": bad version limit"); return; } String vertst = line.substring(1, end); String tver = mcver; if (vertst.startsWith("mod:")) { // If mod version ranged tver = modversion; vertst = vertst.substring(4); } if (!HDBlockModels.checkVersionRange(tver, vertst)) { skip = true; } line = line.substring(end+1); } // If we're skipping due to version restriction if (skip) { } else if(line.startsWith("block:")) { ArrayList<Integer> blkids = new ArrayList<Integer>(); int databits = 0; scale = 0; line = line.substring(6); String[] args = line.split(","); for(String a : args) { String[] av = a.split("="); if(av.length < 2) continue; if(av[0].equals("id")) { blkids.add(getIntValue(varvals,av[1])); } else if(av[0].equals("data")) { if(av[1].equals("*")) databits = 0xFFFF; else databits |= (1 << getIntValue(varvals,av[1])); } else if(av[0].equals("scale")) { scale = Integer.parseInt(av[1]); } } /* If we have everything, build block */ if((blkids.size() > 0) && (databits != 0) && (scale > 0)) { modlist.clear(); for(Integer id : blkids) { if(id > 0) { modlist.add(new HDBlockVolumetricModel(id.intValue(), databits, scale, new long[0], blockset)); cnt++; } } } else { Log.severe("Block model missing required parameters = line " + rdr.getLineNumber() + " of " + fname); } layerbits = 0; } else if(line.startsWith("layer:")) { line = line.substring(6); String args[] = line.split(","); layerbits = 0; rownum = 0; for(String a: args) { layerbits |= (1 << Integer.parseInt(a)); } } else if(line.startsWith("rotate:")) { line = line.substring(7); String args[] = line.split(","); int id = -1; int data = -1; int rot = -1; for(String a : args) { String[] av = a.split("="); if(av.length < 2) continue; if(av[0].equals("id")) { int newid = getIntValue(varvals,av[1]); if(newid > 0) id = newid; } if(av[0].equals("data")) { data = getIntValue(varvals,av[1]); } if(av[0].equals("rot")) { rot = Integer.parseInt(av[1]); } } /* get old model to be rotated */ HDBlockModel mod = models_by_id_data.get((id<<4)+data); if(modlist.isEmpty()) { } else if((mod != null) && ((rot%90) == 0) && (mod instanceof HDBlockVolumetricModel)) { HDBlockVolumetricModel vmod = (HDBlockVolumetricModel)mod; for(int x = 0; x < scale; x++) { for(int y = 0; y < scale; y++) { for(int z = 0; z < scale; z++) { if(vmod.isSubblockSet(x, y, z) == false) continue; switch(rot) { case 0: for(HDBlockVolumetricModel bm : modlist) { bm.setSubblock(x, y, z, true); } break; case 90: for(HDBlockVolumetricModel bm : modlist) { bm.setSubblock(scale-z-1, y, x, true); } break; case 180: for(HDBlockVolumetricModel bm : modlist) { bm.setSubblock(scale-x-1, y, scale-z-1, true); } break; case 270: for(HDBlockVolumetricModel bm : modlist) { bm.setSubblock(z, y, scale-x-1, true); } break; } } } } } else { Log.severe("Invalid rotate error - line " + rdr.getLineNumber() + " of " + fname); return; } } else if(line.startsWith("patchrotate:")) { line = line.substring(12); String args[] = line.split(","); int id = -1; int data = -1; int rotx = 0; int roty = 0; int rotz = 0; for(String a : args) { String[] av = a.split("="); if(av.length < 2) continue; if(av[0].equals("id")) { int newid = getIntValue(varvals,av[1]); if(newid > 0) { id = newid; } } if(av[0].equals("data")) { data = getIntValue(varvals,av[1]); } if(av[0].equals("rot")) { roty = Integer.parseInt(av[1]); } if(av[0].equals("roty")) { roty = Integer.parseInt(av[1]); } if(av[0].equals("rotx")) { rotx = Integer.parseInt(av[1]); } if(av[0].equals("rotz")) { rotz = Integer.parseInt(av[1]); } } /* get old model to be rotated */ HDBlockModel mod = models_by_id_data.get((id<<4)+data); if(pmodlist.isEmpty()) { } else if((mod != null) && (mod instanceof HDBlockPatchModel)) { HDBlockPatchModel pmod = (HDBlockPatchModel)mod; PatchDefinition patches[] = pmod.getPatches(); PatchDefinition newpatches[] = new PatchDefinition[patches.length]; for(int i = 0; i < patches.length; i++) { newpatches[i] = (PatchDefinition)pdf.getRotatedPatch(patches[i], rotx, roty, rotz, patches[i].textureindex); } if(patches.length > max_patches) max_patches = patches.length; for(HDBlockPatchModel patchmod : pmodlist) { patchmod.patches = newpatches; } } else { Log.severe("Invalid rotate error - line " + rdr.getLineNumber() + " of " + fname); return; } } else if(line.startsWith("linkmap:")) { ArrayList<Integer> blkids = new ArrayList<Integer>(); line = line.substring(8); String[] args = line.split(","); List<Integer> map = new ArrayList<Integer>(); int linktype = 0; for(String a : args) { String[] av = a.split("="); if(av.length < 2) continue; if(av[0].equals("id")) { blkids.add(getIntValue(varvals,av[1])); } else if(av[0].equals("linkalg")) { linktype = Integer.parseInt(av[1]); } else if(av[0].equals("linkid")) { map.add(getIntValue(varvals,av[1])); } } if(linktype > 0) { int[] mapids = new int[map.size()]; for(int i = 0; i < mapids.length; i++) mapids[i] = map.get(i); for(Integer bid : blkids) { linkalg[bid] = linktype; linkmap[bid] = mapids; } } } else if(line.startsWith("ignore-updates:")) { ArrayList<Integer> blkids = new ArrayList<Integer>(); int blkdat = 0; line = line.substring(line.indexOf(':')+1); String[] args = line.split(","); for(String a : args) { String[] av = a.split("="); if(av.length < 2) continue; if(av[0].equals("id")) { blkids.add(getIntValue(varvals,av[1])); } else if(av[0].equals("data")) { if(av[1].equals("*")) blkdat = 0xFFFF; else blkdat |= (1 << getIntValue(varvals,av[1])); } } if(blkdat == 0) blkdat = 0xFFFF; for(Integer id : blkids) { if(id <= 0) continue; for(int i = 0; i < 16; i++) { changeIgnoredBlocks.set(id*16 + i); } } } else if(line.startsWith("#") || line.startsWith(";")) { } else if(line.startsWith("enabled:")) { /* Test if texture file is enabled */ line = line.substring(8).trim(); if(line.startsWith("true")) { /* We're enabled? */ /* Nothing to do - keep processing */ } else if(line.startsWith("false")) { /* Disabled */ return; /* Quit */ } /* If setting is not defined or false, quit */ else if(config.getBoolean(line, false) == false) { return; } else { Log.info(line + " models enabled"); } } else if(line.startsWith("var:")) { /* Test if variable declaration */ line = line.substring(4).trim(); String args[] = line.split(","); for(int i = 0; i < args.length; i++) { String[] v = args[i].split("="); if(v.length < 2) { Log.severe("Format error - line " + rdr.getLineNumber() + " of " + fname); return; } try { int val = Integer.valueOf(v[1]); /* Parse default value */ int parmval = config.getInteger(v[0], val); /* Read value, with applied default */ varvals.put(v[0], parmval); /* And save value */ } catch (NumberFormatException nfx) { Log.severe("Format error - line " + rdr.getLineNumber() + " of " + fname); return; } } } else if(line.startsWith("cfgfile:")) { /* If config file */ File cfgfile = new File(line.substring(8).trim()); ForgeConfigFile cfg = new ForgeConfigFile(cfgfile); if (!mod_cfg_loaded) { need_mod_cfg = true; } if(cfg.load()) { cfg.addBlockIDs(varvals); need_mod_cfg = false; mod_cfg_loaded = true; } } else if(line.startsWith("patch:")) { String patchid = null; line = line.substring(6); String[] args = line.split(","); double p_x0 = 0.0, p_y0 = 0.0, p_z0 = 0.0; double p_xu = 0.0, p_yu = 1.0, p_zu = 0.0; double p_xv = 1.0, p_yv = 0.0, p_zv = 0.0; double p_umin = 0.0, p_umax = 1.0; double p_vmin = 0.0, p_vmax = 1.0; double p_uplusvmax = 100.0; SideVisible p_sidevis = SideVisible.BOTH; for(String a : args) { String[] av = a.split("="); if(av.length < 2) continue; if(av[0].equals("id")) { patchid = av[1]; } else if(av[0].equals("Ox")) { p_x0 = Double.parseDouble(av[1]); } else if(av[0].equals("Oy")) { p_y0 = Double.parseDouble(av[1]); } else if(av[0].equals("Oz")) { p_z0 = Double.parseDouble(av[1]); } else if(av[0].equals("Ux")) { p_xu = Double.parseDouble(av[1]); } else if(av[0].equals("Uy")) { p_yu = Double.parseDouble(av[1]); } else if(av[0].equals("Uz")) { p_zu = Double.parseDouble(av[1]); } else if(av[0].equals("Vx")) { p_xv = Double.parseDouble(av[1]); } else if(av[0].equals("Vy")) { p_yv = Double.parseDouble(av[1]); } else if(av[0].equals("Vz")) { p_zv = Double.parseDouble(av[1]); } else if(av[0].equals("Umin")) { p_umin = Double.parseDouble(av[1]); } else if(av[0].equals("Umax")) { p_umax = Double.parseDouble(av[1]); } else if(av[0].equals("Vmin")) { p_vmin = Double.parseDouble(av[1]); } else if(av[0].equals("Vmax")) { p_vmax = Double.parseDouble(av[1]); } else if(av[0].equals("UplusVmax")) { p_uplusvmax = Double.parseDouble(av[1]); } else if(av[0].equals("visibility")) { if(av[1].equals("top")) p_sidevis = SideVisible.TOP; else if(av[1].equals("bottom")) p_sidevis = SideVisible.BOTTOM; else if(av[1].equals("flip")) p_sidevis = SideVisible.FLIP; else p_sidevis = SideVisible.BOTH; } } /* If completed, add to map */ if(patchid != null) { PatchDefinition pd = pdf.getPatch(p_x0, p_y0, p_z0, p_xu, p_yu, p_zu, p_xv, p_yv, p_zv, p_umin, p_umax, p_vmin, p_vmax, p_uplusvmax, p_sidevis, 0); if(pd != null) { patchdefs.put(patchid, pd); } } } else if(line.startsWith("patchblock:")) { ArrayList<Integer> blkids = new ArrayList<Integer>(); int databits = 0; line = line.substring(11); String[] args = line.split(","); ArrayList<PatchDefinition> patches = new ArrayList<PatchDefinition>(); for(String a : args) { String[] av = a.split("="); if(av.length < 2) continue; if(av[0].equals("id")) { blkids.add(getIntValue(varvals,av[1])); } else if(av[0].equals("data")) { if(av[1].equals("*")) databits = 0xFFFF; else databits |= (1 << getIntValue(varvals,av[1])); } else if(av[0].startsWith("patch")) { int patchnum0, patchnum1; String ids = av[0].substring(5); String[] ids2 = ids.split("-"); if(ids2.length == 1) { patchnum0 = patchnum1 = Integer.parseInt(ids2[0]); } else { patchnum0 = Integer.parseInt(ids2[0]); patchnum1 = Integer.parseInt(ids2[1]); } if(patchnum0 < 0) { Log.severe("Invalid patch index " + patchnum0 + " - line " + rdr.getLineNumber() + " of " + fname); return; } if(patchnum1 < patchnum0) { Log.severe("Invalid patch index " + patchnum1 + " - line " + rdr.getLineNumber() + " of " + fname); return; } String patchid = av[1]; /* Look up patch by name */ for(int i = patchnum0; i <= patchnum1; i++) { PatchDefinition pd = pdf.getPatchByName(patchid, i); if(pd == null) { Log.severe("Invalid patch ID " + patchid + " - line " + rdr.getLineNumber() + " of " + fname); return; } patches.add(i, pd); } } } /* If we have everything, build block */ pmodlist.clear(); if((blkids.size() > 0) && (databits != 0)) { PatchDefinition[] patcharray = patches.toArray(new PatchDefinition[patches.size()]); if(patcharray.length > max_patches) max_patches = patcharray.length; for(Integer id : blkids) { if(id > 0) { pmodlist.add(new HDBlockPatchModel(id.intValue(), databits, patcharray, blockset)); cnt++; } } } else { Log.severe("Patch block model missing required parameters = line " + rdr.getLineNumber() + " of " + fname); } } // Shortcut for defining a patchblock that is a simple rectangular prism, with sidex corresponding to full block sides else if(line.startsWith("boxblock:")) { ArrayList<Integer> blkids = new ArrayList<Integer>(); int databits = 0; line = line.substring(9); String[] args = line.split(","); double xmin = 0.0, xmax = 1.0, ymin = 0.0, ymax = 1.0, zmin = 0.0, zmax = 1.0; for(String a : args) { String[] av = a.split("="); if(av.length < 2) continue; if(av[0].equals("id")) { blkids.add(getIntValue(varvals,av[1])); } else if(av[0].equals("data")) { if(av[1].equals("*")) databits = 0xFFFF; else databits |= (1 << getIntValue(varvals,av[1])); } else if(av[0].equals("xmin")) { xmin = Double.parseDouble(av[1]); } else if(av[0].equals("xmax")) { xmax = Double.parseDouble(av[1]); } else if(av[0].equals("ymin")) { ymin = Double.parseDouble(av[1]); } else if(av[0].equals("ymax")) { ymax = Double.parseDouble(av[1]); } else if(av[0].equals("zmin")) { zmin = Double.parseDouble(av[1]); } else if(av[0].equals("zmax")) { zmax = Double.parseDouble(av[1]); } } /* If we have everything, build block */ pmodlist.clear(); if((blkids.size() > 0) && (databits != 0)) { ArrayList<RenderPatch> pd = new ArrayList<RenderPatch>(); CustomRenderer.addBox(pdf, pd, xmin, xmax, ymin, ymax, zmin, zmax, boxPatchList); PatchDefinition[] patcharray = new PatchDefinition[pd.size()]; for (int i = 0; i < patcharray.length; i++) { patcharray[i] = (PatchDefinition) pd.get(i); } if(patcharray.length > max_patches) max_patches = patcharray.length; for(Integer id : blkids) { if(id > 0) { pmodlist.add(new HDBlockPatchModel(id.intValue(), databits, patcharray, blockset)); cnt++; } } } else { Log.severe("Box block model missing required parameters = line " + rdr.getLineNumber() + " of " + fname); } } else if(line.startsWith("customblock:")) { ArrayList<Integer> blkids = new ArrayList<Integer>(); HashMap<String,String> custargs = new HashMap<String,String>(); int databits = 0; line = line.substring(12); String[] args = line.split(","); String cls = null; for(String a : args) { String[] av = a.split("="); if(av.length < 2) continue; if(av[0].equals("id")) { blkids.add(getIntValue(varvals,av[1])); } else if(av[0].equals("data")) { if(av[1].equals("*")) databits = 0xFFFF; else databits |= (1 << getIntValue(varvals,av[1])); } else if(av[0].equals("class")) { cls = av[1]; } else { /* See if substitution value available */ Integer vv = varvals.get(av[1]); if(vv == null) custargs.put(av[0], av[1]); else custargs.put(av[0], vv.toString()); } } /* If we have everything, build block */ if((blkids.size() > 0) && (databits != 0) && (cls != null)) { for(Integer id : blkids) { if(id > 0) { CustomBlockModel cbm = new CustomBlockModel(id.intValue(), databits, cls, custargs, blockset); if(cbm.render == null) { Log.severe("Custom block model failed to initialize = line " + rdr.getLineNumber() + " of " + fname); } else { /* Update maximum texture count */ int texturecnt = cbm.getTextureCount(); if(texturecnt > max_patches) { max_patches = texturecnt; } } cnt++; } } } else { Log.severe("Custom block model missing required parameters = line " + rdr.getLineNumber() + " of " + fname); } } else if(line.startsWith("modname:")) { String[] names = line.substring(8).split(","); boolean found = false; for(String n : names) { String[] ntok = n.split("[\\[\\]]"); String rng = null; if (ntok.length > 1) { n = ntok[0].trim(); rng = ntok[1].trim(); } n = n.trim(); if (loadedmods.contains(n)) { // Already supplied by mod itself? return; } String modver = core.getServer().getModVersion(n); if((modver != null) && ((rng == null) || checkVersionRange(modver, rng))) { found = true; Log.info(n + "[" + modver + "] models enabled"); modname = n; modversion = modver; loadedmods.add(n); // Add to loaded mods // Prime values from block and item unique IDs core.addModBlockItemIDs(modname, varvals); break; } } if(!found) { return; } } else if(line.startsWith("version:")) { line = line.substring(line.indexOf(':')+1); if (!checkVersionRange(mcver, line)) { return; } } else if(layerbits != 0) { /* If we're working pattern lines */ /* Layerbits determine Y, rows count from North to South (X=0 to X=N-1), columns Z are West to East (N-1 to 0) */ for(int i = 0; (i < scale) && (i < line.length()); i++) { if(line.charAt(i) == '*') { /* If an asterix, set flag */ for(int y = 0; y < scale; y++) { if((layerbits & (1<<y)) != 0) { for(HDBlockVolumetricModel mod : modlist) { mod.setSubblock(rownum, y, scale-i-1, true); } } } } } /* See if we're done with layer */ rownum++; if(rownum >= scale) { rownum = 0; layerbits = 0; } } } if(need_mod_cfg) { Log.severe("Error loading configuration file for " + modname); } Log.verboseinfo("Loaded " + cnt + " block models from " + fname); } catch (IOException iox) { Log.severe("Error reading models.txt - " + iox.toString()); } catch (NumberFormatException nfx) { Log.severe("Format error - line " + rdr.getLineNumber() + " of " + fname + ": " + nfx.getMessage()); } finally { if(rdr != null) { try { rdr.close(); rdr = null; } catch (IOException e) { } } pdf.setPatchNameMape(null); } } private static long vscale[] = { 10000000000L, 100000000, 1000000, 10000, 100, 1 }; private static String normalizeVersion(String v) { StringBuilder v2 = new StringBuilder(); boolean skip = false; for (int i = 0; i < v.length(); i++) { char c = v.charAt(i); if ((c == '.') || ((c >= '0') && (c <= '9'))) { v2.append(c); skip = false; } else { if (!skip) { skip = true; v2.append('.'); } } } return v2.toString(); } private static long parseVersion(String v, boolean up) { v = normalizeVersion(v); String[] vv = v.split("\\."); long ver = 0; for (int i = 0; i < vscale.length; i++) { if (i < vv.length){ try { ver += vscale[i] * Integer.parseInt(vv[i]); } catch (NumberFormatException nfx) { } } else if (up) { ver += vscale[i] * 99; } } return ver; } public static boolean checkVersionRange(String ver, String range) { if (ver.equals(range)) return true; String[] rng = range.split("-", -1); String low; String high; long v = parseVersion(ver, false); if (v == 0) return false; if (rng.length == 1) { low = rng[0]; high = rng[0]; } else { low = rng[0]; high = rng[1]; } if ((low.length() > 0) && (parseVersion(low, false) > v)) { return false; } if ((high.length() > 0) && (parseVersion(high, true) < v)) { return false; } return true; } /** * Get render data for block * @param blocktypeid - block ID * @param map - map iterator * @return render data, or -1 if none */ public static int getBlockRenderData(int blocktypeid, MapIterator map) { int blockrenderdata = -1; switch(HDBlockModels.getLinkAlgID(blocktypeid)) { case FENCE_ALGORITHM: /* Fence algorithm */ blockrenderdata = generateFenceBlockData(blocktypeid, map); break; case CHEST_ALGORITHM: blockrenderdata = generateChestBlockData(blocktypeid, map); break; case REDSTONE_ALGORITHM: blockrenderdata = generateRedstoneWireBlockData(map); break; case GLASS_IRONFENCE_ALG: blockrenderdata = generateIronFenceGlassBlockData(blocktypeid, map); break; case WIRE_ALGORITHM: blockrenderdata = generateWireBlockData(HDBlockModels.getLinkIDs(blocktypeid), map); break; case DOOR_ALGORITHM: blockrenderdata = generateDoorBlockData(blocktypeid, map); break; } return blockrenderdata; } private static int generateFenceBlockData(int blkid, MapIterator mapiter) { int blockdata = 0; int id; /* Check north */ id = mapiter.getBlockTypeIDAt(BlockStep.X_MINUS); if((id == blkid) || (id == FENCEGATE_BLKTYPEID) || ((id > 0) && (HDTextureMap.getTransparency(id) == BlockTransparency.OPAQUE))) { /* Fence? */ blockdata |= 1; } /* Look east */ id = mapiter.getBlockTypeIDAt(BlockStep.Z_MINUS); if((id == blkid) || (id == FENCEGATE_BLKTYPEID) || ((id > 0) && (HDTextureMap.getTransparency(id) == BlockTransparency.OPAQUE))) { /* Fence? */ blockdata |= 2; } /* Look south */ id = mapiter.getBlockTypeIDAt(BlockStep.X_PLUS); if((id == blkid) || (id == FENCEGATE_BLKTYPEID) || ((id > 0) && (HDTextureMap.getTransparency(id) == BlockTransparency.OPAQUE))) { /* Fence? */ blockdata |= 4; } /* Look west */ id = mapiter.getBlockTypeIDAt(BlockStep.Z_PLUS); if((id == blkid) || (id == FENCEGATE_BLKTYPEID) || ((id > 0) && (HDTextureMap.getTransparency(id) == BlockTransparency.OPAQUE))) { /* Fence? */ blockdata |= 8; } return blockdata; } /** * Generate chest block to drive model selection: * 0 = single facing west * 1 = single facing south * 2 = single facing east * 3 = single facing north * 4 = left side facing west * 5 = left side facing south * 6 = left side facing east * 7 = left side facing north * 8 = right side facing west * 9 = right side facing south * 10 = right side facing east * 11 = right side facing north * @return */ private static int generateChestBlockData(int blktype, MapIterator mapiter) { int blkdata = mapiter.getBlockData(); /* Get block data */ ChestData cd = ChestData.SINGLE_WEST; /* Default to single facing west */ switch(blkdata) { /* First, use orientation data */ case 2: /* East (now north) */ if(mapiter.getBlockTypeIDAt(BlockStep.X_MINUS) == blktype) { /* Check north */ cd = ChestData.LEFT_EAST; } else if(mapiter.getBlockTypeIDAt(BlockStep.X_PLUS) == blktype) { /* Check south */ cd = ChestData.RIGHT_EAST; } else { cd = ChestData.SINGLE_EAST; } break; case 4: /* North */ if(mapiter.getBlockTypeIDAt(BlockStep.Z_MINUS) == blktype) { /* Check east */ cd = ChestData.RIGHT_NORTH; } else if(mapiter.getBlockTypeIDAt(BlockStep.Z_PLUS) == blktype) { /* Check west */ cd = ChestData.LEFT_NORTH; } else { cd = ChestData.SINGLE_NORTH; } break; case 5: /* South */ if(mapiter.getBlockTypeIDAt(BlockStep.Z_MINUS) == blktype) { /* Check east */ cd = ChestData.LEFT_SOUTH; } else if(mapiter.getBlockTypeIDAt(BlockStep.Z_PLUS) == blktype) { /* Check west */ cd = ChestData.RIGHT_SOUTH; } else { cd = ChestData.SINGLE_SOUTH; } break; case 3: /* West */ default: if(mapiter.getBlockTypeIDAt(BlockStep.X_MINUS) == blktype) { /* Check north */ cd = ChestData.RIGHT_WEST; } else if(mapiter.getBlockTypeIDAt(BlockStep.X_PLUS) == blktype) { /* Check south */ cd = ChestData.LEFT_WEST; } else { cd = ChestData.SINGLE_WEST; } break; } return cd.ordinal(); } /** * Generate redstone wire model data: * 0 = NSEW wire * 1 = NS wire * 2 = EW wire * 3 = NE wire * 4 = NW wire * 5 = SE wire * 6 = SW wire * 7 = NSE wire * 8 = NSW wire * 9 = NEW wire * 10 = SEW wire * 11 = none * @return */ private static int generateRedstoneWireBlockData(MapIterator mapiter) { /* Check adjacent block IDs */ int ids[] = { mapiter.getBlockTypeIDAt(BlockStep.Z_PLUS), /* To west */ mapiter.getBlockTypeIDAt(BlockStep.X_PLUS), /* To south */ mapiter.getBlockTypeIDAt(BlockStep.Z_MINUS), /* To east */ mapiter.getBlockTypeIDAt(BlockStep.X_MINUS) }; /* To north */ int flags = 0; for(int i = 0; i < 4; i++) if(ids[i] == REDSTONE_BLKTYPEID) flags |= (1<<i); switch(flags) { case 0: /* Nothing nearby */ return 11; case 15: /* NSEW */ return 0; /* NSEW graphic */ case 2: /* S */ case 8: /* N */ case 10: /* NS */ return 1; /* NS graphic */ case 1: /* W */ case 4: /* E */ case 5: /* EW */ return 2; /* EW graphic */ case 12: /* NE */ return 3; case 9: /* NW */ return 4; case 6: /* SE */ return 5; case 3: /* SW */ return 6; case 14: /* NSE */ return 7; case 11: /* NSW */ return 8; case 13: /* NEW */ return 9; case 7: /* SEW */ return 10; } return 0; } /** * Generate block render data for glass pane and iron fence. * - bit 0 = X-minus axis * - bit 1 = Z-minus axis * - bit 2 = X-plus axis * - bit 3 = Z-plus axis * * @param typeid - ID of our material (test is for adjacent material OR nontransparent) * @return */ private static int generateIronFenceGlassBlockData(int typeid, MapIterator mapiter) { int blockdata = 0; int id; /* Check north */ id = mapiter.getBlockTypeIDAt(BlockStep.X_MINUS); if((id == typeid) || ((id > 0) && (HDTextureMap.getTransparency(id) == BlockTransparency.OPAQUE))) { blockdata |= 1; } /* Look east */ id = mapiter.getBlockTypeIDAt(BlockStep.Z_MINUS); if((id == typeid) || ((id > 0) && (HDTextureMap.getTransparency(id) == BlockTransparency.OPAQUE))) { blockdata |= 2; } /* Look south */ id = mapiter.getBlockTypeIDAt(BlockStep.X_PLUS); if((id == typeid) || ((id > 0) && (HDTextureMap.getTransparency(id) == BlockTransparency.OPAQUE))) { blockdata |= 4; } /* Look west */ id = mapiter.getBlockTypeIDAt(BlockStep.Z_PLUS); if((id == typeid) || ((id > 0) && (HDTextureMap.getTransparency(id) == BlockTransparency.OPAQUE))) { blockdata |= 8; } return blockdata; } /** * Generate render data for doors * - bit 3 = top half (1) or bottom half (0) * - bit 2 = right hinge (0), left hinge (1) * - bit 1,0 = 00=west,01=north,10=east,11=south * @param typeid - ID of our material * @return */ private static int generateDoorBlockData(int typeid, MapIterator mapiter) { int blockdata = 0; int topdata = mapiter.getBlockData(); /* Get block data */ int bottomdata = 0; if((topdata & 0x08) != 0) { /* We're door top */ blockdata |= 0x08; /* Set top bit */ mapiter.stepPosition(BlockStep.Y_MINUS); bottomdata = mapiter.getBlockData(); mapiter.unstepPosition(BlockStep.Y_MINUS); } else { /* Else, we're bottom */ bottomdata = topdata; mapiter.stepPosition(BlockStep.Y_PLUS); topdata = mapiter.getBlockData(); mapiter.unstepPosition(BlockStep.Y_PLUS); } boolean onright = false; if((topdata & 0x01) == 1) { /* Right hinge */ blockdata |= 0x4; /* Set hinge bit */ onright = true; } blockdata |= (bottomdata & 0x3); /* Set side bits */ /* If open, rotate data appropriately */ if((bottomdata & 0x4) > 0) { if(onright) { /* Hinge on right? */ blockdata = (blockdata & 0x8) | 0x0 | ((blockdata-1) & 0x3); } else { blockdata = (blockdata & 0x8) | 0x4 | ((blockdata+1) & 0x3); } } return blockdata; } private static boolean containsID(int id, int[] linkids) { for(int i = 0; i < linkids.length; i++) if(id == linkids[i]) return true; return false; } private static int generateWireBlockData(int[] linkids, MapIterator mapiter) { int blockdata = 0; int id; /* Check north */ id = mapiter.getBlockTypeIDAt(BlockStep.X_MINUS); if(containsID(id, linkids)) { blockdata |= 1; } /* Look east */ id = mapiter.getBlockTypeIDAt(BlockStep.Z_MINUS); if(containsID(id, linkids)) { blockdata |= 2; } /* Look south */ id = mapiter.getBlockTypeIDAt(BlockStep.X_PLUS); if(containsID(id, linkids)) { blockdata |= 4; } /* Look west */ id = mapiter.getBlockTypeIDAt(BlockStep.Z_PLUS); if(containsID(id, linkids)) { blockdata |= 8; } return blockdata; } }